From: Richard Fairhurst Date: Sat, 17 May 2014 07:57:03 +0000 (+0100) Subject: Merge branch 'jfire2' into routing X-Git-Tag: live~4824^2~15 X-Git-Url: https://git.openstreetmap.org./rails.git/commitdiff_plain/bf5f842c23a469cf2098ae7953f1f8cd9534e361?hp=23ffdadb565be5ccc4ae9fad9647bdf0fa9b8683 Merge branch 'jfire2' into routing --- diff --git a/app/assets/javascripts/index.js b/app/assets/javascripts/index.js index 52b8b4c23..1c0812485 100644 --- a/app/assets/javascripts/index.js +++ b/app/assets/javascripts/index.js @@ -13,14 +13,15 @@ //= require index/history //= require index/note //= require index/new_note +//= require index/directions //= require router -//= require routing -//= require_tree ./routing_engines -(function() { +$(document).ready(function () { var loaderTimeout; OSM.loadSidebarContent = function(path, callback) { + map.setSidebarOverlaid(false); + clearTimeout(loaderTimeout); loaderTimeout = setTimeout(function() { @@ -68,9 +69,7 @@ } }); }; -})(); -$(document).ready(function () { var params = OSM.mapParams(); var map = new L.OSM.Map("map", { @@ -223,10 +222,8 @@ $(document).ready(function () { OSM.Index = function(map) { var page = {}; - page.pushstate = function() { - $("#content").addClass("overlay-sidebar"); - map.invalidateSize({pan: false}) - .panBy([-350, 0], {animate: false}); + page.pushstate = page.popstate = function() { + map.setSidebarOverlaid(true); document.title = I18n.t('layouts.project_name.title'); }; @@ -237,18 +234,6 @@ $(document).ready(function () { return map.getState(); }; - page.popstate = function() { - $("#content").addClass("overlay-sidebar"); - map.invalidateSize({pan: false}); - document.title = I18n.t('layouts.project_name.title'); - }; - - page.unload = function() { - map.panBy([350, 0], {animate: false}); - $("#content").removeClass("overlay-sidebar"); - map.invalidateSize({pan: false}); - }; - return page; }; @@ -287,6 +272,7 @@ $(document).ready(function () { OSM.router = OSM.Router(map, { "/": OSM.Index(map), "/search": OSM.Search(map), + "/directions": OSM.Directions(map), "/export": OSM.Export(map), "/note/new": OSM.NewNote(map), "/history/friends": history, @@ -322,62 +308,4 @@ $(document).ready(function () { if (OSM.router.route(this.pathname + this.search + this.hash)) e.preventDefault(); }); - - $(".search_form").on("submit", function(e) { - e.preventDefault(); - if ($(".query_wrapper.routing").is(":visible")) { - // Routing - OSM.routing.requestRoute(true, true); - } else { - // Search - $("header").addClass("closed"); - var query = $(this).find("input[name=query]").val(); - if (query) { - OSM.router.route("/search?query=" + encodeURIComponent(query) + OSM.formatHash(map)); - } else { - OSM.router.route("/" + OSM.formatHash(map)); - } - } - }); - - $(".describe_location").on("click", function(e) { - e.preventDefault(); - var precision = OSM.zoomPrecision(map.getZoom()); - OSM.router.route("/search?query=" + encodeURIComponent( - map.getCenter().lat.toFixed(precision) + "," + - map.getCenter().lng.toFixed(precision))); - }); - - OSM.routing = OSM.Routing(map,'OSM.routing',$('.query_wrapper.routing')); - OSM.routing.chooseEngine('javascripts.directions.engines.osrm_car'); - - $(".get_directions").on("click",function(e) { - e.preventDefault(); - $(".search").hide(); - $(".routing").show(); - $(".search_form input[type='submit']").addClass("routing_submit"); - $(".query_wrapper.routing [name=route_from]").focus(); - $("#map").on('dragend dragover',function(e) { e.preventDefault(); }); - $("#map").on('drop',function(e) { OSM.routing.handleDrop(e); e.preventDefault(); }); - $(".routing_marker").on('dragstart',function(e) { - e.originalEvent.dataTransfer.effectAllowed = 'move'; - e.originalEvent.dataTransfer.setData('id', this.id); - var xo=e.originalEvent.clientX - $(e.target).offset().left; - var yo=e.originalEvent.clientY - $(e.target).offset().top; - e.originalEvent.dataTransfer.setData('offsetX', e.originalEvent.target.width/2 - xo); - e.originalEvent.dataTransfer.setData('offsetY', e.originalEvent.target.height - yo); - }); - }); - - $(".close_directions").on("click",function(e) { - e.preventDefault(); - $(".search").show(); - $(".routing").hide(); - $(".search_form input[type='submit']").removeClass("routing_submit"); - OSM.routing.close(); - $("#map").off('dragend drop dragover'); - $(".routing_marker").off('dragstart'); - $(".query_wrapper.search [name=query]").focus(); - }); - }); diff --git a/app/assets/javascripts/index/directions.js.erb b/app/assets/javascripts/index/directions.js.erb new file mode 100644 index 000000000..3520f3ecd --- /dev/null +++ b/app/assets/javascripts/index/directions.js.erb @@ -0,0 +1,329 @@ +//= require_self +//= require_tree ./directions_engines + +OSM.Directions = function (map) { + var awaitingGeocode; // true if the user has requested a route, but we're waiting on a geocode result + var awaitingRoute; // true if we've asked the engine for a route and are waiting to hear back + var dragging; // true if the user is dragging a start/end point + var chosenEngine; + + var popup = L.popup(); + + var polyline = L.polyline([], { + color: '#03f', + opacity: 0.3, + weight: 10 + }); + + var highlight = L.polyline([], { + color: '#ff0', + opacity: 0.5, + weight: 12 + }); + + var endpoints = [ + Endpoint($("input[name='route_from']"), <%= asset_path('marker-green.png').to_json %>), + Endpoint($("input[name='route_to']"), <%= asset_path('marker-red.png').to_json %>) + ]; + + function Endpoint(input, iconUrl) { + var endpoint = {}; + + endpoint.marker = L.marker([0, 0], { + icon: L.icon({ + iconUrl: iconUrl, + iconSize: [25, 41], + iconAnchor: [12, 41], + popupAnchor: [1, -34], + shadowUrl: <%= asset_path('images/marker-shadow.png').to_json %>, + shadowSize: [41, 41] + }), + draggable: true + }); + + endpoint.marker.on('drag dragend', function (e) { + dragging = (e.type == 'drag'); + if (dragging && !chosenEngine.draggable) return; + if (dragging && awaitingRoute) return; + endpoint.setLatLng(e.target.getLatLng()); + if (map.hasLayer(polyline)) { + getRoute(); + } + }); + + input.on("change", function (e) { + endpoint.awaitingGeocode = true; + + $.getJSON('<%= NOMINATIM_URL %>search?q=' + encodeURIComponent(e.target.value) + '&format=json', function (json) { + endpoint.awaitingGeocode = false; + + if (json.length == 0) { + alert(I18n.t('javascripts.directions.errors.no_place')); + return; + } + + input.val(json[0].display_name); + + endpoint.latlng = L.latLng(json[0]); + endpoint.marker + .setLatLng(endpoint.latlng) + .addTo(map); + + if (awaitingGeocode) { + awaitingGeocode = false; + getRoute(); + } + }); + }); + + endpoint.setLatLng = function (ll) { + var precision = OSM.zoomPrecision(map.getZoom()); + input.val(ll.lat.toFixed(precision) + ", " + ll.lng.toFixed(precision)); + endpoint.latlng = ll; + endpoint.marker + .setLatLng(ll) + .addTo(map); + }; + + return endpoint; + } + + function formatDistance(m) { + if (m < 1000) { + return Math.round(m) + "m"; + } else if (m < 10000) { + return (m / 1000.0).toFixed(1) + "km"; + } else { + return Math.round(m / 1000) + "km"; + } + } + + function formatTime(s) { + var m = Math.round(s / 60); + var h = Math.floor(m / 60); + m -= h * 60; + return h + ":" + (m < 10 ? '0' : '') + m; + } + + function setEngine(id) { + engines.forEach(function(engine, i) { + if (engine.id == id) { + chosenEngine = engine; + select.val(i); + } + }); + } + + function getRoute() { + if (endpoints[0].awaitingGeocode || endpoints[1].awaitingGeocode) { + awaitingGeocode = true; + return; + } + + var o = endpoints[0].latlng, + d = endpoints[1].latlng; + + if (!o || !d) return; + + var precision = OSM.zoomPrecision(map.getZoom()); + + OSM.router.replace("/directions?" + querystring.stringify({ + engine: chosenEngine.id, + route: o.lat.toFixed(precision) + ',' + o.lng.toFixed(precision) + ';' + + d.lat.toFixed(precision) + ',' + d.lng.toFixed(precision) + })); + + $(".directions_form .spinner").show(); + awaitingRoute = true; + + chosenEngine.getRoute([o, d], function (err, route) { + awaitingRoute = false; + + $(".directions_form .spinner").hide(); + + if (err) { + map.removeLayer(polyline); + + if (!dragging) { + alert(I18n.t('javascripts.directions.errors.no_route')); + } + + return; + } + + polyline + .setLatLngs(route.line) + .addTo(map); + + map.setSidebarOverlaid(false); + + if (!dragging) { + map.fitBounds(polyline.getBounds().pad(0.05)); + } + + var html = '

' + + '' + I18n.t('javascripts.directions.directions') + + '

' + + I18n.t('javascripts.directions.distance') + ': ' + formatDistance(route.distance) + '. ' + + I18n.t('javascripts.directions.time') + ': ' + formatTime(route.time) + '.

' + + ''; + + $('#sidebar_content') + .html(html); + + // Add each row + var cumulative = 0; + route.steps.forEach(function (step) { + var ll = step[0], + direction = step[1], + instruction = step[2], + dist = step[3], + lineseg = step[4]; + + cumulative += dist; + + if (dist < 5) { + dist = ""; + } else if (dist < 200) { + dist = Math.round(dist / 10) * 10 + "m"; + } else if (dist < 1500) { + dist = Math.round(dist / 100) * 100 + "m"; + } else if (dist < 5000) { + dist = Math.round(dist / 100) / 10 + "km"; + } else { + dist = Math.round(dist / 1000) + "km"; + } + + var row = $(""); + row.append("
"); + row.append("" + instruction); + row.append("" + dist); + + row.on('click', function () { + popup + .setLatLng(ll) + .setContent("

" + instruction + "

") + .openOn(map); + }); + + row.hover(function () { + highlight + .setLatLngs(lineseg) + .addTo(map); + }, function () { + map.removeLayer(highlight); + }); + + $('#turnbyturn').append(row); + }); + + $('#sidebar_content').append('

' + + I18n.t('javascripts.directions.instructions.courtesy', {link: chosenEngine.creditline}) + + '

'); + }); + } + + var engines = OSM.Directions.engines; + + engines.sort(function (a, b) { + a = I18n.t('javascripts.directions.engines.' + a.id); + b = I18n.t('javascripts.directions.engines.' + b.id); + return a.localeCompare(b); + }); + + var select = $('select.routing_engines'); + + engines.forEach(function(engine, i) { + select.append(""); + }); + + setEngine('osrm_car'); + + select.on("change", function (e) { + chosenEngine = engines[e.target.selectedIndex]; + if (map.hasLayer(polyline)) { + getRoute(); + } + }); + + $(".directions_form").on("submit", function(e) { + e.preventDefault(); + $("header").addClass("closed"); + getRoute(); + }); + + $(".routing_marker").on('dragstart', function (e) { + e.originalEvent.dataTransfer.effectAllowed = 'move'; + e.originalEvent.dataTransfer.setData('id', this.id); + var xo = e.originalEvent.clientX - $(e.target).offset().left; + var yo = e.originalEvent.clientY - $(e.target).offset().top; + e.originalEvent.dataTransfer.setData('offsetX', e.originalEvent.target.width / 2 - xo); + e.originalEvent.dataTransfer.setData('offsetY', e.originalEvent.target.height - yo); + }); + + var page = {}; + + page.pushstate = page.popstate = function() { + $(".search_form").hide(); + $(".directions_form").show(); + + $("#map").on('dragend dragover', function (e) { + e.preventDefault(); + }); + + $("#map").on('drop', function (e) { + e.preventDefault(); + var oe = e.originalEvent; + var id = oe.dataTransfer.getData('id'); + var pt = L.DomEvent.getMousePosition(oe, map.getContainer()); // co-ordinates of the mouse pointer at present + pt.x += Number(oe.dataTransfer.getData('offsetX')); + pt.y += Number(oe.dataTransfer.getData('offsetY')); + var ll = map.containerPointToLatLng(pt); + endpoints[id === 'marker_from' ? 0 : 1].setLatLng(ll); + getRoute(); + }); + + var params = querystring.parse(location.search.substring(1)), + route = (params.route || '').split(';'); + + if (params.engine) { + setEngine(params.engine); + } + + var o = route[0] && L.latLng(route[0].split(',')), + d = route[1] && L.latLng(route[1].split(',')); + + if (o) endpoints[0].setLatLng(o); + if (d) endpoints[1].setLatLng(d); + + map.setSidebarOverlaid(!o || !d); + + getRoute(); + }; + + page.load = function() { + page.pushstate(); + }; + + page.unload = function() { + $(".search_form").show(); + $(".directions_form").hide(); + $("#map").off('dragend dragover drop'); + + map + .removeLayer(popup) + .removeLayer(polyline) + .removeLayer(endpoints[0].marker) + .removeLayer(endpoints[1].marker); + }; + + return page; +}; + +OSM.Directions.engines = []; + +OSM.Directions.addEngine = function (engine, supportsHTTPS) { + if (document.location.protocol == "http:" || supportsHTTPS) { + OSM.Directions.engines.push(engine); + } +}; diff --git a/app/assets/javascripts/index/directions_engines/graphhopper.js b/app/assets/javascripts/index/directions_engines/graphhopper.js new file mode 100644 index 000000000..11d70efc5 --- /dev/null +++ b/app/assets/javascripts/index/directions_engines/graphhopper.js @@ -0,0 +1,74 @@ +function GraphHopperEngine(id, vehicleParam) { + var GH_INSTR_MAP = { + "-3": 6, // sharp left + "-2": 7, // left + "-1": 8, // slight left + 0: 0, // straight + 1: 1, // slight right + 2: 2, // right + 3: 3, // sharp right + 4: -1, // finish reached + 5: -1 // via reached + }; + + return { + id: id, + creditline: 'Graphhopper', + draggable: false, + + getRoute: function (points, callback) { + // documentation + // https://github.com/graphhopper/graphhopper/blob/master/docs/web/api-doc.md + var url = "http://graphhopper.com/api/1/route?" + + vehicleParam + + "&locale=" + I18n.currentLocale() + + "&key=LijBPDQGfu7Iiq80w3HzwB4RUDJbMbhs6BU0dEnn" + + "&type=jsonp" + + "&instructions=true"; + + for (var i = 0; i < points.length; i++) { + url += "&point=" + points[i].lat + ',' + points[i].lng; + } + + $.ajax({ + url: url, + dataType: 'jsonp', + success: function (data) { + if (!data.paths || data.paths.length == 0) + return callback(true); + + var path = data.paths[0]; + var line = L.PolylineUtil.decode(path.points); + + var steps = []; + var len = path.instructions.length; + for (var i = 0; i < len; i++) { + var instr = path.instructions[i]; + var instrCode = (i === len - 1) ? 15 : GH_INSTR_MAP[instr.sign]; + var instrText = "" + (i + 1) + ". "; + instrText += instr.text; + var latLng = line[instr.interval[0]]; + var distInMeter = instr.distance; + steps.push([ + {lat: latLng.lat, lng: latLng.lng}, + instrCode, + instrText, + distInMeter, + [] + ]); // TODO does graphhopper map instructions onto line indices? + } + + callback(null, { + line: line, + steps: steps, + distance: path.distance, + time: path.time / 1000 + }); + } + }); + } + }; +} + +OSM.Directions.addEngine(GraphHopperEngine("graphhopper_bicycle", "vehicle=bike"), false); +OSM.Directions.addEngine(GraphHopperEngine("graphhopper_foot", "vehicle=foot"), false); diff --git a/app/assets/javascripts/index/directions_engines/mapquest.js b/app/assets/javascripts/index/directions_engines/mapquest.js new file mode 100644 index 000000000..935a1ca4c --- /dev/null +++ b/app/assets/javascripts/index/directions_engines/mapquest.js @@ -0,0 +1,94 @@ +// For docs, see: +// http://developer.mapquest.com/web/products/open/directions-service +// http://open.mapquestapi.com/directions/ +// https://github.com/apmon/openstreetmap-website/blob/21edc353a4558006f0ce23f5ec3930be6a7d4c8b/app/controllers/routing_controller.rb#L153 + +function MapQuestEngine(id, vehicleParam) { + var MQ_SPRITE_MAP = { + 0: 1, // straight + 1: 2, // slight right + 2: 3, // right + 3: 4, // sharp right + 4: 5, // reverse + 5: 6, // sharp left + 6: 7, // left + 7: 8, // slight left + 8: 5, // right U-turn + 9: 5, // left U-turn + 10: 2, // right merge + 11: 8, // left merge + 12: 2, // right on-ramp + 13: 8, // left on-ramp + 14: 2, // right off-ramp + 15: 8, // left off-ramp + 16: 2, // right fork + 17: 8, // left fork + 18: 1 // straight fork + }; + + return { + id: id, + creditline: 'MapQuest ', + draggable: false, + + getRoute: function (points, callback) { + var url = document.location.protocol + "//open.mapquestapi.com/directions/v2/route?key=Fmjtd%7Cluur290anu%2Crl%3Do5-908a0y"; + var from = points[0]; + var to = points[points.length - 1]; + url += "&from=" + from.lat + ',' + from.lng; + url += "&to=" + to.lat + ',' + to.lng; + url += "&" + vehicleParam; + //url+="&locale=" + I18n.currentLocale(); //Doesn't actually work. MapQuest requires full locale e.g. "de_DE", but I18n may only provides language, e.g. "de" + url += "&manMaps=false"; + url += "&shapeFormat=raw&generalize=0&unit=k"; + + $.ajax({ + url: url, + success: function (data) { + if (data.info.statuscode != 0) + return callback(true); + + var line = []; + var shape = data.route.shape.shapePoints; + for (var i = 0; i < shape.length; i += 2) { + line.push(L.latLng(shape[i], shape[i + 1])); + } + + // data.route.shape.maneuverIndexes links turns to polyline positions + // data.route.legs[0].maneuvers is list of turns + var steps = []; + var mq = data.route.legs[0].maneuvers; + for (var i = 0; i < mq.length; i++) { + var s = mq[i]; + var d; + var linesegstart, linesegend, lineseg; + linesegstart = data.route.shape.maneuverIndexes[i]; + if (i == mq.length - 1) { + d = 15; + linesegend = linesegstart + 1; + } else { + d = MQ_SPRITE_MAP[s.turnType]; + linesegend = data.route.shape.maneuverIndexes[i + 1] + 1; + } + lineseg = []; + for (var j = linesegstart; j < linesegend; j++) { + lineseg.push(L.latLng(data.route.shape.shapePoints[j * 2], data.route.shape.shapePoints[j * 2 + 1])); + } + steps.push([L.latLng(s.startPoint.lat, s.startPoint.lng), d, s.narrative, s.distance * 1000, lineseg]); + } + + callback(null, { + line: line, + steps: steps, + distance: data.route.distance * 1000, + time: data.route['time'] + }); + } + }); + } + }; +} + +OSM.Directions.addEngine(MapQuestEngine("mapquest_bicycle", "routeType=bicycle"), true); +OSM.Directions.addEngine(MapQuestEngine("mapquest_foot", "routeType=pedestrian"), true); +OSM.Directions.addEngine(MapQuestEngine("mapquest_car", "routeType=fastest"), true); diff --git a/app/assets/javascripts/index/directions_engines/osrm.js b/app/assets/javascripts/index/directions_engines/osrm.js new file mode 100644 index 000000000..69b78c25e --- /dev/null +++ b/app/assets/javascripts/index/directions_engines/osrm.js @@ -0,0 +1,96 @@ +// OSRM car engine +// Doesn't yet support hints + +function OSRMEngine() { + var previousPoints, hintData; + + return { + id: "osrm_car", + creditline: 'OSRM', + draggable: true, + + getRoute: function (points, callback) { + var TURN_INSTRUCTIONS = [ + "", + I18n.t('javascripts.directions.instructions.continue_on'), // 1 + I18n.t('javascripts.directions.instructions.slight_right'), // 2 + I18n.t('javascripts.directions.instructions.turn_right'), // 3 + I18n.t('javascripts.directions.instructions.sharp_right'), // 4 + I18n.t('javascripts.directions.instructions.uturn'), // 5 + I18n.t('javascripts.directions.instructions.sharp_left'), // 6 + I18n.t('javascripts.directions.instructions.turn_left'), // 7 + I18n.t('javascripts.directions.instructions.slight_left'), // 8 + I18n.t('javascripts.directions.instructions.via_point'), // 9 + I18n.t('javascripts.directions.instructions.follow'), // 10 + I18n.t('javascripts.directions.instructions.roundabout'), // 11 + I18n.t('javascripts.directions.instructions.leave_roundabout'), // 12 + I18n.t('javascripts.directions.instructions.stay_roundabout'), // 13 + I18n.t('javascripts.directions.instructions.start'), // 14 + I18n.t('javascripts.directions.instructions.destination'), // 15 + I18n.t('javascripts.directions.instructions.against_oneway'), // 16 + I18n.t('javascripts.directions.instructions.end_oneway') // 17 + ]; + + var url = "http://router.project-osrm.org/viaroute?z=14&output=json&instructions=true"; + + for (var i = 0; i < points.length; i++) { + url += "&loc=" + points[i].lat + ',' + points[i].lng; + if (hintData && previousPoints && previousPoints[i].equals(points[i])) { + url += "&hint=" + hintData.locations[i]; + } + } + + if (hintData && hintData.checksum) { + url += "&checksum=" + hintData.checksum; + } + + $.ajax({ + url: url, + dataType: 'json', + success: function (data) { + if (data.status == 207) + return callback(true); + + previousPoints = points; + hintData = data.hint_data; + + var line = L.PolylineUtil.decode(data.route_geometry); + for (var i = 0; i < line.length; i++) { + line[i].lat /= 10; + line[i].lng /= 10; + } + + var steps = []; + for (i = 0; i < data.route_instructions.length; i++) { + var s = data.route_instructions[i]; + var linesegend; + var instCodes = s[0].split('-'); + var instText = "" + (i + 1) + ". "; + instText += TURN_INSTRUCTIONS[instCodes[0]]; + if (instCodes[1]) { + instText += "exit " + instCodes[1] + " "; + } + if (instCodes[0] != 15) { + instText += s[1] ? "" + s[1] + "" : I18n.t('javascripts.directions.instructions.unnamed'); + } + if ((i + 1) < data.route_instructions.length) { + linesegend = data.route_instructions[i + 1][3] + 1; + } else { + linesegend = s[3] + 1; + } + steps.push([line[s[3]], s[0].split('-')[0], instText, s[2], line.slice(s[3], linesegend)]); + } + + callback(null, { + line: line, + steps: steps, + distance: data.route_summary.total_distance, + time: data.route_summary.total_time + }); + } + }); + } + }; +} + +OSM.Directions.addEngine(OSRMEngine(), false); diff --git a/app/assets/javascripts/index/search.js b/app/assets/javascripts/index/search.js index 81b96635b..7fb8edbb0 100644 --- a/app/assets/javascripts/index/search.js +++ b/app/assets/javascripts/index/search.js @@ -1,12 +1,30 @@ OSM.Search = function(map) { - $(".search_form input[name=query]") - .on("input", function(e) { - if ($(e.target).val() == "") { - $(".describe_location").fadeIn(100); - } else { - $(".describe_location").fadeOut(100); - } - }) + $(".search_form input[name=query]").on("input", function(e) { + if ($(e.target).val() == "") { + $(".describe_location").fadeIn(100); + } else { + $(".describe_location").fadeOut(100); + } + }); + + $(".search_form").on("submit", function(e) { + e.preventDefault(); + $("header").addClass("closed"); + var query = $(this).find("input[name=query]").val(); + if (query) { + OSM.router.route("/search?query=" + encodeURIComponent(query) + OSM.formatHash(map)); + } else { + OSM.router.route("/" + OSM.formatHash(map)); + } + }); + + $(".describe_location").on("click", function(e) { + e.preventDefault(); + var precision = OSM.zoomPrecision(map.getZoom()); + OSM.router.route("/search?query=" + encodeURIComponent( + map.getCenter().lat.toFixed(precision) + "," + + map.getCenter().lng.toFixed(precision))); + }); $("#sidebar_content") .on("click", ".search_more a", clickSearchMore) diff --git a/app/assets/javascripts/leaflet.map.js.erb b/app/assets/javascripts/leaflet.map.js.erb index 35370ae99..6f7d7754f 100644 --- a/app/assets/javascripts/leaflet.map.js.erb +++ b/app/assets/javascripts/leaflet.map.js.erb @@ -242,6 +242,19 @@ L.OSM.Map = L.Map.extend({ setState: function(state, options) { if (state.center) this.setView(state.center, state.zoom, options); this.updateLayers(state.layers); + }, + + setSidebarOverlaid: function(overlaid) { + if (overlaid && !$("#content").hasClass("overlay-sidebar")) { + $("#content").addClass("overlay-sidebar"); + this.invalidateSize({pan: false}) + .panBy([-350, 0], {animate: false}); + } else if (!overlaid && $("#content").hasClass("overlay-sidebar")) { + this.panBy([350, 0], {animate: false}); + $("#content").removeClass("overlay-sidebar"); + this.invalidateSize({pan: false}); + } + return this; } }); diff --git a/app/assets/javascripts/router.js b/app/assets/javascripts/router.js index 05e47c356..a05ed5a9d 100644 --- a/app/assets/javascripts/router.js +++ b/app/assets/javascripts/router.js @@ -121,6 +121,10 @@ OSM.Router = function(map, rts) { return true; }; + router.replace = function (url) { + window.history.replaceState(OSM.parseHash(url), document.title, url); + }; + router.stateChange = function(state) { if (state.center) { window.history.replaceState(state, document.title, OSM.formatHash(state)); @@ -129,7 +133,7 @@ OSM.Router = function(map, rts) { } }; } else { - router.route = function (url) { + router.route = router.replace = function (url) { window.location.assign(url); }; @@ -174,9 +178,6 @@ OSM.Router = function(map, rts) { map.on('moveend baselayerchange overlaylayerchange', router.updateHash); $(window).on('hashchange', router.hashUpdated); - $(window).on('unload', function(e) { - $(".query_wrapper.routing input").val(""); - }); return router; }; diff --git a/app/assets/javascripts/routing.js.erb b/app/assets/javascripts/routing.js.erb deleted file mode 100644 index 84bf17832..000000000 --- a/app/assets/javascripts/routing.js.erb +++ /dev/null @@ -1,307 +0,0 @@ -/* - osm.org routing interface -*/ - -var TURN_INSTRUCTIONS=[] - -var ROUTING_POLYLINE={ - color: '#03f', - opacity: 0.3, - weight: 10 -}; - -var ROUTING_POLYLINE_HIGHLIGHT={ - color: '#ff0', - opacity: 0.5, - weight: 12 -}; - - -OSM.RoutingEngines={ - list: [], - add: function(supportsHTTPS,engine) { - if (document.location.protocol=="http:" || supportsHTTPS) this.list.push(engine); - } -}; - -OSM.Routing=function(map,name,jqSearch) { - var r={}; - - TURN_INSTRUCTIONS=["", - I18n.t('javascripts.directions.instructions.continue_on'), // 1 - I18n.t('javascripts.directions.instructions.slight_right'), // 2 - I18n.t('javascripts.directions.instructions.turn_right'), // 3 - I18n.t('javascripts.directions.instructions.sharp_right'), // 4 - I18n.t('javascripts.directions.instructions.uturn'), // 5 - I18n.t('javascripts.directions.instructions.sharp_left'), // 6 - I18n.t('javascripts.directions.instructions.turn_left'), // 7 - I18n.t('javascripts.directions.instructions.slight_left'), // 8 - I18n.t('javascripts.directions.instructions.via_point'), // 9 - I18n.t('javascripts.directions.instructions.follow'), // 10 - I18n.t('javascripts.directions.instructions.roundabout'), // 11 - I18n.t('javascripts.directions.instructions.leave_roundabout'), // 12 - I18n.t('javascripts.directions.instructions.stay_roundabout'), // 13 - I18n.t('javascripts.directions.instructions.start'), // 14 - I18n.t('javascripts.directions.instructions.destination'), // 15 - I18n.t('javascripts.directions.instructions.against_oneway'), // 16 - I18n.t('javascripts.directions.instructions.end_oneway')] // 17 - - r.map=map; // Leaflet map - r.name=name; // global variable name of this instance (needed for JSONP) - r.jqSearch=jqSearch; // JQuery object for search panel - - r.route_from=null; // null=unset, false=awaiting response, [lat,lon]=geocoded - r.route_to=null; // | - r.awaitingGeocode=false;// true if the user has requested a route, but we're waiting on a geocode result - r.awaitingRoute=false; // true if we've asked the engine for a route and are waiting to hear back - r.dragging=false; // true if the user is dragging a start/end point - r.viaPoints=[]; // not yet used - - r.polyline=null; // Leaflet polyline object - r.popup=null; // Leaflet popup object - r.marker_from=null; // Leaflet from marker - r.marker_to=null; // Leaflet to marker - - r.chosenEngine=null; // currently selected routing engine - - var icon_from = L.icon({ - iconUrl: <%= asset_path('marker-green.png').to_json %>, - iconSize: [25, 41], - iconAnchor: [12, 41], - popupAnchor: [1, -34], - shadowUrl: <%= asset_path('images/marker-shadow.png').to_json %>, - shadowSize: [41, 41] - }); - var icon_to = L.icon({ - iconUrl: <%= asset_path('marker-red.png').to_json %>, - iconSize: [25, 41], - iconAnchor: [12, 41], - popupAnchor: [1, -34], - shadowUrl: <%= asset_path('images/marker-shadow.png').to_json %>, - shadowSize: [41, 41] - }); - - // Geocoding - - r.geocode=function(id,event) { var _this=this; - var field=event.target; - var v=event.target.value; - var querystring = '<%= NOMINATIM_URL %>search?q=' + encodeURIComponent(v) + '&format=json'; - // *** &accept-language=<%#= request.user_preferred_languages.join(',') %> - r[field.id]=false; - $.getJSON(querystring, function(json) { _this._gotGeocode(json,field); }); - }; - - r._gotGeocode=function(json,field) { - if (json.length==0) { - alert(I18n.t('javascripts.directions.errors.no_place')); - r[field.id]=null; - return; - } - field.value=json[0].display_name; - var lat=Number(json[0].lat), lon=Number(json[0].lon); - r[field.id]=[lat,lon]; - r.updateMarker(field.id); - if (r.awaitingGeocode) { - r.awaitingGeocode=false; - r.requestRoute(true, true); - } - }; - - // Drag and drop markers - - r.handleDrop=function(e) { - var oe=e.originalEvent; - var id=oe.dataTransfer.getData('id'); - var pt=L.DomEvent.getMousePosition(oe,map.getContainer()); // co-ordinates of the mouse pointer at present - pt.x+=Number(oe.dataTransfer.getData('offsetX')); - pt.y+=Number(oe.dataTransfer.getData('offsetY')); - var ll=map.containerPointToLatLng(pt); - r.createMarker(ll,id); - r.setNumericInput(ll,id); - r.requestRoute(true, false); - // update to/from field - }; - r.createMarker=function(latlng,id) { - if (r[id]) r.map.removeLayer(r[id]); - r[id]=L.marker(latlng, { - icon: id=='marker_from' ? icon_from : icon_to, - draggable: true, - name: id - }).addTo(r.map); - r[id].on('drag',r.markerDragged); - r[id].on('dragend',r.markerDragged); - }; - // Update marker from geocoded route input - r.updateMarker=function(id) { - var m=id.replace('route','marker'); - if (!r[m]) { r.createMarker(r[id],m); return; } - var ll=r[m].getLatLng(); - if (ll.lat!=r[id][0] || ll.lng!=r[id][1]) { - r.createMarker(r[id],m); - } - }; - // Marker has been dragged - r.markerDragged=function(e) { - r.dragging=(e.type=='drag'); // true for drag, false for dragend - if (r.dragging && !r.chosenEngine.draggable) return; - if (r.dragging && r.awaitingRoute) return; - r.setNumericInput(e.target.getLatLng(), e.target.options.name); - r.requestRoute(!r.dragging, false); - }; - // Set a route input field to a numeric value - r.setNumericInput=function(ll,id) { - var routeid=id.replace('marker','route'); - r[routeid]=[ll.lat,ll.lng]; - $("[name="+routeid+"]:visible").val(Math.round(ll.lat*10000)/10000+" "+Math.round(ll.lng*10000)/10000); - } - - // Route-fetching UI - - r.requestRoute=function(isFinal, updateZoom) { - if (r.route_from && r.route_to) { - $(".query_wrapper.routing .spinner").show(); - r.awaitingRoute=true; - r.chosenEngine.getRoute(isFinal,[r.route_from,r.route_to]); - if(updateZoom){ - r.map.fitBounds(L.latLngBounds([r.route_from, r.route_to]).pad(0.05)); - } - // then, when the route has been fetched, it'll call the engine's gotRoute function - } else if (r.route_from==false || r.route_to==false) { - // we're waiting for a Nominatim response before we can request a route - r.awaitingGeocode=true; - } - }; - - // Take an array of Leaflet LatLngs and draw it as a polyline - r.setPolyline=function(line) { - if (r.polyline) map.removeLayer(r.polyline); - r.polyline=L.polyline(line, ROUTING_POLYLINE).addTo(r.map); - }; - - // Take directions and write them out - // data = { steps: array of [latlng, sprite number, instruction text, distance in metres, highlightPolyline] } - // sprite numbers equate to OSRM's route_instructions turn values - r.setItinerary=function(data) { - // Create base table - $("#content").removeClass("overlay-sidebar"); - $('#sidebar_content').empty(); - var html=('

' + - '' + I18n.t('javascripts.directions.directions') + - '

' + - I18n.t('javascripts.directions.distance') + ': ' + r.formatDistance(data.distance)+ '. ' + - I18n.t('javascripts.directions.time' ) + ': ' + r.formatTime(data.time) + '.

' + - '').replace(/~/g,"'"); - $('#sidebar_content').html(html); - // Add each row - var cumulative=0; - for (var i=0; i"); - row.append("
"); - row.append(""+step[2]); - row.append(""+dist); - with ({ instruction: step[2], ll: step[0], lineseg: step[4] }) { - row.on('click',function(e) { r.clickTurn(instruction, ll); }); - row.hover(function(e){r.highlightSegment(lineseg);}, function(e){r.unhighlightSegment();}); - }; - $('#turnbyturn').append(row); - cumulative+=step[3]; - } - $('#sidebar_content').append('

' + r.chosenEngine.creditline + '

'); - - }; - r.clickTurn=function(instruction,latlng) { - r.popup=L.popup().setLatLng(latlng).setContent("

"+instruction+"

").openOn(r.map); - }; - r.highlightSegment=function(lineseg){ - if (r.highlighted) map.removeLayer(r.highlighted); - r.highlighted=L.polyline(lineseg, ROUTING_POLYLINE_HIGHLIGHT).addTo(r.map); - } - r.unhighlightSegment=function(){ - if (r.highlighted) map.removeLayer(r.highlighted); - } - r.formatDistance=function(m) { - if (m < 1000 ) { return Math.round(m) + "m"; } - else if (m < 10000) { return (m/1000.0).toFixed(1) + "km"; } - else { return Math.round(m / 1000) + "km"; } - }; - r.formatTime=function(s) { - var d=new Date(s*1000); var h=d.getHours(); var m=d.getMinutes(); - return h+":"+(m<10 ? '0' : '')+m; - }; - - // Close all routing UI - - r.close=function() { - $("#content").addClass("overlay-sidebar"); - r.route_from=r.route_to=null; - $(".query_wrapper.routing input").val(""); - var remove=['polyline','popup','marker_from','marker_to']; - for (var i=0; iI18n.t(b.name); }); - var select=r.jqSearch.find('select.routing_engines'); - for (var i=0; i"+I18n.t(list[i].name)+""); - } - r.engines=list; - r.chosenEngine=list[0]; // default to first engine - - // Choose an engine on dropdown change - r.selectEngine=function(e) { - r.chosenEngine=r.engines[e.target.selectedIndex]; - if (r.polyline){ // and if a route is currently showing, must also refresh, else confusion - r.requestRoute(true, false); - } - }; - // Choose an engine by name - r.chooseEngine=function(name) { - for (var i=0; i "; - instrText += instr.descriptions[i]; - var latlng = instr.latLngs[i]; - var distInMeter = instr.distances[i]; - steps.push([{lat: latlng[0], lng: latlng[1]}, instrCode, instrText, distInMeter, []]); // TODO does graphhopper map instructions onto line indices? - } - router.setItinerary({ steps: steps, distance: data.route.distance, time: data.route['time']/1000 }); - return true; - }, - GH_INSTR_MAP: { - "-3": 6, // sharp left - "-2": 7, // left - "-1": 8, // slight left - 0: 0, // straight - 1: 1, // slight right - 2: 2, // right - 3: 3 // sharp right - } - }; -}; - -OSM.RoutingEngines.add(false, new GraphHopperEngine("Bicycle", "vehicle=bike").createConfig()); -OSM.RoutingEngines.add(false, new GraphHopperEngine("Foot", "vehicle=foot").createConfig()); diff --git a/app/assets/javascripts/routing_engines/mapquest.js b/app/assets/javascripts/routing_engines/mapquest.js deleted file mode 100644 index dc854ccdc..000000000 --- a/app/assets/javascripts/routing_engines/mapquest.js +++ /dev/null @@ -1,94 +0,0 @@ -// For docs, see: -// http://developer.mapquest.com/web/products/open/directions-service -// http://open.mapquestapi.com/directions/ -// https://github.com/apmon/openstreetmap-website/blob/21edc353a4558006f0ce23f5ec3930be6a7d4c8b/app/controllers/routing_controller.rb#L153 - -MapQuestEngine = function(vehicleName, vehicleParam, locale) { - this.vehicleName = vehicleName; - this.vehicleParam = vehicleParam; - this.locale = locale; - if (!locale) - this.locale = "en"; -}; - -MapQuestEngine.prototype.createConfig = function() { - var that = this; - return { - name: "javascripts.directions.engines.mapquest_"+this.vehicleName.toLowerCase(), - creditline: 'Directions courtesy of MapQuest ', - draggable: false, - _hints: {}, - MQ_SPRITE_MAP: { - 0: 1, // straight - 1: 2, // slight right - 2: 3, // right - 3: 4, // sharp right - 4: 5, // reverse - 5: 6, // sharp left - 6: 7, // left - 7: 8, // slight left - 8: 5, // right U-turn - 9: 5, // left U-turn - 10: 2, // right merge - 11: 8, // left merge - 12: 2, // right on-ramp - 13: 8, // left on-ramp - 14: 2, // right off-ramp - 15: 8, // left off-ramp - 16: 2, // right fork - 17: 8, // left fork - 18: 1 // straight fork - }, - getRoute: function(isFinal,points) { - var url=document.location.protocol+"//open.mapquestapi.com/directions/v2/route?key=Fmjtd%7Cluur290anu%2Crl%3Do5-908a0y"; - var from=points[0]; var to=points[points.length-1]; - url+="&from="+from.join(','); - url+="&to="+to.join(','); - url+="&"+that.vehicleParam; - //url+="&locale=" + I18n.currentLocale(); //Doesn't actually work. MapQuest requires full locale e.g. "de_DE", but I18n only provides language, e.g. "de" - url+="&manMaps=false"; - url+="&shapeFormat=raw&generalize=0&unit=k"; - this.requestCORS(url); - }, - gotRoute: function(router,data) { - if (data.info.statuscode!=0) return false; - - var poly=[]; - var shape=data.route.shape.shapePoints; - for (var i=0; iOSRM', - draggable: true, - _hints: {}, - getRoute: function(isFinal,points) { - var url=that.baseURL+"?z=14&output=json"; - for (var i=0; i "; - instText+=TURN_INSTRUCTIONS[instCodes[0]]; - if (instCodes[1]) { instText+="exit "+instCodes[1]+" "; } - if (instCodes[0]!=15) { instText+=s[1] ? ""+s[1]+"" : I18n.t('javascripts.directions.instructions.unnamed'); } - if ((i+1) [:search] + + def search + render :layout => map_layout + end +end diff --git a/app/views/directions/search.html.erb b/app/views/directions/search.html.erb new file mode 100644 index 000000000..ea6ee7088 --- /dev/null +++ b/app/views/directions/search.html.erb @@ -0,0 +1 @@ +<% content_for(:content_class) { "overlay-sidebar" } %> diff --git a/app/views/layouts/_search.html.erb b/app/views/layouts/_search.html.erb index 9ac3eae33..6e1aa5dc2 100644 --- a/app/views/layouts/_search.html.erb +++ b/app/views/layouts/_search.html.erb @@ -1,28 +1,32 @@ -
+
+ +
+ <%= link_to t('site.search.where_am_i'), '#', { :class => "describe_location", :title => t('site.search.where_am_i_title') } %> + · + <%= link_to t('site.search.get_directions'), directions_path, { :class => "geolink", :title => t('site.search.get_directions_title') } %> +
- + <%= submit_tag t('site.search.submit_text') %> -
- <%= link_to t('site.search.close_directions'), '#', { :class => "close_directions", :title => t('site.search.close_directions_title') } %> -
+
+ <%= text_field_tag "query", params[:query], :placeholder => t("site.search.search"), :autofocus => autofocus %> +
+ - <%= submit_tag t('site.search.submit_text') %> +
+
+ <%= link_to t('site.search.close_directions'), root_path, { :class => "geolink", :title => t('site.search.close_directions_title') } %> +
- + <%= submit_tag t('site.search.submit_text') %> -
- <%= image_tag "marker-green.png", :class => 'routing_marker', :id => 'marker_from', :draggable => 'true' %> - <%= text_field_tag "route_from", params[:from], :placeholder => t('site.search.from'), :onchange=>"OSM.routing.geocode('route_from',event)" %> - <%= image_tag "marker-red.png" , :class => 'routing_marker', :id => 'marker_to' , :draggable => 'true' %> - <%= text_field_tag "route_to" , params[:to] , :placeholder => t('site.search.to') , :onchange=>"OSM.routing.geocode('route_to' ,event)" %> - - <%= image_tag "searching-small.gif", :class => 'spinner', :style => "vertical-align: middle; display: none;" %> -
- -
+
+ <%= image_tag "marker-green.png", :class => 'routing_marker', :id => 'marker_from', :draggable => 'true' %> + <%= text_field_tag "route_from", params[:from], :placeholder => t('site.search.from') %> + <%= image_tag "marker-red.png" , :class => 'routing_marker', :id => 'marker_to' , :draggable => 'true' %> + <%= text_field_tag "route_to" , params[:to] , :placeholder => t('site.search.to') %> + + <%= image_tag "searching-small.gif", :class => 'spinner', :style => "vertical-align: middle; display: none;" %> +
+ +
diff --git a/config/locales/de.yml b/config/locales/de.yml index d2d9e9349..798105ee0 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -944,10 +944,17 @@ de: directions: directions: "Fahranweisungen: " engines: - graphhopper_bike: "Fahrrad (GraphHopper)" - mapquest_bike: "Fahrrad (MapQuest)" + graphhopper_bicycle: "Fahrrad (GraphHopper)" + graphhopper_foot: "Fuss (GraphHopper)" + mapquest_bicycle: "Fahrrad (MapQuest)" + mapquest_foot: "Fuss (MapQuest)" + mapquest_car: "Auto (MapQuest)" osrm_car: "Auto (OSRM)" - cloudmade_foot: "Fuss (Cloudmade)" + distance: "Distanz:" + time: "Zeit:" + errors: + no_route: "Wir konnten keine Strecke zwischen diesen beiden Orten berechnen." + no_place: "Wir konnten den Ort nicht finden." instructions: continue_on: "Weiter auf " slight_right: "Rechts halten auf " @@ -967,6 +974,7 @@ de: against_oneway: "Go against one-way on " end_oneway: "Ende der Einbahnstrasse " unnamed: "(unbekannt)" + courtesy: "Fahranweisungen stammen von %{link}" key: title: Legende tooltip: Legende diff --git a/config/locales/en.yml b/config/locales/en.yml index 4384fda86..7787fde86 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -2151,6 +2151,7 @@ en: against_oneway: "Go against one-way on " end_oneway: "End of one-way on " unnamed: "(unnamed)" + courtesy: "Directions courtesy of %{link}" time: "Time" redaction: edit: diff --git a/config/routes.rb b/config/routes.rb index e9f593d92..3b00cbf6a 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -239,6 +239,9 @@ OpenStreetMap::Application.routes.draw do match '/geocoder/search_osm_nominatim_reverse' => 'geocoder#search_osm_nominatim_reverse', :via => :get match '/geocoder/search_geonames_reverse' => 'geocoder#search_geonames_reverse', :via => :get + # directions + match '/directions' => 'directions#search', :via => :get, :as => :directions + # export match '/export/finish' => 'export#finish', :via => :post match '/export/embed' => 'export#embed', :via => :get