diff --git a/poor/config.py b/poor/config.py index 9a8c808..26890f7 100644 --- a/poor/config.py +++ b/poor/config.py @@ -111,6 +111,12 @@ def _migrate(self, values): # Impose new default providers introduced in 0.25. for option in ("geocoder", "guide", "router"): values[option] = DEFAULTS[option] + if version < (0,30): + # libosmscout and Valhalla routers merged in 0.30. + # https://github.com/otsaloma/poor-maps/pull/41 + routers = values.setdefault("routers", {}) + routers.pop("osmscout", None) + routers.pop("osmscout_valhalla", None) return values def read(self, path=None): diff --git a/routers/osmscout.json b/routers/osmscout.json index 2c0f29a..53fccd1 100644 --- a/routers/osmscout.json +++ b/routers/osmscout.json @@ -2,7 +2,7 @@ "attribution": "Data © OpenStreetMap contributors", "_description": "Routing based on OpenStreetMap data", "_modes": "car, bicycle, foot", - "name": "OSM Scout libosmscout", - "requires": ["harbour-osmscout-server"], + "name": "OSM Scout", + "requires": ["harbour-osmscout-server", "harbour-osmscout-server-module-route"], "_source": "OSM Scout offline server" } diff --git a/routers/osmscout.py b/routers/osmscout.py index dc9bb61..324d292 100644 --- a/routers/osmscout.py +++ b/routers/osmscout.py @@ -16,18 +16,59 @@ # along with this program. If not, see . """ -Routing using OSM Scout Server. +Routing using OSM Scout Server's Valhalla and libosmscout routers. https://github.com/rinigus/osmscout-server +https://github.com/valhalla/valhalla-docs/blob/master/api-reference.md """ import copy +import json import poor import urllib.parse -CONF_DEFAULTS = {"type": "car"} +CONF_DEFAULTS = {"type": "auto"} -ICONS = { +ICONS = { 0: "flag", + 1: "depart", + 2: "depart-right", + 3: "depart-left", + 4: "arrive", + 5: "arrive-right", + 6: "arrive-left", + 7: "continue", + 8: "continue", + 9: "turn-slight-right", + 10: "turn-right", + 11: "turn-sharp-right", + 12: "uturn", + 13: "uturn", + 14: "turn-sharp-left", + 15: "turn-left", + 16: "turn-slight-left", + 17: "continue", + 18: "off-ramp-slight-right", + 19: "off-ramp-slight-left", + 20: "off-ramp-slight-right", + 21: "off-ramp-slight-left", + 22: "fork-straight", + 23: "fork-slight-right", + 24: "fork-slight-left", + 25: "merge-slight-left", + 26: "roundabout", + 27: "off-ramp-slight-right", + 28: "flag", + 29: "flag", + 30: "flag", + 31: "flag", + 32: "flag", + 33: "flag", + 34: "flag", + 35: "flag", + 36: "flag", +} + +ICONS_OSMSCOUT = { "destination": "arrive", "flag": "flag", "merge": "merge-slight-left", @@ -46,36 +87,62 @@ "turn-slight-right": "turn-slight-right", } -URL = "http://localhost:8553/v1/route?type={type}" +URL = "http://localhost:8553/v2/route?json={input}" cache = {} -def render_endpoint(i, point): - """Return URL component for given endpoint.""" +def prepare_endpoint(point): + """Return `point` as a dictionary ready to be passed on to the router.""" if isinstance(point, (list, tuple)): - return ("&p[{i:d}][lng]={x:.6f}&p[{i:d}][lat]={y:.6f}" - .format(i=i, x=point[0], y=point[1])) - point = urllib.parse.quote_plus(point) - return "&p[{i:d}][search]={point}".format(i=i, point=point) + return dict(lat=point[1], lon=point[0]) + geocoder = poor.Geocoder("osmscout") + results = geocoder.geocode(point, dict(limit=1)) + return prepare_endpoint((results[0]["x"], results[0]["y"])) def route(fm, to, params): """Find route and return its properties as a dictionary.""" - type = poor.conf.routers.osmscout.type + fm, to = map(prepare_endpoint, (fm, to)) + lang = poor.util.get_default_language("en") + input = dict(locations=[fm, to], + costing=poor.conf.routers.osmscout.type, + directions_options=dict(language=lang)) + + input = urllib.parse.quote(json.dumps(input)) url = URL.format(**locals()) - for i, point in enumerate((fm, to)): - url += render_endpoint(i, point) with poor.util.silent(KeyError): return copy.deepcopy(cache[url]) result = poor.http.get_json(url) + if result.get("API version", "") == "libosmscout V1": + return parse_result_libosmscout(url, result) + return parse_result_valhalla(url, result) + +def parse_result_libosmscout(url, result): + """Parse and return route from libosmscout engine.""" x, y = result["lng"], result["lat"] maneuvers = [dict( x=float(maneuver["lng"]), y=float(maneuver["lat"]), - icon=ICONS.get(maneuver.get("type", "flag"), "flag"), + icon=ICONS_OSMSCOUT.get(maneuver.get("type", "flag"), "flag"), narrative=maneuver["instruction"], duration=float(maneuver["time"]), length=float(maneuver["length"]), ) for maneuver in result["maneuvers"]] - route = dict(x=x, y=y, maneuvers=maneuvers) + route = dict(x=x, y=y, maneuvers=maneuvers, engine="libosmscout") + if route and route["x"]: + cache[url] = copy.deepcopy(route) + return route + +def parse_result_valhalla(url, result): + """Parse and return route from Valhalla engine.""" + legs = result["trip"]["legs"][0] + x, y = poor.util.decode_epl(legs["shape"], precision=6) + maneuvers = [dict( + x=float(x[maneuver["begin_shape_index"]]), + y=float(y[maneuver["begin_shape_index"]]), + icon=ICONS.get(maneuver["type"], "flag"), + narrative=maneuver["instruction"], + duration=float(maneuver["time"]), + ) for maneuver in legs["maneuvers"]] + route = dict(x=x, y=y, maneuvers=maneuvers, engine="Valhalla") if route and route["x"]: cache[url] = copy.deepcopy(route) return route diff --git a/routers/osmscout_results.qml b/routers/osmscout_results.qml index 566c305..6d09396 100644 --- a/routers/osmscout_results.qml +++ b/routers/osmscout_results.qml @@ -26,7 +26,6 @@ Page { property bool loading: true BusyModal { id: busy - description: qsTranslate("", "For long routes, this could take up to a minute.") running: page.loading } onStatusChanged: { @@ -50,7 +49,11 @@ Page { "x": route.x, "y": route.y, "mode": "car", - "attribution": qsTranslate("", "Routing courtesy of %1.").arg("OSM Scout Server") + // TRANSLATORS: %1 refers to the routing engine, %2 to the service. + "attribution": (qsTranslate("", "Routing by %1, courtesy of %2") + .arg(route.engine) + .arg("OSM Scout Server")) + }); map.hidePoiBubbles(); map.fitViewToRoute(); diff --git a/routers/osmscout_settings.qml b/routers/osmscout_settings.qml index 25412a0..e254270 100644 --- a/routers/osmscout_settings.qml +++ b/routers/osmscout_settings.qml @@ -28,7 +28,7 @@ Column { MenuItem { text: qsTranslate("", "Bicycle") } MenuItem { text: qsTranslate("", "Foot") } } - property var keys: ["car", "bicycle", "foot"] + property var keys: ["auto", "bicycle", "pedestrian"] Component.onCompleted: { var key = app.conf.get("routers.osmscout.type"); typeComboBox.currentIndex = typeComboBox.keys.indexOf(key); diff --git a/routers/osmscout_valhalla.json b/routers/osmscout_valhalla.json deleted file mode 100644 index c09ffa4..0000000 --- a/routers/osmscout_valhalla.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "attribution": "Data © OpenStreetMap contributors", - "_description": "Routing based on OpenStreetMap data", - "_modes": "car, bicycle, foot", - "name": "OSM Scout Valhalla", - "requires": ["harbour-osmscout-server", "harbour-osmscout-server-module-route"], - "_source": "OSM Scout offline server" -} diff --git a/routers/osmscout_valhalla.py b/routers/osmscout_valhalla.py deleted file mode 100644 index 789f478..0000000 --- a/routers/osmscout_valhalla.py +++ /dev/null @@ -1,105 +0,0 @@ -# -*- coding: utf-8 -*- - -# Copyright (C) 2015 Osmo Salomaa -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -""" -Routing using OSM Scout Server's Valhalla router. - -https://github.com/rinigus/osmscout-server -https://github.com/valhalla/valhalla-docs/blob/master/api-reference.md -""" - -import copy -import json -import poor -import urllib.parse - -CONF_DEFAULTS = {"type": "auto"} - -ICONS = { 0: "flag", - 1: "depart", - 2: "depart-right", - 3: "depart-left", - 4: "arrive", - 5: "arrive-right", - 6: "arrive-left", - 7: "continue", - 8: "continue", - 9: "turn-slight-right", - 10: "turn-right", - 11: "turn-sharp-right", - 12: "uturn", - 13: "uturn", - 14: "turn-sharp-left", - 15: "turn-left", - 16: "turn-slight-left", - 17: "continue", - 18: "off-ramp-slight-right", - 19: "off-ramp-slight-left", - 20: "off-ramp-slight-right", - 21: "off-ramp-slight-left", - 22: "fork-straight", - 23: "fork-slight-right", - 24: "fork-slight-left", - 25: "merge-slight-left", - 26: "roundabout", - 27: "off-ramp-slight-right", - 28: "flag", - 29: "flag", - 30: "flag", - 31: "flag", - 32: "flag", - 33: "flag", - 34: "flag", - 35: "flag", - 36: "flag", -} - -URL = "http://localhost:8553/v2/route?json={input}" -cache = {} - -def prepare_endpoint(point): - """Return `point` as a dictionary ready to be passed on to the router.""" - if isinstance(point, (list, tuple)): - return dict(lat=point[1], lon=point[0]) - geocoder = poor.Geocoder("osmscout") - results = geocoder.geocode(point, dict(limit=1)) - return prepare_endpoint((results[0]["x"], results[0]["y"])) - -def route(fm, to, params): - """Find route and return its properties as a dictionary.""" - fm, to = map(prepare_endpoint, (fm, to)) - type = poor.conf.routers.osmscout_valhalla.type - input = dict(locations=[fm, to], costing=type, - directions_options={"language": poor.util.get_default_language("en")}) - input = urllib.parse.quote(json.dumps(input)) - url = URL.format(**locals()) - with poor.util.silent(KeyError): - return copy.deepcopy(cache[url]) - result = poor.http.get_json(url) - legs = result["trip"]["legs"][0] - x, y = poor.util.decode_epl(legs["shape"], precision=6) - maneuvers = [dict( - x=float(x[maneuver["begin_shape_index"]]), - y=float(y[maneuver["begin_shape_index"]]), - icon=ICONS.get(maneuver["type"], "flag"), - narrative=maneuver["instruction"], - duration=float(maneuver["time"]), - ) for maneuver in legs["maneuvers"]] - route = dict(x=x, y=y, maneuvers=maneuvers) - if route and route["x"]: - cache[url] = copy.deepcopy(route) - return route diff --git a/routers/osmscout_valhalla_results.qml b/routers/osmscout_valhalla_results.qml deleted file mode 100644 index ef8459a..0000000 --- a/routers/osmscout_valhalla_results.qml +++ /dev/null @@ -1,68 +0,0 @@ -/* -*- coding: utf-8-unix -*- - * - * Copyright (C) 2014 Osmo Salomaa - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -import QtQuick 2.0 -import Sailfish.Silica 1.0 -import "../qml" - -Page { - id: page - allowedOrientations: app.defaultAllowedOrientations - property bool loading: true - BusyModal { - id: busy - running: page.loading - } - onStatusChanged: { - if (page.status === PageStatus.Activating) { - busy.text = qsTranslate("", "Searching"); - } else if (page.status === PageStatus.Active) { - page.findRoute(); - } - } - function findRoute() { - // Load routing results from the Python backend. - var routePage = app.pageStack.previousPage(); - var args = [routePage.from, routePage.to]; - py.call("poor.app.router.route", args, function(route) { - if (route && route.error && route.message) { - busy.error = route.message; - page.loading = false; - } else if (route && route.x && route.x.length > 0) { - app.hideMenu(); - map.addRoute({ - "x": route.x, - "y": route.y, - "mode": "car", - // TRANSLATORS: %1 refers to the routing engine, %2 to the service. - "attribution": (qsTranslate("", "Routing by %1, courtesy of %2") - .arg("Valhalla") - .arg("OSM Scout Server")) - - }); - map.hidePoiBubbles(); - map.fitViewToRoute(); - map.addManeuvers(route.maneuvers); - app.pageStack.navigateBack(PageStackAction.Immediate); - } else { - busy.error = qsTranslate("", "No results"); - page.loading = false; - } - }); - } -} diff --git a/routers/osmscout_valhalla_settings.qml b/routers/osmscout_valhalla_settings.qml deleted file mode 100644 index 9214f96..0000000 --- a/routers/osmscout_valhalla_settings.qml +++ /dev/null @@ -1,41 +0,0 @@ -/* -*- coding: utf-8-unix -*- - * - * Copyright (C) 2014 Osmo Salomaa - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -import QtQuick 2.0 -import Sailfish.Silica 1.0 - -Column { - ComboBox { - id: typeComboBox - label: qsTranslate("", "Type") - menu: ContextMenu { - MenuItem { text: qsTranslate("", "Car") } - MenuItem { text: qsTranslate("", "Bicycle") } - MenuItem { text: qsTranslate("", "Foot") } - } - property var keys: ["auto", "bicycle", "pedestrian"] - Component.onCompleted: { - var key = app.conf.get("routers.osmscout_valhalla.type"); - typeComboBox.currentIndex = typeComboBox.keys.indexOf(key); - } - onCurrentIndexChanged: { - var option = "routers.osmscout_valhalla.type"; - app.conf.set(option, typeComboBox.keys[typeComboBox.currentIndex]); - } - } -}