[gnome-maps/wip/osrm-routing: 10/24] Add preliminary routing using OSRM
- From: Mattias Bengtsson <mattiasb src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-maps/wip/osrm-routing: 10/24] Add preliminary routing using OSRM
- Date: Wed, 3 Jul 2013 22:38:49 +0000 (UTC)
commit 236f7a89f3a336eb649fd4f4afd230a2af5e3db1
Author: Jussi Kukkonen <jku goto fi>
Date: Fri Apr 12 22:09:44 2013 +0300
Add preliminary routing using OSRM
It's missing major features to the point of being useless,
but a search result can now be used as the end point of a route.
User location will be the starting point.
src/Makefile-js.am | 1 +
src/mapLocation.js | 14 +++-
src/mapView.js | 51 ++++++++++
src/osrm.js | 281 ++++++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 346 insertions(+), 1 deletions(-)
---
diff --git a/src/Makefile-js.am b/src/Makefile-js.am
index 3caf4bc..01078c9 100644
--- a/src/Makefile-js.am
+++ b/src/Makefile-js.am
@@ -5,6 +5,7 @@ dist_js_DATA = \
mainWindow.js \
mapLocation.js \
mapView.js \
+ osrm.js \
path.js \
sidebar.js \
utils.js \
diff --git a/src/mapLocation.js b/src/mapLocation.js
index 151094c..8c1e92e 100644
--- a/src/mapLocation.js
+++ b/src/mapLocation.js
@@ -23,6 +23,8 @@
const Clutter = imports.gi.Clutter;
const Champlain = imports.gi.Champlain;
const Geocode = imports.gi.GeocodeGlib;
+const Gtk = imports.gi.Gtk;
+const GtkClutter = imports.gi.GtkClutter;
const Lang = imports.lang;
const Mainloop = imports.mainloop;
@@ -104,13 +106,23 @@ const MapLocation = new Lang.Class({
margin_right: 12 });
bubble.add_child(box);
- let text = new Clutter.Text({ text: this.description });
+ let text = new Clutter.Text({ text: this.description,
+ x_expand: true });
text.set_color(new Clutter.Color({ red: 255,
blue: 255,
green: 255,
alpha: 255 }));
box.add_child(text);
+ let button = new Gtk.Button ({ label: "Route to this location" });
+ button.connect ("clicked", Lang.bind (this,
+ function () {
+ this.emit('route-request');
+ }));
+ button.show();
+ let buttonActor = new GtkClutter.Actor({ contents: button });
+ box.add_child (buttonActor);
+
layer.add_marker (bubble);
log("Added marker at " + this.latitude + ", " + this.longitude);
},
diff --git a/src/mapView.js b/src/mapView.js
index 0bfe944..075d15c 100644
--- a/src/mapView.js
+++ b/src/mapView.js
@@ -24,6 +24,7 @@ const Clutter = imports.gi.Clutter;
const Cogl = imports.gi.Cogl;
const Gdk = imports.gi.Gdk;
const GdkPixbuf = imports.gi.GdkPixbuf;
+const GLib = imports.gi.GLib;
const Gtk = imports.gi.Gtk;
const GtkChamplain = imports.gi.GtkChamplain;
const Champlain = imports.gi.Champlain;
@@ -33,12 +34,14 @@ const Lang = imports.lang;
const Mainloop = imports.mainloop;
const Signals = imports.signals;
+const Application = imports.application;
const Sidebar = imports.sidebar;
const Utils = imports.utils;
const Path = imports.path;
const MapLocation = imports.mapLocation;
const UserLocation = imports.userLocation;
const Geoclue = imports.geoclue;
+const osrm = imports.osrm;
const _ = imports.gettext.gettext;
const MapType = {
@@ -70,6 +73,13 @@ const MapView = new Lang.Class({
this._markerLayer.set_selection_mode(Champlain.SelectionMode.SINGLE);
this.view.add_layer(this._markerLayer);
+ this._routeLayer = new Champlain.PathLayer();
+ this._routeLayer.set_stroke_width(2.0);
+ this.view.add_layer(this._routeLayer);
+
+ this._instructionsLayer = new Champlain.MarkerLayer();
+ this.view.add_layer(this._instructionsLayer);
+
this._userLocationLayer = new Champlain.MarkerLayer();
this._userLocationLayer.set_selection_mode(Champlain.SelectionMode.SINGLE);
this.view.add_layer(this._userLocationLayer);
@@ -80,6 +90,45 @@ const MapView = new Lang.Class({
this._showUserLocation();
},
+ _route_request: function(toLocation) {
+ let fromLocation = this._userLocation;
+ let router = new osrm.Router();
+
+ this._routeLayer.remove_all();
+ this._instructionsLayer.remove_all();
+
+ router.addViaPoint(fromLocation.latitude, fromLocation.longitude);
+ router.addViaPoint(toLocation.latitude, toLocation.longitude);
+
+ router.calculateRoute(Lang.bind(this, function(route) {
+ if (!route) {
+ log("No route found");
+ /* TODO: ? */
+ return;
+ }
+ log("Got a " +route.length+ "m route with " + route.points.length + " nodes and " +
+ route.instructions.length + " turn instructions.");
+
+ for (let i = 0; i < route.points.length; i++) {
+ let coord = new Champlain.Coordinate();
+ coord.set_location(route.points[i]._lat,
+ route.points[i]._lon);
+ this._routeLayer.add_node(coord);
+ }
+
+ for (let i = 0; i < route.instructions.length; i++) {
+ let coord = new Champlain.Point();
+ coord.set_size(8.0);
+ coord.set_location(route.instructions[i]._lat,
+ route.instructions[i]._lon);
+ this._instructionsLayer.add_marker(coord);
+ }
+ this._routeLayer.set_visible(true);
+
+ this.ensureVisible([fromLocation, toLocation]);
+ }));
+ },
+
setMapType: function(mapType) {
let source = this._factory.create_cached_source(mapType);
this.view.set_map_source(source);
@@ -101,6 +150,8 @@ const MapView = new Lang.Class({
return;
let mapLocation = new MapLocation.MapLocation(location, this);
+ mapLocation.connect("route-request",
+ Lang.bind(this, this._route_request));
mapLocations.push(mapLocation);
}));
this._showLocations(mapLocations);
diff --git a/src/osrm.js b/src/osrm.js
new file mode 100644
index 0000000..aaa8052
--- /dev/null
+++ b/src/osrm.js
@@ -0,0 +1,281 @@
+const GLib = imports.gi.GLib;
+const Soup = imports.gi.Soup;
+const Lang = imports.lang;
+
+const Direction = {
+ "N": "north",
+ "NE": "northeast",
+ "E": "east",
+ "SE": "southeast",
+ "S": "south",
+ "SW": "southwest",
+ "W": "west",
+ "NW": "northwest",
+}
+
+// https://github.com/DennisOSRM/Project-OSRM/blob/master/DataStructures/TurnInstructions.h
+// We'll need strings without street names as well
+const TurnInstruction = {
+ "0": "", // No instruction
+ "1": "Continue on {WAYNAME}",
+ "2": "Turn slightly right onto {WAYNAME}",
+ "3": "Turn right onto {WAYNAME}",
+ "4": "Turn sharp right onto {WAYNAME}",
+ "5": "Make a U-turn on {WAYNAME}",
+ "6": "Turn sharp left onto {WAYNAME}",
+ "7": "Turn left onto {WAYNAME}",
+ "8": "Turn slightly left onto {WAYNAME}",
+ "9": "You have reached a waypoint",
+ "10": "Head {DIR} on {WAYNAME}", // start of route
+ "11": "Enter roundabout",
+ "11-1": "Enter roundabout and leave at first exit",
+ "11-2": "Enter roundabout and leave at second exit",
+ "11-3": "Enter roundabout and leave at third exit",
+ "11-4": "Enter roundabout and leave at fourth exit",
+ "11-5": "Enter roundabout and leave at fifth exit",
+ "11-6": "Enter roundabout and leave at sixth exit",
+ "11-7": "Enter roundabout and leave at seventh exit",
+ "11-8": "Enter roundabout and leave at eighth exit",
+ "11-9": "Enter roundabout and leave at ninth exit",
+ "12": "Leave roundabout",
+ "13": "Stay on roundabout",
+ "14": "Start at end of {WAYNAME}", // ?
+ "15": "You have reached your destination",
+ "16": "Enter against allowed direction", // ?
+ "17": "Leave against allowed direction" // ?
+}
+
+const RoutePoint = new Lang.Class({
+ Name: 'RoutePoint',
+
+ _init: function(lat, lon) {
+ this._lat = lat;
+ this._lon = lon;
+ },
+
+ setInstructions: function(turn_instruction, dir, name, length, time) {
+ this._turn_instruction = turn_instruction;
+ this._way_name = name;
+ this._direction = dir;
+ this.length = length;
+ this._time = time;
+ },
+
+ getInstructionString: function() {
+ if (!this._turn_instruction)
+ return null;
+ let string = TurnInstruction[this._turn_instruction]
+
+ let wayname = this._way_name ? this._way_name : "unnamed street";
+ string = string.replace(/{WAYNAME}/g, wayname);
+
+ string = string.replace(/{DIR}/g, Direction[this._direction]);
+
+ return string + " (" + this.length + "m)";
+ },
+});
+
+const Route = new Lang.Class({
+ Name: 'Route',
+
+ _init: function(osrm_json) {
+ this.points = []
+ this.instructions = []
+ if (!osrm_json) {
+ log("Route json not valid");
+ return;
+ }
+
+ if (osrm_json.status != "0") {
+ log("Route has status " + osrm_json.status)
+ return;
+ }
+
+ if (osrm_json.route_summary) {
+ this.length = osrm_json.route_summary.total_distance;
+ this._start_point = osrm_json.route_summary.start_point;
+ this._end_point = osrm_json.route_summary.end_point;
+ }
+ [this.points, this.instructions] = this._build_route(osrm_json);
+ },
+
+ _build_route: function(json) {
+ let points = this._decodePolyline(json.route_geometry);
+ let instruction_points = this._apply_instructions(points, json.route_instructions);
+ return [points, instruction_points];
+ },
+
+ _apply_instructions: function(points, instructions) {
+ if (!points)
+ return [];
+
+ let instruction_points = [];
+ for (let i = 0; i < instructions.length; i++) {
+ // 0: turn instruction, see TurnInstruction
+ // 1: way name
+ // 2: length (m)
+ // 3: point index
+ // 4: time (s)
+ // 5: length string with unit
+ // 6: direction abbreviation
+ // 7: azimuth
+
+ let point = points[instructions[i][3]];
+ if (!point) {
+ log("Turn instruction for non-existing point " +
+ instructions[i][3]);
+ continue;
+ }
+ if (!TurnInstruction[instructions[i][0]]) {
+ log("Unknown turn instruction " + instructions[i][0]);
+ continue;
+ }
+ point.setInstructions(instructions[i][0],
+ instructions[i][6],
+ instructions[i][1],
+ instructions[i][2],
+ instructions[i][4]);
+ instruction_points.push(point);
+ }
+ return instruction_points;
+ },
+
+ _decodeValue: function(data, index) {
+ let b;
+ let shift = 0;
+ let value = 0;
+
+ do {
+ // 63 added to keep string printable
+ b = data.charCodeAt(index++) - 63;
+
+ // Get 5 bits at a time until hit the end of value
+ // (which is not OR'd with 0x20)
+ value |= (b & 0x1f) << shift;
+ shift += 5;
+ } while (b >= 0x20);
+
+ // negative values are encoded as two's complement
+ let ret_val = ((value & 1) ? ~(value >> 1) : (value >> 1));
+ return [ret_val, index];
+ },
+
+ // Decode a Google encoded polyline
+ // https://developers.google.com/maps/documentation/utilities/polylinealgorithm
+ _decodePolyline: function(data) {
+ let length = data.length;
+ let polyline = [];
+ let index = 0;
+ let lat = 0;
+ let lon = 0;
+
+ while (index < length) {
+ let latdelta, londelta;
+
+ [latdelta, index] = this._decodeValue(data, index);
+ [londelta, index] = this._decodeValue(data, index);
+
+ // first value is absolute, rest are relative to previous value
+ lat += latdelta;
+ lon += londelta;
+ polyline.push(new RoutePoint(lat * 1e-5, lon * 1e-5));
+ }
+ return polyline;
+ },
+});
+
+const ViaPoint = new Lang.Class({
+ Name: 'ViaPoint',
+
+ _init: function(lat, lon) {
+ this._lat = lat;
+ this._lon = lon;
+ this._hint = null;
+ },
+
+ toString: function() {
+ let hint = ""
+ if (this._hint)
+ hint = "&hint=" + this._hint
+ return "loc=" + this._lat + "," + this._lon + hint
+ },
+
+ setHint: function(hint) {
+ this._hint = hint;
+ },
+});
+
+const Router = new Lang.Class({
+ Name: 'Router',
+
+ _init: function(server) {
+ this._server = server || 'http://router.project-osrm.org';
+ this._alternatives = false;
+ this.instructions = true;
+ this._zoom = 18;
+ this._viaPoints = [];
+ this._checksum = null;
+ this._callback = null;
+ this._session = new Soup.SessionAsync({ user_agent : "gnome-maps " });
+ },
+
+ _updateHints: function(hint_locations, checksum) {
+ this._checksum = checksum;
+ for (let i = 0; i < hint_locations.length; i++)
+ if (this._viaPoints[i])
+ this._viaPoints[i].setHint(hint_locations[i]);
+ },
+
+ _onReply: function(session, message) {
+ if (message.status_code !== 200) {
+ log("Error: " + message.status_code);
+ this._callback(null);
+ }
+
+ let json = JSON.parse(message.response_body.data);
+ if (json.hint_data)
+ this._updateHints(json.hint_data.locations,
+ json.hint_data.checksum);
+
+ this._callback(new Route(json));
+ },
+
+ _buildURL: function() {
+ let points= "";
+ for (let i = 0; i < this._viaPoints.length; i++)
+ points += this._viaPoints[i].toString() + "&"
+
+ let checksum = "";
+ if (this._checksum)
+ checksum = "checksum=" + this._checksum + "&"
+
+ return this._server + '/viaroute?' +
+ points +
+ checksum +
+ "z=" + this._zoom + "&" +
+ 'instructions=' + this.instructions + "&" +
+ 'alt=' + this._alternatives;
+ },
+
+ calculateRoute: function(callback) {
+ if (this._viaPoints.length < 2) {
+ callback(null)
+ return;
+ }
+ this._callback = callback;
+ let request = Soup.Message.new('GET', this._buildURL());
+ this._session.queue_message(request, Lang.bind(this, this._onReply));
+ },
+
+ addViaPoint: function(lat, lon) {
+ this._viaPoints.push(new ViaPoint(lat, lon));
+ },
+
+ insertViaPoint: function(index, lat, lon) {
+ this._viaPoints.splice(index, 0, new ViaPoint(lat, lon));
+ },
+
+ removeViaPoint: function(index) {
+ this._viaPoints.splice(index, 1);
+ },
+});
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]