From: Tom Hughes Date: Sat, 8 Mar 2014 17:44:22 +0000 (+0000) Subject: Merge remote-tracking branch 'origin/master' into routing X-Git-Tag: live~4781^2~23^2 X-Git-Url: https://git.openstreetmap.org./rails.git/commitdiff_plain/b095b59d5e0ee805e49980af80b21bd1acff1345?hp=8c82be0537e1bbdcbd7f20d5f26deaae09774281 Merge remote-tracking branch 'origin/master' into routing Conflicts: app/views/layouts/_search.html.erb --- diff --git a/app/assets/images/routing-sprite.png b/app/assets/images/routing-sprite.png new file mode 100644 index 000000000..37d94886d Binary files /dev/null and b/app/assets/images/routing-sprite.png differ diff --git a/app/assets/images/searching-small.gif b/app/assets/images/searching-small.gif new file mode 100644 index 000000000..06dbc2bc2 Binary files /dev/null and b/app/assets/images/searching-small.gif differ diff --git a/app/assets/javascripts/index.js b/app/assets/javascripts/index.js index dc3c93277..e5f6fe478 100644 --- a/app/assets/javascripts/index.js +++ b/app/assets/javascripts/index.js @@ -5,6 +5,7 @@ //= require leaflet.key //= require leaflet.note //= require leaflet.share +//= require leaflet.polyline //= require index/search //= require index/browse //= require index/export @@ -13,6 +14,8 @@ //= require index/note //= require index/new_note //= require router +//= require routing +//= require_tree ./routing_engines (function() { var loaderTimeout; @@ -322,12 +325,18 @@ $(document).ready(function () { $(".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)); + if ($(".query_wrapper.routing").is(":visible")) { + // Routing + OSM.routing.requestRoute(true, true); } else { - OSM.router.route("/" + OSM.formatHash(map)); + // 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)); + } } }); @@ -338,4 +347,33 @@ $(document).ready(function () { 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(); + $(".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); + e.originalEvent.dataTransfer.setData('offsetX', e.originalEvent.target.width/2 - (e.originalEvent.x-e.target.x)); + e.originalEvent.dataTransfer.setData('offsetY', e.originalEvent.target.height - (e.originalEvent.y-e.target.y)); + }); + }); + + $(".close_directions").on("click",function(e) { + e.preventDefault(); + $(".search").show(); + $(".routing").hide(); + OSM.routing.close(); + $("#map").off('dragend drop dragover'); + $(".routing_marker").off('dragstart'); + $(".query_wrapper.search [name=query]").focus(); + }); + }); diff --git a/app/assets/javascripts/routing.js.erb b/app/assets/javascripts/routing.js.erb new file mode 100644 index 000000000..d62e94f0c --- /dev/null +++ b/app/assets/javascripts/routing.js.erb @@ -0,0 +1,290 @@ +/* + osm.org routing interface +*/ + +var TURN_INSTRUCTIONS=[] + +var ROUTING_POLYLINE={ + color: '#03f', + opacity: 0.3, + weight: 10 +}; + + +OSM.RoutingEngines={ + list: [] +}; + +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] } + // 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 ({ num: i, ll: step[0] }) { + row.on('click',function(e) { r.clickTurn(num, ll); }); + }; + $('#turnbyturn').append(row); + cumulative+=step[3]; + } + $('#sidebar_content').append('

' + r.chosenEngine.creditline + '

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

"+(num+1)+"

").openOn(r.map); + }; + 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]); + } + 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.list.push(new GraphHopperEngine("Bicycle", "vehicle=bike").createConfig()); +OSM.RoutingEngines.list.push(new GraphHopperEngine("Foot", "vehicle=foot").createConfig()); diff --git a/app/assets/javascripts/routing_engines/mapquest.js b/app/assets/javascripts/routing_engines/mapquest.js new file mode 100644 index 000000000..eb92893b3 --- /dev/null +++ b/app/assets/javascripts/routing_engines/mapquest.js @@ -0,0 +1,81 @@ +// 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="http://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'); } + steps.push([line[s[3]], s[0].split('-')[0], instText, s[2]]); + } + if (steps.length) router.setItinerary({ steps: steps, distance: data.route_summary.total_distance, time: data.route_summary.total_time }); + return true; + } + }; +}; + +OSM.RoutingEngines.list.push(new OSRMEngine("Car", "http://router.project-osrm.org/viaroute").createConfig()); diff --git a/app/assets/stylesheets/common.css.scss b/app/assets/stylesheets/common.css.scss index 533e91c69..51f3c7e93 100644 --- a/app/assets/stylesheets/common.css.scss +++ b/app/assets/stylesheets/common.css.scss @@ -635,6 +635,10 @@ nav.secondary { font-size: 13px; } + p { + padding: 0 $lineheight $lineheight/4; + } + .icon.close { float: right; cursor: pointer; @@ -912,6 +916,7 @@ header .search_form { .search_form { position: relative; padding: $lineheight/2; + padding-top: 1px; background-color: $lightgrey; .query_wrapper { @@ -942,13 +947,15 @@ header .search_form { border-radius: 0 2px 2px 0; } - .describe_location { - position: absolute; - top: 6px; - right: 6px; + .query_options { + text-align: right; font-size: 10px; color: $blue; } + + .routing { + display: none; + } } /* Rules for the map key which appears in the popout sidebar */ @@ -985,6 +992,41 @@ header .search_form { color: #f00; } +/* Rules for routing */ + +#sidebar_content>table { + padding: 5px 20px 10px 15px; + width: 100%; + border-collapse: separate; +} + +td.direction { + background-image: image-url('routing-sprite.png'); + width: 20px; height: 20px; + background-repeat: no-repeat; +} +@for $i from 1 through 17 { +td.direction.i#{$i} { background-position: #{($i)*-20+20}px 0px; } +} + +td.instruction, td.distance { + padding-top: 0; + border-bottom: 1px solid #DDD; +} +td.distance { + color: #BBB; + text-align: right; + font-size: x-small; +} +tr.turn { + cursor: pointer; +} +tr.turn:hover { + background: lighten($green, 30%); +} +.routing_engines, #route_from, #route_to { margin-left: 25px; } +.routing_marker { width: 15px; position: absolute; } + /* Rules for entity history */ #sidebar_content { @@ -1175,6 +1217,15 @@ header .search_form { } } +/* Rules for the routing sidebar */ + +#sidebar_content { + #routing_credit { + text-align: center; + padding: 0.5em; + } +} + /* Rules for edit pages */ .site-edit { diff --git a/app/views/layouts/_search.html.erb b/app/views/layouts/_search.html.erb index ed548e21c..9ac3eae33 100644 --- a/app/views/layouts/_search.html.erb +++ b/app/views/layouts/_search.html.erb @@ -1,7 +1,28 @@
+ + + +
+ <%= link_to t('site.search.close_directions'), '#', { :class => "close_directions", :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;" %> +
+ diff --git a/config/environments/production.rb b/config/environments/production.rb index c4910d86f..36f71792f 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -69,7 +69,7 @@ OpenStreetMap::Application.configure do # Precompile additional assets. # application.js, application.css, and all non-JS/CSS in app/assets folder are already added. - config.assets.precompile += %w( index.js browse.js welcome.js fixthemap.js ) + config.assets.precompile += %w( index.js browse.js welcome.js fixthemap.js routing.js ) config.assets.precompile += %w( user.js diary_entry.js swfobject.js ) config.assets.precompile += %w( large-ltr.css small-ltr.css print-ltr.css ) config.assets.precompile += %w( large-rtl.css small-rtl.css print-rtl.css ) diff --git a/config/locales/de.yml b/config/locales/de.yml index 2f79730d2..d2d9e9349 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -941,6 +941,32 @@ de: javascripts: close: Schließen edit_help: Wähle eine höhere Zoomstufe und verschiebe die Karte an einen Ort, den du bearbeiten möchtest, und klicke hier. + directions: + directions: "Fahranweisungen: " + engines: + graphhopper_bike: "Fahrrad (GraphHopper)" + mapquest_bike: "Fahrrad (MapQuest)" + osrm_car: "Auto (OSRM)" + cloudmade_foot: "Fuss (Cloudmade)" + instructions: + continue_on: "Weiter auf " + slight_right: "Rechts halten auf " + turn_right: "Rechts abbiegen auf " + sharp_right: "Hart rechts auf " + uturn: "U-turn along " + sharp_left: "Hart links auf " + turn_left: "Links abbiegen auf " + slight_left: "Links halten auf " + via_point: "(via point) " + follow: "Folge " + roundabout: "Im Kreisverkehr nehme " + leave_roundabout: "Verlasse den Kreisverkehr - " + stay_roundabout: "Stay on roundabout - " + start: "Start at end of " + destination: "Ziel erreicht" + against_oneway: "Go against one-way on " + end_oneway: "Ende der Einbahnstrasse " + unnamed: "(unbekannt)" key: title: Legende tooltip: Legende @@ -1479,6 +1505,12 @@ de: preview: Vorschau search: search: Suchen + get_directions: "Route berechnen" + get_directions_title: "Routenberechnung zwischen zwei Orten" + close_directions: "Schliessen der Route" + close_directions_title: "Schliessen des Routenmenus" + from: "Von" + to: "Nach" submit_text: Los where_am_i: Wo bin ich? where_am_i_title: Die momentane Position mit der Suchmaschine anzeigen diff --git a/config/locales/en.yml b/config/locales/en.yml index 94537c1fb..4384fda86 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1325,6 +1325,12 @@ en: close: Close search: search: Search + get_directions: "Get directions" + get_directions_title: "Find directions between two points" + close_directions: "Close directions" + close_directions_title: "Close the directions panel" + from: "From" + to: "To" where_am_i: "Where am I?" where_am_i_title: Describe the current location using the search engine submit_text: "Go" @@ -2113,6 +2119,39 @@ en: comment_and_resolve: Comment & Resolve comment: Comment edit_help: Move the map and zoom in on a location you want to edit, then click here. + directions: + engines: + graphhopper_bicycle: "Bicycle (GraphHopper)" + graphhopper_foot: "Foot (GraphHopper)" + mapquest_bicycle: "Bicycle (MapQuest)" + mapquest_car: "Car (MapQuest)" + mapquest_foot: "Foot (MapQuest)" + osrm_car: "Car (OSRM)" + directions: "Directions" + distance: "Distance" + errors: + no_route: "Couldn't find a route between those two places." + no_place: "Sorry - couldn't find that place." + instructions: + continue_on: "Continue on " + slight_right: "Slight right onto " + turn_right: "Turn right onto " + sharp_right: "Sharp right onto " + uturn: "U-turn along " + sharp_left: "Sharp left onto " + turn_left: "Turn left onto " + slight_left: "Slight left onto " + via_point: "(via point) " + follow: "Follow " + roundabout: "At roundabout take " + leave_roundabout: "Leave roundabout - " + stay_roundabout: "Stay on roundabout - " + start: "Start at end of " + destination: "Reach destination" + against_oneway: "Go against one-way on " + end_oneway: "End of one-way on " + unnamed: "(unnamed)" + time: "Time" redaction: edit: description: "Description" diff --git a/vendor/assets/leaflet/leaflet.polyline.js b/vendor/assets/leaflet/leaflet.polyline.js new file mode 100755 index 000000000..b7e85d6c5 --- /dev/null +++ b/vendor/assets/leaflet/leaflet.polyline.js @@ -0,0 +1,127 @@ +/* + * L.PolylineUtil contains utilify functions for polylines, two methods + * are added to the L.Polyline object to support creation of polylines + * from an encoded string and converting existing polylines to an + * encoded string. + * + * - L.Polyline.fromEncoded(encoded [, options]) returns a L.Polyline + * - L.Polyline.encodePath() returns a string + * + * Actual code from: + * http://facstaff.unca.edu/mcmcclur/GoogleMaps/EncodePolyline/\ + */ + +/*jshint browser:true, debug: true, strict:false, globalstrict:false, indent:4, white:true, smarttabs:true*/ +/*global L:true, console:true*/ + + +// Inject functionality into Leaflet +(function (L) { + if (!(L.Polyline.prototype.fromEncoded)) { + L.Polyline.fromEncoded = function (encoded, options) { + return new L.Polyline(L.PolylineUtil.decode(encoded), options); + }; + } + if (!(L.Polygon.prototype.fromEncoded)) { + L.Polygon.fromEncoded = function (encoded, options) { + return new L.Polygon(L.PolylineUtil.decode(encoded), options); + }; + } + + var encodeMixin = { + encodePath: function () { + return L.PolylineUtil.encode(this.getLatLngs()); + } + }; + + if (!L.Polyline.prototype.encodePath) { + L.Polyline.include(encodeMixin); + } + if (!L.Polygon.prototype.encodePath) { + L.Polygon.include(encodeMixin); + } +})(L); + +// Utility functions. +L.PolylineUtil = {}; + +L.PolylineUtil.encode = function (latlngs) { + var i, dlat, dlng; + var plat = 0; + var plng = 0; + var encoded_points = ""; + + for (i = 0; i < latlngs.length; i++) { + var lat = latlngs[i].lat; + var lng = latlngs[i].lng; + var late5 = Math.floor(lat * 1e5); + var lnge5 = Math.floor(lng * 1e5); + dlat = late5 - plat; + dlng = lnge5 - plng; + plat = late5; + plng = lnge5; + encoded_points += + L.PolylineUtil.encodeSignedNumber(dlat) + + L.PolylineUtil.encodeSignedNumber(dlng); + } + return encoded_points; +}; + +// This function is very similar to Google's, but I added +// some stuff to deal with the double slash issue. +L.PolylineUtil.encodeNumber = function (num) { + var encodeString = ""; + var nextValue, finalValue; + while (num >= 0x20) { + nextValue = (0x20 | (num & 0x1f)) + 63; + encodeString += (String.fromCharCode(nextValue)); + num >>= 5; + } + finalValue = num + 63; + encodeString += (String.fromCharCode(finalValue)); + return encodeString; +}; + +// This one is Google's verbatim. +L.PolylineUtil.encodeSignedNumber = function (num) { + var sgn_num = num << 1; + if (num < 0) { + sgn_num = ~(sgn_num); + } + return (L.PolylineUtil.encodeNumber(sgn_num)); +}; + +L.PolylineUtil.decode = function (encoded) { + var len = encoded.length; + var index = 0; + var latlngs = []; + var lat = 0; + var lng = 0; + + while (index < len) { + var b; + var shift = 0; + var result = 0; + do { + b = encoded.charCodeAt(index++) - 63; + result |= (b & 0x1f) << shift; + shift += 5; + } while (b >= 0x20); + var dlat = ((result & 1) ? ~(result >> 1) : (result >> 1)); + lat += dlat; + + shift = 0; + result = 0; + do { + b = encoded.charCodeAt(index++) - 63; + result |= (b & 0x1f) << shift; + shift += 5; + } while (b >= 0x20); + var dlng = ((result & 1) ? ~(result >> 1) : (result >> 1)); + lng += dlng; + + latlngs.push(new L.LatLng(lat * 1e-5, lng * 1e-5)); + } + + return latlngs; +}; \ No newline at end of file