diff --git a/public/css/leaflet-routing-machine.css b/public/css/leaflet-routing-machine.css index 966f811..f513955 100644 --- a/public/css/leaflet-routing-machine.css +++ b/public/css/leaflet-routing-machine.css @@ -1,7 +1,14 @@ -.leaflet-routing-container { +.leaflet-routing-container, .leaflet-routing-error { width: 320px; background-color: white; padding-top: 4px; + transition: all 0.2s ease; + box-sizing: border-box; +} + +.leaflet-control-container .leaflet-routing-container-hide { + width: 32px; + height: 32px; } .leaflet-routing-container h2 { @@ -13,7 +20,11 @@ font-weight: normal; } -.leaflet-routing-alt, .leaflet-routing-geocoders { +.leaflet-routing-collapsible .leaflet-routing-geocoders { + margin-top: 20px; +} + +.leaflet-routing-alt, .leaflet-routing-geocoders, .leaflet-routing-error { padding: 6px; margin-top: 2px; margin-bottom: 6px; @@ -23,6 +34,11 @@ transition: all 0.2s ease; } +.leaflet-control-container .leaflet-routing-container-hide .leaflet-routing-alt, +.leaflet-control-container .leaflet-routing-container-hide .leaflet-routing-geocoders { + display: none; +} + .leaflet-bar .leaflet-routing-alt:last-child { border-bottom: none; } @@ -57,9 +73,40 @@ background-color: #888; } +.leaflet-routing-icon { + background-image: url('leaflet.routing.icons.png'); + -webkit-background-size: 240px 20px; + background-size: 240px 20px; + background-repeat: no-repeat; + margin: 0; + content: ''; + display: inline-block; + vertical-align: top; + width: 20px; + height: 20px; +} + +.leaflet-routing-icon-continue { background-position: 0 0; } +.leaflet-routing-icon-sharp-right { background-position: -20px 0; } +.leaflet-routing-icon-turn-right { background-position: -40px 0; } +.leaflet-routing-icon-bear-right { background-position: -60px 0; } +.leaflet-routing-icon-u-turn { background-position: -80px 0; } +.leaflet-routing-icon-sharp-left { background-position: -100px 0; } +.leaflet-routing-icon-turn-left { background-position: -120px 0; } +.leaflet-routing-icon-bear-left { background-position: -140px 0; } +.leaflet-routing-icon-depart { background-position: -160px 0; } +.leaflet-routing-icon-enter-roundabout { background-position: -180px 0; } +.leaflet-routing-icon-arrive { background-position: -200px 0; } +.leaflet-routing-icon-via { background-position: -220px 0; } + +.leaflet-routing-geocoders div { + padding: 4px 0px 4px 0px; +} + .leaflet-routing-geocoders input { - width: 292px; - padding: 4px; + width: 303px; + width: calc(100% - 4px); + line-height: 1.67; border: 1px solid #ccc; } @@ -68,21 +115,37 @@ border: 1px solid #ccc; border-radius: 4px; background-color: white; - margin: 0 6px; + margin: 0; + margin-right: 3px; float: right; cursor: pointer; transition: background-color 0.2s ease; } +.leaflet-routing-add-waypoint:after { + content: '+'; +} + +.leaflet-routing-reverse-waypoints:after { + font-weight: normal; + content: '\21c5'; +} + .leaflet-routing-geocoders button:hover { background-color: #eee; } +.leaflet-routing-geocoders input,.leaflet-routing-remove-waypoint,.leaflet-routing-geocoder { + position: relative; +} + .leaflet-routing-geocoder-result { + font: 12px/1.5 "Helvetica Neue", Arial, Helvetica, sans-serif; position: absolute; max-height: 0; overflow: hidden; transition: all 0.5s ease; + z-index: 1000; /* Arbitrary, but try to be above "most" things. */ } .leaflet-routing-geocoder-result table { @@ -99,4 +162,97 @@ .leaflet-routing-geocoder-selected, .leaflet-routing-geocoder-result tr:hover { background-color: #eee; -} \ No newline at end of file +} + +.leaflet-routing-geocoder-no-results { + font-style: italic; + color: #888; +} + +.leaflet-routing-remove-waypoint { + background-color: transparent; + display: inline-block; + vertical-align: middle; + cursor: pointer; +} + +.leaflet-routing-remove-waypoint:after { + position: absolute; + display: block; + width: 15px; + height: 1px; + z-index: 1; + right: 1px; + top: 4px; + bottom: 0; + margin: auto; + padding: 2px; + font-size: 18px; + font-weight: bold; + content: "\00d7"; + text-align: center; + cursor: pointer; + color: #ccc; + background: white; + padding-bottom: 16px; + margin-top: -16px; + padding-right: 4px; + line-height: 1; +} + +.leaflet-routing-remove-waypoint:hover { + color: black; +} + +.leaflet-routing-instruction-distance { + width: 48px; +} + +.leaflet-routing-collapse-btn { + position: absolute; + top: 0; + right: 6px; + font-size: 24px; + color: #ccc; + font-weight: bold; +} + +.leaflet-routing-collapse-btn:after { + content: '\00d7'; +} + +.leaflet-routing-container-hide .leaflet-routing-collapse-btn { + position: relative; + left: 4px; + top: 4px; + display: block; + width: 26px; + height: 23px; + background-image: url('routing-icon.png'); +} + +.leaflet-routing-container-hide .leaflet-routing-collapse-btn:after { + content: none; +} + +.leaflet-top .leaflet-routing-container.leaflet-routing-container-hide { + margin-top: 10px !important; +} +.leaflet-right .leaflet-routing-container.leaflet-routing-container-hide { + margin-right: 10px !important; +} +.leaflet-bottom .leaflet-routing-container.leaflet-routing-container-hide { + margin-bottom: 10px !important; +} +.leaflet-left .leaflet-routing-container.leaflet-routing-container-hide { + margin-left: 10px !important; +} + +@media only screen and (max-width: 640px) { + .leaflet-routing-container { + margin: 0 !important; + padding: 0 !important; + width: 100%; + height: 100%; + } +} diff --git a/public/js/leaflet-routing-machine.js b/public/js/leaflet-routing-machine.js index f528762..fc820c5 100644 --- a/public/js/leaflet-routing-machine.js +++ b/public/js/leaflet-routing-machine.js @@ -1,21 +1,190 @@ -// Packaging/modules magic dance. This code is inserted before all other -// code when the dist is built. -(function (factory) { - var L; - if (typeof define === 'function' && define.amd) { - // AMD - define(['leaflet'], factory); - } else if (typeof module !== 'undefined') { - // Node/CommonJS - L = require('leaflet'); - module.exports = factory(L); - } else { - // Browser globals - if (typeof window.L === 'undefined') - throw 'Leaflet must be loaded first'; - factory(window.L); +(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}(g.L || (g.L = {})).Routing = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o= 200 && status < 300 || status === 304; + } + + if (cors && !('withCredentials' in x)) { + // IE8-9 + x = new window.XDomainRequest(); + + // Ensure callback is never called synchronously, i.e., before + // x.send() returns (this has been observed in the wild). + // See https://github.com/mapbox/mapbox.js/issues/472 + var original = callback; + callback = function() { + if (sent) { + original.apply(this, arguments); + } else { + var that = this, args = arguments; + setTimeout(function() { + original.apply(that, args); + }, 0); + } + } + } + + function loaded() { + if ( + // XDomainRequest + x.status === undefined || + // modern browsers + isSuccessful(x.status)) callback.call(x, null, x); + else callback.call(x, x, null); + } + + // Both `onreadystatechange` and `onload` can fire. `onreadystatechange` + // has [been supported for longer](http://stackoverflow.com/a/9181508/229001). + if ('onload' in x) { + x.onload = loaded; + } else { + x.onreadystatechange = function readystate() { + if (x.readyState === 4) { + loaded(); + } + }; + } + + // Call the callback with the XMLHttpRequest object as an error and prevent + // it from ever being called again by reassigning it to `noop` + x.onerror = function error(evt) { + // XDomainRequest provides no evt parameter + callback.call(this, evt || true, null); + callback = function() { }; + }; + + // IE9 must have onprogress be set to a unique function. + x.onprogress = function() { }; + + x.ontimeout = function(evt) { + callback.call(this, evt, null); + callback = function() { }; + }; + + x.onabort = function(evt) { + callback.call(this, evt, null); + callback = function() { }; + }; + + // GET is the only supported HTTP Verb by XDomainRequest and is the + // only one supported here. + x.open('GET', url, true); + + // Send the request. Sending data is not supported. + x.send(null); + sent = true; + + return x; +} + +if (typeof module !== 'undefined') module.exports = corslite; + +},{}],2:[function(require,module,exports){ +var polyline = {}; + +// Based off of [the offical Google document](https://developers.google.com/maps/documentation/utilities/polylinealgorithm) +// +// Some parts from [this implementation](http://facstaff.unca.edu/mcmcclur/GoogleMaps/EncodePolyline/PolylineEncoder.js) +// by [Mark McClure](http://facstaff.unca.edu/mcmcclur/) + +function encode(coordinate, factor) { + coordinate = Math.round(coordinate * factor); + coordinate <<= 1; + if (coordinate < 0) { + coordinate = ~coordinate; + } + var output = ''; + while (coordinate >= 0x20) { + output += String.fromCharCode((0x20 | (coordinate & 0x1f)) + 63); + coordinate >>= 5; + } + output += String.fromCharCode(coordinate + 63); + return output; +} + +// This is adapted from the implementation in Project-OSRM +// https://github.com/DennisOSRM/Project-OSRM-Web/blob/master/WebContent/routing/OSRM.RoutingGeometry.js +polyline.decode = function(str, precision) { + var index = 0, + lat = 0, + lng = 0, + coordinates = [], + shift = 0, + result = 0, + byte = null, + latitude_change, + longitude_change, + factor = Math.pow(10, precision || 5); + + // Coordinates have variable length when encoded, so just keep + // track of whether we've hit the end of the string. In each + // loop iteration, a single coordinate is decoded. + while (index < str.length) { + + // Reset shift, result, and byte + byte = null; + shift = 0; + result = 0; + + do { + byte = str.charCodeAt(index++) - 63; + result |= (byte & 0x1f) << shift; + shift += 5; + } while (byte >= 0x20); + + latitude_change = ((result & 1) ? ~(result >> 1) : (result >> 1)); + + shift = result = 0; + + do { + byte = str.charCodeAt(index++) - 63; + result |= (byte & 0x1f) << shift; + shift += 5; + } while (byte >= 0x20); + + longitude_change = ((result & 1) ? ~(result >> 1) : (result >> 1)); + + lat += latitude_change; + lng += longitude_change; + + coordinates.push([lat / factor, lng / factor]); + } + + return coordinates; +}; + +polyline.encode = function(coordinates, precision) { + if (!coordinates.length) return ''; + + var factor = Math.pow(10, precision || 5), + output = encode(coordinates[0][0], factor) + encode(coordinates[0][1], factor); + + for (var i = 1; i < coordinates.length; i++) { + var a = coordinates[i], b = coordinates[i - 1]; + output += encode(a[0] - b[0], factor); + output += encode(a[1] - b[1], factor); + } + + return output; +}; + +if (typeof module !== undefined) module.exports = polyline; + +},{}],3:[function(require,module,exports){ (function() { 'use strict'; @@ -23,7 +192,9 @@ L.Routing.Autocomplete = L.Class.extend({ options: { - timeout: 500 + timeout: 500, + blurTimeout: 100, + noResultsMessage: 'No results found.' }, initialize: function(elem, callback, context, options) { @@ -36,13 +207,15 @@ this._container = L.DomUtil.create('div', 'leaflet-routing-geocoder-result'); this._resultTable = L.DomUtil.create('table', '', this._container); + // TODO: looks a bit like a kludge to register same for input and keypress - + // browsers supporting both will get duplicate events; just registering + // input will not catch enter, though. + L.DomEvent.addListener(this._elem, 'input', this._keyPressed, this); L.DomEvent.addListener(this._elem, 'keypress', this._keyPressed, this); L.DomEvent.addListener(this._elem, 'keydown', this._keyDown, this); L.DomEvent.addListener(this._elem, 'blur', function() { if (this._isOpen) { - // TODO: setTimeout here looks like a hack, but is needed - // to get click events to fire before hiding the container. - setTimeout(L.Util.bind(function() { this.close(); }, this), 100); + this.close(); } }, this); }, @@ -53,16 +226,12 @@ }, _open: function() { - var sibling = this._elem.nextSibling; + var rect = this._elem.getBoundingClientRect(); if (!this._container.parentElement) { - if (sibling) { - this._elem.parentElement.insertBefore(this._container, sibling); - } else { - this._elem.parentElement.appendChild(this._container); - } - this._container.style.left = this._elem.offsetLeft + 'px'; - this._container.style.top = (this._elem.offsetTop + this._elem.offsetHeight) + 'px'; - this._container.style.width = this._elem.offsetWidth + 'px'; + this._container.style.left = (rect.left + window.scrollX) + 'px'; + this._container.style.top = (rect.bottom + window.scrollY) + 'px'; + this._container.style.width = (rect.right - rect.left) + 'px'; + document.body.appendChild(this._container); } L.DomUtil.addClass(this._container, 'leaflet-routing-geocoder-result-open'); @@ -72,7 +241,8 @@ _setResults: function(results) { var i, tr, - td; + td, + text; delete this._selection; this._results = results; @@ -85,22 +255,43 @@ tr = L.DomUtil.create('tr', '', this._resultTable); tr.setAttribute('data-result-index', i); td = L.DomUtil.create('td', '', tr); - td.textContent = results[i].name; - L.DomEvent.addListener(td, 'click', this._resultSelected(results[i]), this); + text = document.createTextNode(results[i].name); + td.appendChild(text); + // mousedown + click because: + // http://stackoverflow.com/questions/10652852/jquery-fire-click-before-blur-event + L.DomEvent.addListener(td, 'mousedown', L.DomEvent.preventDefault); + L.DomEvent.addListener(td, 'click', this._createClickListener(results[i])); } + if (!i) { + tr = L.DomUtil.create('tr', '', this._resultTable); + td = L.DomUtil.create('td', 'leaflet-routing-geocoder-no-results', tr); + td.innerHTML = this.options.noResultsMessage; + } + + this._open(); + if (results.length > 0) { - this._open(); // Select the first entry this._select(1); } }, + _createClickListener: function(r) { + var resultSelected = this._resultSelected(r); + return L.bind(function() { + this._elem.blur(); + resultSelected(); + }, this); + }, + _resultSelected: function(r) { - return function() { + return L.bind(function() { this.close(); + this._elem.value = r.name; + this._lastCompletedText = r.name; this._selectFn(r); - }; + }, this); }, _keyPressed: function(e) { @@ -108,22 +299,26 @@ if (this._isOpen && e.keyCode === 13 && this._selection) { index = parseInt(this._selection.getAttribute('data-result-index'), 10); - this._resultSelected(this._results[index]).call(this); + this._resultSelected(this._results[index])(); L.DomEvent.preventDefault(e); return; } if (e.keyCode === 13) { this._complete(this._resultFn, true); + return; } - if (this._autocomplete) { + if (this._autocomplete && document.activeElement === this._elem) { if (this._timer) { clearTimeout(this._timer); } this._timer = setTimeout(L.Util.bind(function() { this._complete(this._autocomplete); }, this), this.options.timeout); + return; } + + this._unselect(); }, _select: function(dir) { @@ -142,15 +337,27 @@ } }, + _unselect: function() { + if (this._selection) { + L.DomUtil.removeClass(this._selection.firstChild, 'leaflet-routing-geocoder-selected'); + } + delete this._selection; + }, + _keyDown: function(e) { if (this._isOpen) { switch (e.keyCode) { + // Escape + case 27: + this.close(); + L.DomEvent.preventDefault(e); + return; // Up case 38: this._select(-1); L.DomEvent.preventDefault(e); return; - // Up + // Down case 40: this._select(1); L.DomEvent.preventDefault(e); @@ -160,38 +367,1563 @@ }, _complete: function(completeFn, trySelect) { - completeFn(this._elem.value, function(results) { + var v = this._elem.value; + function completeResults(results) { + this._lastCompletedText = v; if (trySelect && results.length === 1) { - this._resultSelected(results[0]); + this._resultSelected(results[0])(); } else { this._setResults(results); } - }, this); + } + + if (!v) { + return; + } + + if (v !== this._lastCompletedText) { + completeFn(v, completeResults, this); + } else if (trySelect) { + completeResults.call(this, this._results); + } } }); })(); + +},{}],4:[function(require,module,exports){ +(function (global){ +(function() { + 'use strict'; + + var L = (typeof window !== "undefined" ? window.L : typeof global !== "undefined" ? global.L : null); + + L.Routing = L.Routing || {}; + L.extend(L.Routing, require('./L.Routing.Itinerary')); + L.extend(L.Routing, require('./L.Routing.Line')); + L.extend(L.Routing, require('./L.Routing.Plan')); + L.extend(L.Routing, require('./L.Routing.OSRM')); + L.extend(L.Routing, require('./L.Routing.ErrorControl')); + + L.Routing.Control = L.Routing.Itinerary.extend({ + options: { + fitSelectedRoutes: 'smart', + routeLine: function(route, options) { return L.Routing.line(route, options); }, + autoRoute: true, + routeWhileDragging: false, + routeDragInterval: 500, + waypointMode: 'connect', + useZoomParameter: false, + showAlternatives: false + }, + + initialize: function(options) { + L.Util.setOptions(this, options); + + this._router = this.options.router || new L.Routing.OSRM(options); + this._plan = this.options.plan || L.Routing.plan(this.options.waypoints, options); + this._requestCount = 0; + + L.Routing.Itinerary.prototype.initialize.call(this, options); + + this.on('routeselected', this._routeSelected, this); + this._plan.on('waypointschanged', this._onWaypointsChanged, this); + if (options.routeWhileDragging) { + this._setupRouteDragging(); + } + + if (this.options.autoRoute) { + this.route(); + } + }, + + onAdd: function(map) { + var container = L.Routing.Itinerary.prototype.onAdd.call(this, map); + + this._map = map; + this._map.addLayer(this._plan); + + if (this.options.useZoomParameter) { + this._map.on('zoomend', function() { + this.route({ + callback: L.bind(this._updateLineCallback, this) + }); + }, this); + } + + if (this._plan.options.geocoder) { + container.insertBefore(this._plan.createGeocoders(), container.firstChild); + } + + return container; + }, + + onRemove: function(map) { + if (this._line) { + map.removeLayer(this._line); + } + map.removeLayer(this._plan); + return L.Routing.Itinerary.prototype.onRemove.call(this, map); + }, + + getWaypoints: function() { + return this._plan.getWaypoints(); + }, + + setWaypoints: function(waypoints) { + this._plan.setWaypoints(waypoints); + return this; + }, + + spliceWaypoints: function() { + var removed = this._plan.spliceWaypoints.apply(this._plan, arguments); + return removed; + }, + + getPlan: function() { + return this._plan; + }, + + getRouter: function() { + return this._router; + }, + + _routeSelected: function(e) { + var route = e.route, + alternatives = this.options.showAlternatives && e.alternatives, + fitMode = this.options.fitSelectedRoutes, + fitBounds = + (fitMode === 'smart' && !this._waypointsVisible()) || + (fitMode !== 'smart' && fitMode); + + this._updateLines({route: route, alternatives: alternatives}); + + if (fitBounds) { + this._map.fitBounds(this._line.getBounds()); + } + + if (this.options.waypointMode === 'snap') { + this._plan.off('waypointschanged', this._onWaypointsChanged, this); + this.setWaypoints(route.waypoints); + this._plan.on('waypointschanged', this._onWaypointsChanged, this); + } + }, + + _waypointsVisible: function() { + var wps = this.getWaypoints(), + mapSize, + bounds, + boundsSize, + i, + p; + + try { + mapSize = this._map.getSize(); + + for (i = 0; i < wps.length; i++) { + p = this._map.latLngToLayerPoint(wps[i].latLng); + + if (bounds) { + bounds.extend(p); + } else { + bounds = L.bounds([p]); + } + } + + boundsSize = bounds.getSize(); + return (boundsSize.x > mapSize.x / 5 || + boundsSize.y > mapSize.y / 5) && this._waypointsInViewport(); + + } catch (e) { + return false; + } + }, + + _waypointsInViewport: function() { + var wps = this.getWaypoints(), + mapBounds, + i; + + try { + mapBounds = this._map.getBounds(); + } catch (e) { + return false; + } + + for (i = 0; i < wps.length; i++) { + if (mapBounds.contains(wps[i].latLng)) { + return true; + } + } + + return false; + }, + + _updateLines: function(routes) { + var addWaypoints = this.options.addWaypoints !== undefined ? + this.options.addWaypoints : true; + this._clearLines(); + + // add alternatives first so they lie below the main route + this._alternatives = []; + if (routes.alternatives) routes.alternatives.forEach(function(alt, i) { + this._alternatives[i] = this.options.routeLine(alt, + L.extend({ + isAlternative: true + }, this.options.altLineOptions || this.options.lineOptions)); + this._alternatives[i].addTo(this._map); + this._hookAltEvents(this._alternatives[i]); + }, this); + + this._line = this.options.routeLine(routes.route, + L.extend({ + addWaypoints: addWaypoints, + extendToWaypoints: this.options.waypointMode === 'connect' + }, this.options.lineOptions)); + this._line.addTo(this._map); + this._hookEvents(this._line); + }, + + _hookEvents: function(l) { + l.on('linetouched', function(e) { + this._plan.dragNewWaypoint(e); + }, this); + }, + + _hookAltEvents: function(l) { + l.on('linetouched', function(e) { + var alts = this._routes.slice(); + var selected = alts.splice(e.target._route.routesIndex, 1)[0]; + this.fire('routeselected', {route: selected, alternatives: alts}); + }, this); + }, + + _onWaypointsChanged: function(e) { + if (this.options.autoRoute) { + this.route({}); + } + if (!this._plan.isReady()) { + this._clearLines(); + this._clearAlts(); + } + this.fire('waypointschanged', {waypoints: e.waypoints}); + }, + + _setupRouteDragging: function() { + var timer = 0, + waypoints; + + this._plan.on('waypointdrag', L.bind(function(e) { + waypoints = e.waypoints; + + if (!timer) { + timer = setTimeout(L.bind(function() { + this.route({ + waypoints: waypoints, + geometryOnly: true, + callback: L.bind(this._updateLineCallback, this) + }); + timer = undefined; + }, this), this.options.routeDragInterval); + } + }, this)); + this._plan.on('waypointdragend', function() { + if (timer) { + clearTimeout(timer); + timer = undefined; + } + this.route(); + }, this); + }, + + _updateLineCallback: function(err, routes) { + if (!err) { + this._updateLines({route: routes[0], alternatives: routes.slice(1) }); + } else { + this._clearLines(); + } + }, + + route: function(options) { + var ts = ++this._requestCount, + wps; + + options = options || {}; + + if (this._plan.isReady()) { + if (this.options.useZoomParameter) { + options.z = this._map && this._map.getZoom(); + } + + wps = options && options.waypoints || this._plan.getWaypoints(); + this.fire('routingstart', {waypoints: wps}); + this._router.route(wps, options.callback || function(err, routes) { + // Prevent race among multiple requests, + // by checking the current request's timestamp + // against the last request's; ignore result if + // this isn't the latest request. + if (ts === this._requestCount) { + this._clearLines(); + this._clearAlts(); + if (err) { + this.fire('routingerror', {error: err}); + return; + } + + routes.forEach(function(route, i) { route.routesIndex = i; }); + + if (!options.geometryOnly) { + this.fire('routesfound', {waypoints: wps, routes: routes}); + this.setAlternatives(routes); + } else { + var selectedRoute = routes.splice(0,1)[0]; + this._routeSelected({route: selectedRoute, alternatives: routes}); + } + } + }, this, options); + } + }, + + _clearLines: function() { + if (this._line) { + this._map.removeLayer(this._line); + delete this._line; + } + if (this._alternatives && this._alternatives.length) { + for (var i in this._alternatives) { + this._map.removeLayer(this._alternatives[i]); + } + this._alternatives = []; + } + } + }); + + L.Routing.control = function(options) { + return new L.Routing.Control(options); + }; + + module.exports = L.Routing; +})(); + +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{"./L.Routing.ErrorControl":5,"./L.Routing.Itinerary":8,"./L.Routing.Line":10,"./L.Routing.OSRM":12,"./L.Routing.Plan":13}],5:[function(require,module,exports){ (function() { 'use strict'; L.Routing = L.Routing || {}; - L.Routing._jsonpCallbackId = 0; - L.Routing._jsonp = function(url, callback, context, jsonpParam) { - var callbackId = '_l_routing_machine_' + (L.Routing._jsonpCallbackId++), - script; - url += '&' + jsonpParam + '=' + callbackId; - window[callbackId] = L.Util.bind(callback, context); - script = document.createElement('script'); - script.type = 'text/javascript'; - script.src = url; - script.id = callbackId; - document.getElementsByTagName('head')[0].appendChild(script); + L.Routing.ErrorControl = L.Control.extend({ + options: { + header: 'Routing error', + formatMessage: function(error) { + if (error.status < 0) { + return 'Calculating the route caused an error. Technical description follows:
' +
+						error.message + '
= 1000) { + data = { + value: round(d / 1609.344, sensitivity), + unit: un.miles + }; + } else { + data = { + value: round(yards, sensitivity), + unit: un.yards + }; + } + } else { + v = round(d, sensitivity); + data = { + value: v >= 1000 ? (v / 1000) : v, + unit: v >= 1000 ? un.kilometers : un.meters + }; + } + + if (simpleRounding) { + pow10 = Math.pow(10, -sensitivity); + data.value = Math.round(data.value * pow10) / pow10; + } + + return L.Util.template(this.options.distanceTemplate, data); + }, + + _round: function(d, sensitivity) { + var s = sensitivity || this.options.roundingSensitivity, + pow10 = Math.pow(10, (Math.floor(d / s) + '').length - 1), + r = Math.floor(d / pow10), + p = (r > 5) ? pow10 : pow10 / 2; + + return Math.round(d / p) * p; + }, + + formatTime: function(t /* Number (seconds) */) { + if (t > 86400) { + return Math.round(t / 3600) + ' h'; + } else if (t > 3600) { + return Math.floor(t / 3600) + ' h ' + + Math.round((t % 3600) / 60) + ' min'; + } else if (t > 300) { + return Math.round(t / 60) + ' min'; + } else if (t > 60) { + return Math.floor(t / 60) + ' min' + + (t % 60 !== 0 ? ' ' + (t % 60) + ' s' : ''); + } else { + return t + ' s'; + } + }, + + formatInstruction: function(instr, i) { + if (instr.text === undefined) { + return L.Util.template(this._getInstructionTemplate(instr, i), + L.extend({ + exitStr: instr.exit ? L.Routing.Localization[this.options.language].formatOrder(instr.exit) : '', + dir: L.Routing.Localization[this.options.language].directions[instr.direction] + }, + instr)); + } else { + return instr.text; + } + }, + + getIconName: function(instr, i) { + switch (instr.type) { + case 'Straight': + return (i === 0 ? 'depart' : 'continue'); + case 'SlightRight': + return 'bear-right'; + case 'Right': + return 'turn-right'; + case 'SharpRight': + return 'sharp-right'; + case 'TurnAround': + return 'u-turn'; + case 'SharpLeft': + return 'sharp-left'; + case 'Left': + return 'turn-left'; + case 'SlightLeft': + return 'bear-left'; + case 'WaypointReached': + return 'via'; + case 'Roundabout': + return 'enter-roundabout'; + case 'DestinationReached': + return 'arrive'; + } + }, + + _getInstructionTemplate: function(instr, i) { + var type = instr.type === 'Straight' ? (i === 0 ? 'Head' : 'Continue') : instr.type, + strings = L.Routing.Localization[this.options.language].instructions[type]; + + return strings[0] + (strings.length > 1 && instr.road ? strings[1] : ''); + } + }); + + module.exports = L.Routing; +})(); + + +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{"./L.Routing.Localization":11}],7:[function(require,module,exports){ +(function (global){ +(function() { + 'use strict'; + + var L = (typeof window !== "undefined" ? window.L : typeof global !== "undefined" ? global.L : null); + L.Routing = L.Routing || {}; + L.extend(L.Routing, require('./L.Routing.Autocomplete')); + + function selectInputText(input) { + if (input.setSelectionRange) { + // On iOS, select() doesn't work + input.setSelectionRange(0, 9999); + } else { + // On at least IE8, setSeleectionRange doesn't exist + input.select(); + } + } + + L.Routing.GeocoderElement = L.Class.extend({ + includes: L.Mixin.Events, + + options: { + createGeocoder: function(i, nWps, options) { + var container = L.DomUtil.create('div', 'leaflet-routing-geocoder'), + input = L.DomUtil.create('input', '', container), + remove = options.addWaypoints ? L.DomUtil.create('span', 'leaflet-routing-remove-waypoint', container) : undefined; + + input.disabled = !options.addWaypoints; + + return { + container: container, + input: input, + closeButton: remove + }; + }, + geocoderPlaceholder: function(i, numberWaypoints, plan) { + var l = L.Routing.Localization[plan.options.language].ui; + return i === 0 ? + l.startPlaceholder : + (i < numberWaypoints - 1 ? + L.Util.template(l.viaPlaceholder, {viaNumber: i}) : + l.endPlaceholder); + }, + + geocoderClass: function() { + return ''; + }, + + waypointNameFallback: function(latLng) { + var ns = latLng.lat < 0 ? 'S' : 'N', + ew = latLng.lng < 0 ? 'W' : 'E', + lat = (Math.round(Math.abs(latLng.lat) * 10000) / 10000).toString(), + lng = (Math.round(Math.abs(latLng.lng) * 10000) / 10000).toString(); + return ns + lat + ', ' + ew + lng; + }, + maxGeocoderTolerance: 200, + autocompleteOptions: {}, + language: 'en', + }, + + initialize: function(wp, i, nWps, options) { + L.setOptions(this, options); + + var g = this.options.createGeocoder(i, nWps, this.options), + closeButton = g.closeButton, + geocoderInput = g.input; + geocoderInput.setAttribute('placeholder', this.options.geocoderPlaceholder(i, nWps, this)); + geocoderInput.className = this.options.geocoderClass(i, nWps); + + this._element = g; + this._waypoint = wp; + + this.update(); + // This has to be here, or geocoder's value will not be properly + // initialized. + // TODO: look into why and make _updateWaypointName fix this. + geocoderInput.value = wp.name; + + L.DomEvent.addListener(geocoderInput, 'click', function() { + selectInputText(this); + }, geocoderInput); + + if (closeButton) { + L.DomEvent.addListener(closeButton, 'click', function() { + this.fire('delete', { waypoint: this._waypoint }); + }, this); + } + + new L.Routing.Autocomplete(geocoderInput, function(r) { + geocoderInput.value = r.name; + wp.name = r.name; + wp.latLng = r.center; + this.fire('geocoded', { waypoint: wp, value: r }); + }, this, L.extend({ + resultFn: this.options.geocoder.geocode, + resultContext: this.options.geocoder, + autocompleteFn: this.options.geocoder.suggest, + autocompleteContext: this.options.geocoder + }, this.options.autocompleteOptions)); + }, + + getContainer: function() { + return this._element.container; + }, + + setValue: function(v) { + this._element.input.value = v; + }, + + update: function(force) { + var wp = this._waypoint, + wpCoords; + + wp.name = wp.name || ''; + + if (wp.latLng && (force || !wp.name)) { + wpCoords = this.options.waypointNameFallback(wp.latLng); + if (this.options.geocoder && this.options.geocoder.reverse) { + this.options.geocoder.reverse(wp.latLng, 67108864 /* zoom 18 */, function(rs) { + if (rs.length > 0 && rs[0].center.distanceTo(wp.latLng) < this.options.maxGeocoderTolerance) { + wp.name = rs[0].name; + } else { + wp.name = wpCoords; + } + this._update(); + }, this); + } else { + wp.name = wpCoords; + this._update(); + } + } + }, + + focus: function() { + var input = this._element.input; + input.focus(); + selectInputText(input); + }, + + _update: function() { + var wp = this._waypoint, + value = wp && wp.name ? wp.name : ''; + this.setValue(value); + this.fire('reversegeocoded', {waypoint: wp, value: value}); + } + }); + + L.Routing.geocoderElement = function(wp, i, nWps, plan) { + return new L.Routing.GeocoderElement(wp, i, nWps, plan); + }; + + module.exports = L.Routing; +})(); + +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{"./L.Routing.Autocomplete":3}],8:[function(require,module,exports){ +(function (global){ +(function() { + 'use strict'; + + var L = (typeof window !== "undefined" ? window.L : typeof global !== "undefined" ? global.L : null); + + L.Routing = L.Routing || {}; + L.extend(L.Routing, require('./L.Routing.Formatter')); + L.extend(L.Routing, require('./L.Routing.ItineraryBuilder')); + + L.Routing.Itinerary = L.Control.extend({ + includes: L.Mixin.Events, + + options: { + pointMarkerStyle: { + radius: 5, + color: '#03f', + fillColor: 'white', + opacity: 1, + fillOpacity: 0.7 + }, + summaryTemplate: '

{name}

{distance}, {time}

', + timeTemplate: '{time}', + containerClassName: '', + alternativeClassName: '', + minimizedClassName: '', + itineraryClassName: '', + totalDistanceRoundingSensitivity: -1, + show: true, + collapsible: undefined, + collapseBtn: function(itinerary) { + var collapseBtn = L.DomUtil.create('span', itinerary.options.collapseBtnClass); + L.DomEvent.on(collapseBtn, 'click', itinerary._toggle, itinerary); + itinerary._container.insertBefore(collapseBtn, itinerary._container.firstChild); + }, + collapseBtnClass: 'leaflet-routing-collapse-btn' + }, + + initialize: function(options) { + L.setOptions(this, options); + this._formatter = this.options.formatter || new L.Routing.Formatter(this.options); + this._itineraryBuilder = this.options.itineraryBuilder || new L.Routing.ItineraryBuilder({ + containerClassName: this.options.itineraryClassName + }); + }, + + onAdd: function(map) { + var collapsible = this.options.collapsible; + + collapsible = collapsible || (collapsible === undefined && map.getSize().x <= 640); + + this._container = L.DomUtil.create('div', 'leaflet-routing-container leaflet-bar ' + + (!this.options.show ? 'leaflet-routing-container-hide ' : '') + + (collapsible ? 'leaflet-routing-collapsible ' : '') + + this.options.containerClassName); + this._altContainer = this.createAlternativesContainer(); + this._container.appendChild(this._altContainer); + L.DomEvent.disableClickPropagation(this._container); + L.DomEvent.addListener(this._container, 'mousewheel', function(e) { + L.DomEvent.stopPropagation(e); + }); + + if (collapsible) { + this.options.collapseBtn(this); + } + + return this._container; + }, + + onRemove: function() { + }, + + createAlternativesContainer: function() { + return L.DomUtil.create('div', 'leaflet-routing-alternatives-container'); + }, + + setAlternatives: function(routes) { + var i, + alt, + altDiv; + + this._clearAlts(); + + this._routes = routes; + + for (i = 0; i < this._routes.length; i++) { + alt = this._routes[i]; + altDiv = this._createAlternative(alt, i); + this._altContainer.appendChild(altDiv); + this._altElements.push(altDiv); + } + + this._selectRoute({route: this._routes[0], alternatives: this._routes.slice(1)}); + + return this; + }, + + show: function() { + L.DomUtil.removeClass(this._container, 'leaflet-routing-container-hide'); + }, + + hide: function() { + L.DomUtil.addClass(this._container, 'leaflet-routing-container-hide'); + }, + + _toggle: function() { + var collapsed = L.DomUtil.hasClass(this._container, 'leaflet-routing-container-hide'); + this[collapsed ? 'show' : 'hide'](); + }, + + _createAlternative: function(alt, i) { + var altDiv = L.DomUtil.create('div', 'leaflet-routing-alt ' + + this.options.alternativeClassName + + (i > 0 ? ' leaflet-routing-alt-minimized ' + this.options.minimizedClassName : '')), + template = this.options.summaryTemplate, + data = L.extend({ + name: alt.name, + distance: this._formatter.formatDistance(alt.summary.totalDistance, this.options.totalDistanceRoundingSensitivity), + time: this._formatter.formatTime(alt.summary.totalTime) + }, alt); + altDiv.innerHTML = typeof(template) === 'function' ? template(data) : L.Util.template(template, data); + L.DomEvent.addListener(altDiv, 'click', this._onAltClicked, this); + this.on('routeselected', this._selectAlt, this); + + altDiv.appendChild(this._createItineraryContainer(alt)); + return altDiv; + }, + + _clearAlts: function() { + var el = this._altContainer; + while (el && el.firstChild) { + el.removeChild(el.firstChild); + } + + this._altElements = []; + }, + + _createItineraryContainer: function(r) { + var container = this._itineraryBuilder.createContainer(), + steps = this._itineraryBuilder.createStepsContainer(), + i, + instr, + step, + distance, + text, + icon; + + container.appendChild(steps); + + for (i = 0; i < r.instructions.length; i++) { + instr = r.instructions[i]; + text = this._formatter.formatInstruction(instr, i); + distance = this._formatter.formatDistance(instr.distance); + icon = this._formatter.getIconName(instr, i); + step = this._itineraryBuilder.createStep(text, distance, icon, steps); + + this._addRowListeners(step, r.coordinates[instr.index]); + } + + return container; + }, + + _addRowListeners: function(row, coordinate) { + L.DomEvent.addListener(row, 'mouseover', function() { + this._marker = L.circleMarker(coordinate, + this.options.pointMarkerStyle).addTo(this._map); + }, this); + L.DomEvent.addListener(row, 'mouseout', function() { + if (this._marker) { + this._map.removeLayer(this._marker); + delete this._marker; + } + }, this); + L.DomEvent.addListener(row, 'click', function(e) { + this._map.panTo(coordinate); + L.DomEvent.stopPropagation(e); + }, this); + }, + + _onAltClicked: function(e) { + var altElem = e.target || window.event.srcElement; + while (!L.DomUtil.hasClass(altElem, 'leaflet-routing-alt')) { + altElem = altElem.parentElement; + } + + var j = this._altElements.indexOf(altElem); + var alts = this._routes.slice(); + var route = alts.splice(j, 1)[0]; + + this.fire('routeselected', { + route: route, + alternatives: alts + }); + }, + + _selectAlt: function(e) { + var altElem, + j, + n, + classFn; + + altElem = this._altElements[e.route.routesIndex]; + + if (L.DomUtil.hasClass(altElem, 'leaflet-routing-alt-minimized')) { + for (j = 0; j < this._altElements.length; j++) { + n = this._altElements[j]; + classFn = j === e.route.routesIndex ? 'removeClass' : 'addClass'; + L.DomUtil[classFn](n, 'leaflet-routing-alt-minimized'); + if (this.options.minimizedClassName) { + L.DomUtil[classFn](n, this.options.minimizedClassName); + } + + if (j !== e.route.routesIndex) n.scrollTop = 0; + } + } + + L.DomEvent.stop(e); + }, + + _selectRoute: function(routes) { + if (this._marker) { + this._map.removeLayer(this._marker); + delete this._marker; + } + this.fire('routeselected', routes); + } + }); + + L.Routing.itinerary = function(options) { + return new L.Routing.Itinerary(options); + }; + + module.exports = L.Routing; +})(); + +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{"./L.Routing.Formatter":6,"./L.Routing.ItineraryBuilder":9}],9:[function(require,module,exports){ +(function (global){ +(function() { + 'use strict'; + + var L = (typeof window !== "undefined" ? window.L : typeof global !== "undefined" ? global.L : null); + L.Routing = L.Routing || {}; + + L.Routing.ItineraryBuilder = L.Class.extend({ + options: { + containerClassName: '' + }, + + initialize: function(options) { + L.setOptions(this, options); + }, + + createContainer: function(className) { + var table = L.DomUtil.create('table', className || ''), + colgroup = L.DomUtil.create('colgroup', '', table); + + L.DomUtil.create('col', 'leaflet-routing-instruction-icon', colgroup); + L.DomUtil.create('col', 'leaflet-routing-instruction-text', colgroup); + L.DomUtil.create('col', 'leaflet-routing-instruction-distance', colgroup); + + return table; + }, + + createStepsContainer: function() { + return L.DomUtil.create('tbody', ''); + }, + + createStep: function(text, distance, icon, steps) { + var row = L.DomUtil.create('tr', '', steps), + span, + td; + td = L.DomUtil.create('td', '', row); + span = L.DomUtil.create('span', 'leaflet-routing-icon leaflet-routing-icon-'+icon, td); + td.appendChild(span); + td = L.DomUtil.create('td', '', row); + td.appendChild(document.createTextNode(text)); + td = L.DomUtil.create('td', '', row); + td.appendChild(document.createTextNode(distance)); + return row; + } + }); + + module.exports = L.Routing; +})(); + +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{}],10:[function(require,module,exports){ +(function (global){ +(function() { + 'use strict'; + + var L = (typeof window !== "undefined" ? window.L : typeof global !== "undefined" ? global.L : null); + + L.Routing = L.Routing || {}; + + L.Routing.Line = L.LayerGroup.extend({ + includes: L.Mixin.Events, + + options: { + styles: [ + {color: 'black', opacity: 0.15, weight: 9}, + {color: 'white', opacity: 0.8, weight: 6}, + {color: 'red', opacity: 1, weight: 2} + ], + missingRouteStyles: [ + {color: 'black', opacity: 0.15, weight: 7}, + {color: 'white', opacity: 0.6, weight: 4}, + {color: 'gray', opacity: 0.8, weight: 2, dashArray: '7,12'} + ], + addWaypoints: true, + extendToWaypoints: true, + missingRouteTolerance: 10 + }, + + initialize: function(route, options) { + L.setOptions(this, options); + L.LayerGroup.prototype.initialize.call(this, options); + this._route = route; + + if (this.options.extendToWaypoints) { + this._extendToWaypoints(); + } + + this._addSegment( + route.coordinates, + this.options.styles, + this.options.addWaypoints); + }, + + addTo: function(map) { + map.addLayer(this); + return this; + }, + getBounds: function() { + return L.latLngBounds(this._route.coordinates); + }, + + _findWaypointIndices: function() { + var wps = this._route.inputWaypoints, + indices = [], + i; + for (i = 0; i < wps.length; i++) { + indices.push(this._findClosestRoutePoint(wps[i].latLng)); + } + + return indices; + }, + + _findClosestRoutePoint: function(latlng) { + var minDist = Number.MAX_VALUE, + minIndex, + i, + d; + + for (i = this._route.coordinates.length - 1; i >= 0 ; i--) { + // TODO: maybe do this in pixel space instead? + d = latlng.distanceTo(this._route.coordinates[i]); + if (d < minDist) { + minIndex = i; + minDist = d; + } + } + + return minIndex; + }, + + _extendToWaypoints: function() { + var wps = this._route.inputWaypoints, + wpIndices = this._getWaypointIndices(), + i, + wpLatLng, + routeCoord; + + for (i = 0; i < wps.length; i++) { + wpLatLng = wps[i].latLng; + routeCoord = L.latLng(this._route.coordinates[wpIndices[i]]); + if (wpLatLng.distanceTo(routeCoord) > + this.options.missingRouteTolerance) { + this._addSegment([wpLatLng, routeCoord], + this.options.missingRouteStyles); + } + } + }, + + _addSegment: function(coords, styles, mouselistener) { + var i, + pl; + + for (i = 0; i < styles.length; i++) { + pl = L.polyline(coords, styles[i]); + this.addLayer(pl); + if (mouselistener) { + pl.on('mousedown', this._onLineTouched, this); + } + } + }, + + _findNearestWpBefore: function(i) { + var wpIndices = this._getWaypointIndices(), + j = wpIndices.length - 1; + while (j >= 0 && wpIndices[j] > i) { + j--; + } + + return j; + }, + + _onLineTouched: function(e) { + var afterIndex = this._findNearestWpBefore(this._findClosestRoutePoint(e.latlng)); + this.fire('linetouched', { + afterIndex: afterIndex, + latlng: e.latlng + }); + }, + + _getWaypointIndices: function() { + if (!this._wpIndices) { + this._wpIndices = this._route.waypointIndices || this._findWaypointIndices(); + } + + return this._wpIndices; + } + }); + + L.Routing.line = function(route, options) { + return new L.Routing.Line(route, options); + }; + + module.exports = L.Routing; +})(); + +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{}],11:[function(require,module,exports){ +(function() { + 'use strict'; + L.Routing = L.Routing || {}; + + L.Routing.Localization = { + 'en': { + directions: { + N: 'north', + NE: 'northeast', + E: 'east', + SE: 'southeast', + S: 'south', + SW: 'southwest', + W: 'west', + NW: 'northwest' + }, + instructions: { + // instruction, postfix if the road is named + 'Head': + ['Head {dir}', ' on {road}'], + 'Continue': + ['Continue {dir}', ' on {road}'], + 'SlightRight': + ['Slight right', ' onto {road}'], + 'Right': + ['Right', ' onto {road}'], + 'SharpRight': + ['Sharp right', ' onto {road}'], + 'TurnAround': + ['Turn around'], + 'SharpLeft': + ['Sharp left', ' onto {road}'], + 'Left': + ['Left', ' onto {road}'], + 'SlightLeft': + ['Slight left', ' onto {road}'], + 'WaypointReached': + ['Waypoint reached'], + 'Roundabout': + ['Take the {exitStr} exit in the roundabout', ' onto {road}'], + 'DestinationReached': + ['Destination reached'], + }, + formatOrder: function(n) { + var i = n % 10 - 1, + suffix = ['st', 'nd', 'rd']; + + return suffix[i] ? n + suffix[i] : n + 'th'; + }, + ui: { + startPlaceholder: 'Start', + viaPlaceholder: 'Via {viaNumber}', + endPlaceholder: 'End' + } + }, + + 'de': { + directions: { + N: 'Norden', + NE: 'Nordosten', + E: 'Osten', + SE: 'Südosten', + S: 'Süden', + SW: 'Südwesten', + W: 'Westen', + NW: 'Nordwesten' + }, + instructions: { + // instruction, postfix if the road is named + 'Head': + ['Richtung {dir}', ' auf {road}'], + 'Continue': + ['Geradeaus Richtung {dir}', ' auf {road}'], + 'SlightRight': + ['Leicht rechts abbiegen', ' auf {road}'], + 'Right': + ['Rechts abbiegen', ' auf {road}'], + 'SharpRight': + ['Scharf rechts abbiegen', ' auf {road}'], + 'TurnAround': + ['Wenden'], + 'SharpLeft': + ['Scharf links abbiegen', ' auf {road}'], + 'Left': + ['Links abbiegen', ' auf {road}'], + 'SlightLeft': + ['Leicht links abbiegen', ' auf {road}'], + 'WaypointReached': + ['Zwischenhalt erreicht'], + 'Roundabout': + ['Nehmen Sie die {exitStr} Ausfahrt im Kreisverkehr', ' auf {road}'], + 'DestinationReached': + ['Sie haben ihr Ziel erreicht'], + }, + formatOrder: function(n) { + return n + '.'; + }, + ui: { + startPlaceholder: 'Start', + viaPlaceholder: 'Via {viaNumber}', + endPlaceholder: 'Ziel' + } + }, + + 'sv': { + directions: { + N: 'norr', + NE: 'nordost', + E: 'öst', + SE: 'sydost', + S: 'syd', + SW: 'sydväst', + W: 'väst', + NW: 'nordväst' + }, + instructions: { + // instruction, postfix if the road is named + 'Head': + ['Åk åt {dir}', ' på {road}'], + 'Continue': + ['Fortsätt {dir}', ' på {road}'], + 'SlightRight': + ['Svagt höger', ' på {road}'], + 'Right': + ['Sväng höger', ' på {road}'], + 'SharpRight': + ['Skarpt höger', ' på {road}'], + 'TurnAround': + ['Vänd'], + 'SharpLeft': + ['Skarpt vänster', ' på {road}'], + 'Left': + ['Sväng vänster', ' på {road}'], + 'SlightLeft': + ['Svagt vänster', ' på {road}'], + 'WaypointReached': + ['Viapunkt nådd'], + 'Roundabout': + ['Tag {exitStr} avfarten i rondellen', ' till {road}'], + 'DestinationReached': + ['Framme vid resans mål'], + }, + formatOrder: function(n) { + return ['första', 'andra', 'tredje', 'fjärde', 'femte', + 'sjätte', 'sjunde', 'åttonde', 'nionde', 'tionde' + /* Can't possibly be more than ten exits, can there? */][n - 1]; + }, + ui: { + startPlaceholder: 'Från', + viaPlaceholder: 'Via {viaNumber}', + endPlaceholder: 'Till' + } + }, + + 'sp': { + directions: { + N: 'norte', + NE: 'noreste', + E: 'este', + SE: 'sureste', + S: 'sur', + SW: 'suroeste', + W: 'oeste', + NW: 'noroeste' + }, + instructions: { + // instruction, postfix if the road is named + 'Head': + ['Derecho {dir}', ' sobre {road}'], + 'Continue': + ['Continuar {dir}', ' en {road}'], + 'SlightRight': + ['Leve giro a la derecha', ' sobre {road}'], + 'Right': + ['Derecha', ' sobre {road}'], + 'SharpRight': + ['Giro pronunciado a la derecha', ' sobre {road}'], + 'TurnAround': + ['Dar vuelta'], + 'SharpLeft': + ['Giro pronunciado a la izquierda', ' sobre {road}'], + 'Left': + ['Izquierda', ' en {road}'], + 'SlightLeft': + ['Leve giro a la izquierda', ' en {road}'], + 'WaypointReached': + ['Llegó a un punto del camino'], + 'Roundabout': + ['Tomar {exitStr} salida en la rotonda', ' en {road}'], + 'DestinationReached': + ['Llegada a destino'], + }, + formatOrder: function(n) { + return n + 'º'; + }, + ui: { + startPlaceholder: 'Inicio', + viaPlaceholder: 'Via {viaNumber}', + endPlaceholder: 'Destino' + } + }, + 'nl': { + directions: { + N: 'noordelijke', + NE: 'noordoostelijke', + E: 'oostelijke', + SE: 'zuidoostelijke', + S: 'zuidelijke', + SW: 'zuidewestelijke', + W: 'westelijke', + NW: 'noordwestelijke' + }, + instructions: { + // instruction, postfix if the road is named + 'Head': + ['Vertrek in {dir} richting', ' de {road} op'], + 'Continue': + ['Ga in {dir} richting', ' de {road} op'], + 'SlightRight': + ['Volg de weg naar rechts', ' de {road} op'], + 'Right': + ['Ga rechtsaf', ' de {road} op'], + 'SharpRight': + ['Ga scherpe bocht naar rechts', ' de {road} op'], + 'TurnAround': + ['Keer om'], + 'SharpLeft': + ['Ga scherpe bocht naar links', ' de {road} op'], + 'Left': + ['Ga linksaf', ' de {road} op'], + 'SlightLeft': + ['Volg de weg naar links', ' de {road} op'], + 'WaypointReached': + ['Aangekomen bij tussenpunt'], + 'Roundabout': + ['Neem de {exitStr} afslag op de rotonde', ' de {road} op'], + 'DestinationReached': + ['Aangekomen op eindpunt'], + }, + formatOrder: function(n) { + if (n === 1 || n >= 20) { + return n + 'ste'; + } else { + return n + 'de'; + } + }, + ui: { + startPlaceholder: 'Vertrekpunt', + viaPlaceholder: 'Via {viaNumber}', + endPlaceholder: 'Bestemming' + } + }, + 'fr': { + directions: { + N: 'nord', + NE: 'nord-est', + E: 'est', + SE: 'sud-est', + S: 'sud', + SW: 'sud-ouest', + W: 'ouest', + NW: 'nord-ouest' + }, + instructions: { + // instruction, postfix if the road is named + 'Head': + ['Tout droit au {dir}', ' sur {road}'], + 'Continue': + ['Continuer au {dir}', ' sur {road}'], + 'SlightRight': + ['Légèrement à droite', ' sur {road}'], + 'Right': + ['A droite', ' sur {road}'], + 'SharpRight': + ['Complètement à droite', ' sur {road}'], + 'TurnAround': + ['Faire demi-tour'], + 'SharpLeft': + ['Complètement à gauche', ' sur {road}'], + 'Left': + ['A gauche', ' sur {road}'], + 'SlightLeft': + ['Légèrement à gauche', ' sur {road}'], + 'WaypointReached': + ['Point d\'étape atteint'], + 'Roundabout': + ['Au rond-point, prenez la {exitStr} sortie', ' sur {road}'], + 'DestinationReached': + ['Destination atteinte'], + }, + formatOrder: function(n) { + return n + 'º'; + }, + ui: { + startPlaceholder: 'Départ', + viaPlaceholder: 'Intermédiaire {viaNumber}', + endPlaceholder: 'Arrivée' + } + }, + 'it': { + directions: { + N: 'nord', + NE: 'nord-est', + E: 'est', + SE: 'sud-est', + S: 'sud', + SW: 'sud-ovest', + W: 'ovest', + NW: 'nord-ovest' + }, + instructions: { + // instruction, postfix if the road is named + 'Head': + ['Dritto verso {dir}', ' su {road}'], + 'Continue': + ['Continuare verso {dir}', ' su {road}'], + 'SlightRight': + ['Mantenere la destra', ' su {road}'], + 'Right': + ['A destra', ' su {road}'], + 'SharpRight': + ['Strettamente a destra', ' su {road}'], + 'TurnAround': + ['Fare inversione di marcia'], + 'SharpLeft': + ['Strettamente a sinistra', ' su {road}'], + 'Left': + ['A sinistra', ' sur {road}'], + 'SlightLeft': + ['Mantenere la sinistra', ' su {road}'], + 'WaypointReached': + ['Punto di passaggio raggiunto'], + 'Roundabout': + ['Alla rotonda, prendere la {exitStr} uscita'], + 'DestinationReached': + ['Destinazione raggiunta'], + }, + formatOrder: function(n) { + return n + 'º'; + }, + ui: { + startPlaceholder: 'Partenza', + viaPlaceholder: 'Intermedia {viaNumber}', + endPlaceholder: 'Destinazione' + } + }, + 'pt': { + directions: { + N: 'norte', + NE: 'nordeste', + E: 'leste', + SE: 'sudeste', + S: 'sul', + SW: 'sudoeste', + W: 'oeste', + NW: 'noroeste' + }, + instructions: { + // instruction, postfix if the road is named + 'Head': + ['Siga {dir}', ' na {road}'], + 'Continue': + ['Continue {dir}', ' na {road}'], + 'SlightRight': + ['Curva ligeira a direita', ' na {road}'], + 'Right': + ['Curva a direita', ' na {road}'], + 'SharpRight': + ['Curva fechada a direita', ' na {road}'], + 'TurnAround': + ['Retorne'], + 'SharpLeft': + ['Curva fechada a esquerda', ' na {road}'], + 'Left': + ['Curva a esquerda', ' na {road}'], + 'SlightLeft': + ['Curva ligueira a esquerda', ' na {road}'], + 'WaypointReached': + ['Ponto de interesse atingido'], + 'Roundabout': + ['Pegue a {exitStr} saída na rotatória', ' na {road}'], + 'DestinationReached': + ['Destino atingido'], + }, + formatOrder: function(n) { + return n + 'º'; + }, + ui: { + startPlaceholder: 'Origem', + viaPlaceholder: 'Intermédio {viaNumber}', + endPlaceholder: 'Destino' + } + }, + }; + + module.exports = L.Routing; +})(); + +},{}],12:[function(require,module,exports){ +(function (global){ +(function() { + 'use strict'; + + var L = (typeof window !== "undefined" ? window.L : typeof global !== "undefined" ? global.L : null), + corslite = require('corslite'), + polyline = require('polyline'); + + // Ignore camelcase naming for this file, since OSRM's API uses + // underscores. + /* jshint camelcase: false */ + + L.Routing = L.Routing || {}; + L.extend(L.Routing, require('./L.Routing.Waypoint')); L.Routing.OSRM = L.Class.extend({ options: { serviceUrl: '//router.project-osrm.org/viaroute', - geometryPrecision: 6 + timeout: 30 * 1000, + routingOptions: {} }, initialize: function(options) { @@ -201,77 +1933,202 @@ }; }, - route: function(waypoints, callback, context) { - var url = this._buildRouteUrl(waypoints); + route: function(waypoints, callback, context, options) { + var timedOut = false, + wps = [], + url, + timer, + wp, + i; - L.Routing._jsonp(url, function(data) { - this._routeDone(data, waypoints, callback, context); - }, this, 'jsonp'); + url = this.buildRouteUrl(waypoints, L.extend({}, this.options.routingOptions, options)); + + timer = setTimeout(function() { + timedOut = true; + callback.call(context || callback, { + status: -1, + message: 'OSRM request timed out.' + }); + }, this.options.timeout); + + // Create a copy of the waypoints, since they + // might otherwise be asynchronously modified while + // the request is being processed. + for (i = 0; i < waypoints.length; i++) { + wp = waypoints[i]; + wps.push(new L.Routing.Waypoint(wp.latLng, wp.name, wp.options)); + } + + corslite(url, L.bind(function(err, resp) { + var data, + errorMessage, + statusCode; + + clearTimeout(timer); + if (!timedOut) { + errorMessage = 'HTTP request failed: ' + err; + statusCode = -1; + + if (!err) { + try { + data = JSON.parse(resp.responseText); + try { + return this._routeDone(data, wps, callback, context); + } catch (ex) { + statusCode = -3; + errorMessage = ex.toString(); + } + } catch (ex) { + statusCode = -2; + errorMessage = 'Error parsing OSRM response: ' + ex.toString(); + } + } + + callback.call(context || callback, { + status: statusCode, + message: errorMessage + }); + } + }, this)); + + return this; }, - _routeDone: function(response, waypoints, callback, context) { + _routeDone: function(response, inputWaypoints, callback, context) { + var coordinates, + alts, + actualWaypoints, + i; + context = context || callback; - if (response.status !== 0) { + if (response.status !== 0 && response.status !== 200) { callback.call(context, { status: response.status, - message: response.message + message: response.status_message }); return; } - var alts = [{ - name: response.route_name.join(', '), - coordinates: this._decode(response.route_geometry, this.options.geometryPrecision), - instructions: this._convertInstructions(response.route_instructions), - summary: this._convertSummary(response.route_summary), - waypoints: response.via_points - }], - i; + coordinates = this._decodePolyline(response.route_geometry); + actualWaypoints = this._toWaypoints(inputWaypoints, response.via_points); + alts = [{ + name: this._createName(response.route_name), + coordinates: coordinates, + instructions: response.route_instructions ? this._convertInstructions(response.route_instructions) : [], + summary: response.route_summary ? this._convertSummary(response.route_summary) : [], + inputWaypoints: inputWaypoints, + waypoints: actualWaypoints, + waypointIndices: this._clampIndices(response.via_indices, coordinates) + }]; - if (response.alternative_geometries) { + if (response.alternative_geometries) { for (i = 0; i < response.alternative_geometries.length; i++) { + coordinates = this._decodePolyline(response.alternative_geometries[i]); alts.push({ - name: response.alternative_names[i].join(', '), - coordinates: this._decode(response.alternative_geometries[i], this.options.geometryPrecision), - instructions: this._convertInstructions(response.alternative_instructions[i]), - summary: this._convertSummary(response.alternative_summaries[i]), - waypoints: response.via_points + name: this._createName(response.alternative_names[i]), + coordinates: coordinates, + instructions: response.alternative_instructions[i] ? this._convertInstructions(response.alternative_instructions[i]) : [], + summary: response.alternative_summaries[i] ? this._convertSummary(response.alternative_summaries[i]) : [], + inputWaypoints: inputWaypoints, + waypoints: actualWaypoints, + waypointIndices: this._clampIndices(response.alternative_geometries.length === 1 ? + // Unsure if this is a bug in OSRM or not, but alternative_indices + // does not appear to be an array of arrays, at least not when there is + // a single alternative route. + response.alternative_indices : response.alternative_indices[i], + coordinates) }); } - } + } - this._saveHintData(response, waypoints); + // only versions <4.5.0 will support this flag + if (response.hint_data) { + this._saveHintData(response.hint_data, inputWaypoints); + } callback.call(context, null, alts); }, - _buildRouteUrl: function(waypoints) { + _decodePolyline: function(routeGeometry) { + var cs = polyline.decode(routeGeometry, 6), + result = new Array(cs.length), + i; + for (i = cs.length - 1; i >= 0; i--) { + result[i] = L.latLng(cs[i]); + } + + return result; + }, + + _toWaypoints: function(inputWaypoints, vias) { + var wps = [], + i; + for (i = 0; i < vias.length; i++) { + wps.push(L.Routing.waypoint(L.latLng(vias[i]), + inputWaypoints[i].name, + inputWaypoints[i].options)); + } + + return wps; + }, + + _createName: function(nameParts) { + var name = '', + i; + + for (i = 0; i < nameParts.length; i++) { + if (nameParts[i]) { + if (name) { + name += ', '; + } + name += nameParts[i].charAt(0).toUpperCase() + nameParts[i].slice(1); + } + } + + return name; + }, + + buildRouteUrl: function(waypoints, options) { var locs = [], + wp, + computeInstructions, + computeAlternative, locationKey, hint; for (var i = 0; i < waypoints.length; i++) { - locationKey = this._locationKey(waypoints[i].latLng); + wp = waypoints[i]; + locationKey = this._locationKey(wp.latLng); locs.push('loc=' + locationKey); hint = this._hints.locations[locationKey]; if (hint) { locs.push('hint=' + hint); } + + if (wp.options && wp.options.allowUTurn) { + locs.push('u=true'); + } } + computeAlternative = computeInstructions = + !(options && options.geometryOnly); + return this.options.serviceUrl + '?' + - 'instructions=true&' + + 'instructions=' + computeInstructions.toString() + '&' + + 'alt=' + computeAlternative.toString() + '&' + + (options.z ? 'z=' + options.z + '&' : '') + locs.join('&') + - (this._hints.checksum !== undefined ? '&checksum=' + this._hints.checksum : ''); + (this._hints.checksum !== undefined ? '&checksum=' + this._hints.checksum : '') + + (options.fileformat ? '&output=' + options.fileformat : '') + + (options.allowUTurns ? '&uturns=' + options.allowUTurns : ''); }, _locationKey: function(location) { return location.lat + ',' + location.lng; }, - _saveHintData: function(route, waypoints) { - var hintData = route.hint_data, - loc; + _saveHintData: function(hintData, waypoints) { + var loc; this._hints = { checksum: hintData.checksum, locations: {} @@ -282,43 +2139,6 @@ } }, - // Adapted from - // https://github.com/DennisSchiefer/Project-OSRM-Web/blob/develop/WebContent/routing/OSRM.RoutingGeometry.js - _decode: function(encoded, precision) { - var len = encoded.length, - index=0, - lat=0, - lng = 0, - array = []; - - precision = Math.pow(10, -precision); - - while (index < len) { - var b, - shift = 0, - 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; - //array.push( {lat: lat * precision, lng: lng * precision} ); - array.push( [lat * precision, lng * precision] ); - } - return array; - }, - _convertSummary: function(osrmSummary) { return { totalDistance: osrmSummary.total_distance, @@ -370,7 +2190,7 @@ case 7: return 'Left'; case 8: - return 'SlightRight'; + return 'SlightLeft'; case 9: return 'WaypointReached'; case 10: @@ -385,391 +2205,61 @@ default: return null; } + }, + + _clampIndices: function(indices, coords) { + var maxCoordIndex = coords.length - 1, + i; + for (i = 0; i < indices.length; i++) { + indices[i] = Math.min(maxCoordIndex, Math.max(indices[i], 0)); + } + return indices; } }); L.Routing.osrm = function(options) { return new L.Routing.OSRM(options); }; + + module.exports = L.Routing; })(); + +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{"./L.Routing.Waypoint":14,"corslite":1,"polyline":2}],13:[function(require,module,exports){ +(function (global){ (function() { 'use strict'; + var L = (typeof window !== "undefined" ? window.L : typeof global !== "undefined" ? global.L : null); L.Routing = L.Routing || {}; - - L.Routing.Line = L.Class.extend({ - includes: L.Mixin.Events, - - options: { - styles: [ - {color: 'black', opacity: 0.15, weight: 7}, - {color: 'white', opacity: 0.8, weight: 4}, - {color: 'orange', opacity: 1, weight: 2} - ], - addWaypoints: true - }, - - initialize: function(route, options) { - L.Util.setOptions(this, options); - this._route = route; - - this._wpIndices = this._findWaypointIndices(); - }, - - addTo: function(map) { - map.addLayer(this); - }, - - onAdd: function(map) { - var geom = this._route.coordinates, - i, - pl; - - this._map = map; - this._layers = []; - for (i = 0; i < this.options.styles.length; i++) { - pl = L.polyline(geom, this.options.styles[i]) - .addTo(map); - if (this.options.addWaypoints) { - pl.on('mousedown', this._onLineTouched, this); - } - this._layers.push(pl); - } - }, - - onRemove: function(map) { - var i; - for (i = 0; i < this._layers.length; i++) { - map.removeLayer(this._layers[i]); - } - - delete this._map; - }, - - getBounds: function() { - return L.latLngBounds(this._route.coordinates); - }, - - _findWaypointIndices: function() { - var wps = this._route.waypoints, - indices = [], - i; - for (i = 0; i < wps.length; i++) { - indices.push(this._findClosestRoutePoint(L.latLng(wps[i]))); - } - - return indices; - }, - - _findClosestRoutePoint: function(latlng) { - var minDist = Number.MAX_VALUE, - minIndex, - i, - d; - - for (i = this._route.coordinates.length - 1; i >= 0 ; i--) { - // TODO: maybe do this in pixel space instead? - d = latlng.distanceTo(this._route.coordinates[i]); - if (d < minDist) { - minIndex = i; - minDist = d; - } - } - - return minIndex; - }, - - _findNearestWpBefore: function(i) { - var j = this._wpIndices.length - 1; - while (j >= 0 && this._wpIndices[j] > i) { - j--; - } - - return j; - }, - - _onLineTouched: function(e) { - var afterIndex = this._findNearestWpBefore(this._findClosestRoutePoint(e.latlng)); - this.fire('linetouched', { - afterIndex: afterIndex, - latlng: e.latlng - }); - }, - }); - - L.Routing.line = function(route, options) { - return new L.Routing.Line(route, options); - }; -})(); -(function() { - 'use strict'; - - L.Routing = L.Routing || {}; - - L.Routing.Itinerary = L.Control.extend({ - includes: L.Mixin.Events, - - options: { - units: 'metric', - pointMarkerStyle: { - radius: 5, - color: '#03f', - fillColor: 'white', - opacity: 1, - fillOpacity: 0.7 - } - }, - - initialize: function(options) { - L.setOptions(this, options); - }, - - onAdd: function() { - this._container = L.DomUtil.create('div', 'leaflet-routing-container leaflet-bar'); - L.DomEvent.disableClickPropagation(this._container); - L.DomEvent.addListener(this._container, 'mousewheel', function(e) { - L.DomEvent.stopPropagation(e); - }); - return this._container; - }, - - onRemove: function() { - }, - - setAlternatives: function(routes) { - var i, - alt, - altDiv; - - this._clearAlts(); - - this._routes = routes; - - for (i = 0; i < this._routes.length; i++) { - alt = this._routes[i]; - altDiv = L.DomUtil.create('div', 'leaflet-routing-alt' + - (i > 0 ? ' leaflet-routing-alt-minimized' : ''), - this._container); - altDiv.innerHTML = '

' + alt.name + '

' + - '

' + this._formatDistance(alt.summary.totalDistance) + - ', ' + this._formatTime(alt.summary.totalTime) + '

'; - L.DomEvent.addListener(altDiv, 'click', this._onAltClicked, this); - - altDiv.appendChild(this._createItineraryTable(alt)); - this._altElements.push(altDiv); - } - - this.fire('routeselected', {route: this._routes[0]}); - }, - - _clearAlts: function() { - var i, - alt; - // TODO: this is really inelegant - for (i = 0; this._container && i < this._container.children.length; i++) { - alt = this._container.children[i]; - if (L.DomUtil.hasClass(alt, 'leaflet-routing-alt')) { - this._container.removeChild(alt); - i--; - } - } - - this._altElements = []; - }, - - _createItineraryTable: function(r) { - var table = L.DomUtil.create('table', ''), - body = L.DomUtil.create('tbody', '', table), - i, - instr, - row, - td; - - for (i = 0; i < r.instructions.length; i++) { - instr = r.instructions[i]; - row = L.DomUtil.create('tr', '', body); - td = L.DomUtil.create('td', '', row); - td.appendChild(document.createTextNode(this._instruction(instr, i))); - td = L.DomUtil.create('td', '', row); - td.appendChild(document.createTextNode(this._formatDistance(instr.distance))); - this._addRowListeners(row, r.coordinates[instr.index]); - } - - return table; - }, - - _addRowListeners: function(row, coordinate) { - var _this = this, - marker; - L.DomEvent.addListener(row, 'mouseover', function() { - marker = L.circleMarker(coordinate, - _this.options.pointMarkerStyle).addTo(_this._map); - }); - L.DomEvent.addListener(row, 'mouseout', function() { - if (marker) { - _this._map.removeLayer(marker); - marker = null; - } - }); - L.DomEvent.addListener(row, 'click', function(e) { - _this._map.panTo(coordinate); - L.DomEvent.stopPropagation(e); - }); - }, - - _onAltClicked: function(e) { - var altElem, - j, - n, - isCurrentSelection; - - altElem = e.target; - while (!L.DomUtil.hasClass(altElem, 'leaflet-routing-alt')) { - altElem = altElem.parentElement; - } - - if (L.DomUtil.hasClass(altElem, 'leaflet-routing-alt-minimized')) { - for (j = 0; j < this._altElements.length; j++) { - n = this._altElements[j]; - isCurrentSelection = altElem === n; - L.DomUtil[isCurrentSelection ? 'removeClass' : 'addClass'](n, 'leaflet-routing-alt-minimized'); - - if (isCurrentSelection) { - // TODO: don't fire if the currently active is clicked - this.fire('routeselected', {route: this._routes[j]}); - } - } - } - - L.DomEvent.stop(e); - }, - - _formatDistance: function(d /* Number (meters) */) { - var v; - - if (this.options.units === 'imperial') { - d = d / 1.609344; - if (d >= 1000) { - return (this._round(d) / 1000) + ' mi'; - } else { - return this._round(d / 1.760) + ' yd'; - } - } else { - v = this._round(d); - return v >= 1000 ? ((v / 1000) + ' km') : (v + ' m'); - } - }, - - _round: function(d) { - var pow10 = Math.pow(10, (Math.floor(d) + '').length - 1), - r = Math.floor(d / pow10 * 2), - p = r % 2 ? pow10 / 2 : pow10; - - return Math.round(d / p) * p; - }, - - _formatTime: function(t /* Number (seconds) */) { - if (t > 86400) { - return Math.round(t / 3600) + ' h'; - } else if (t > 3600) { - return Math.floor(t / 3600) + ' h ' + - Math.round((t % 3600) / 60) + ' min'; - } else if (t > 300) { - return Math.round(t / 60) + ' min'; - } else if (t > 60) { - return Math.floor(t / 60) + ' min ' + - (t % 60) + ' s'; - } else { - return t + ' s'; - } - }, - - _instruction: function(instr, i) { - if (instr.type !== undefined) { - return L.Util.template(this._getInstructionTemplate(instr, i), - L.extend({exit: this._formatOrder(instr.exit), dir: this._dir[instr.direction]}, - instr)); - } else { - return instr.text; - } - }, - - _getInstructionTemplate: function(instr, i) { - switch (instr.type) { - case 'Straight': - return (i === 0 ? 'Head' : 'Continue') + ' {dir}' + (instr.road ? ' on {road}' : ''); - case 'SlightRight': - return 'Slight right' + (instr.road ? ' onto {road}' : ''); - case 'Right': - return 'Right' + (instr.road ? ' onto {road}' : ''); - case 'SharpRight': - return 'Sharp right' + (instr.road ? ' onto {road}' : ''); - case 'TurnAround': - return 'Turn around'; - case 'SharpLeft': - return 'Sharp left' + (instr.road ? ' onto {road}' : ''); - case 'Left': - return 'Left' + (instr.road ? ' onto {road}' : ''); - case 'SlightLeft': - return 'Slight left' + (instr.road ? ' onto {road}' : ''); - case 'WaypointReached': - return 'Waypoint reached'; - case 'Roundabout': - return 'Take the {exit} exit in the roundabout'; - case 'DestinationReached': - return 'Destination reached'; - } - }, - - _formatOrder: function(n) { - var i = n % 10 - 1, - suffix = ['st', 'nd', 'rd']; - - return suffix[i] ? n + suffix[i] : n + 'th'; - }, - - _dir: { - N: 'north', - NE: 'northeast', - E: 'east', - SE: 'southeast', - S: 'south', - SW: 'southwest', - W: 'west', - NW: 'northwest' - } - }); - - L.Routing.Itinerary._instructions = { - }; - - L.Routing.itinerary = function(router) { - return new L.Routing.Itinerary(router); - }; -})(); -(function() { - 'use strict'; - - var Waypoint = L.Class.extend({ - initialize: function(latLng, name) { - this.latLng = latLng; - this.name = name; - } - }); - - L.Routing = L.Routing || {}; + L.extend(L.Routing, require('./L.Routing.GeocoderElement')); + L.extend(L.Routing, require('./L.Routing.Waypoint')); L.Routing.Plan = L.Class.extend({ includes: L.Mixin.Events, options: { dragStyles: [ - {color: 'black', opacity: 0.15, weight: 7}, - {color: 'white', opacity: 0.8, weight: 4}, - {color: 'orange', opacity: 1, weight: 2, dashArray: '7,12'} + {color: 'black', opacity: 0.15, weight: 9}, + {color: 'white', opacity: 0.8, weight: 6}, + {color: 'red', opacity: 1, weight: 2, dashArray: '7,12'} ], draggableWaypoints: true, - addWaypoints: true + routeWhileDragging: false, + addWaypoints: true, + reverseWaypoints: false, + addButtonClassName: '', + language: 'en', + createGeocoderElement: L.Routing.geocoderElement, + createMarker: function(i, wp) { + var options = { + draggable: this.draggableWaypoints + }, + marker = L.marker(wp.latLng, options); + + return marker; + }, + geocodersClassName: '' }, initialize: function(waypoints, options) { @@ -803,23 +2293,22 @@ setWaypoints: function(waypoints) { var args = [0, this._waypoints.length].concat(waypoints); this.spliceWaypoints.apply(this, args); + return this; }, spliceWaypoints: function() { var args = [arguments[0], arguments[1]], - i, - wp; + i; for (i = 2; i < arguments.length; i++) { - args.push(arguments[i] && arguments[i].latLng ? arguments[i] : new Waypoint(arguments[i])); + args.push(arguments[i] && arguments[i].hasOwnProperty('latLng') ? arguments[i] : L.Routing.waypoint(arguments[i])); } [].splice.apply(this._waypoints, args); + // Make sure there's always at least two waypoints while (this._waypoints.length < 2) { - wp = new Waypoint(); - this._waypoints.push(wp); - args.push(wp); + this.spliceWaypoints(this._waypoints.length, 0, null); } this._updateMarkers(); @@ -845,125 +2334,83 @@ }, createGeocoders: function() { - var container = L.DomUtil.create('div', 'leaflet-routing-geocoders'), + var container = L.DomUtil.create('div', 'leaflet-routing-geocoders ' + this.options.geocodersClassName), waypoints = this._waypoints, - i, - geocoderElem, - addWpBtn; + addWpBtn, + reverseBtn; this._geocoderContainer = container; this._geocoderElems = []; - for (i = 0; i < waypoints.length; i++) { - geocoderElem = this._createGeocoder(i); - container.appendChild(geocoderElem); - this._geocoderElems.push(geocoderElem); + + if (this.options.addWaypoints) { + addWpBtn = L.DomUtil.create('button', 'leaflet-routing-add-waypoint ' + this.options.addButtonClassName, container); + addWpBtn.setAttribute('type', 'button'); + L.DomEvent.addListener(addWpBtn, 'click', function() { + this.spliceWaypoints(waypoints.length, 0, null); + }, this); } - addWpBtn = L.DomUtil.create('button', '', container); - addWpBtn.setAttribute('type', 'button'); - addWpBtn.innerHTML = '+'; - L.DomEvent.addListener(addWpBtn, 'click', function() { - this.spliceWaypoints(waypoints.length, 0, null); - }, this); + if (this.options.reverseWaypoints) { + reverseBtn = L.DomUtil.create('button', 'leaflet-routing-reverse-waypoints', container); + reverseBtn.setAttribute('type', 'button'); + L.DomEvent.addListener(reverseBtn, 'click', function() { + this._waypoints.reverse(); + this.setWaypoints(this._waypoints); + }, this); + } + this._updateGeocoders(); this.on('waypointsspliced', this._updateGeocoders); return container; }, _createGeocoder: function(i) { - var geocoderElem; - - geocoderElem = L.DomUtil.create('input', ''); - geocoderElem.placeholder = this._geocoderPlaceholder(i); - - this._updateWaypointName(i); - - L.DomEvent.addListener(geocoderElem, 'click', function() { - this.select(); - }, geocoderElem); - - new L.Routing.Autocomplete(geocoderElem, function(r) { - geocoderElem.value = r.name; - this._waypoints[i].name = r.name; - this._waypoints[i].latLng = r.center; - this._updateMarkers(); - this._fireChanged(); - }, this, { - resultFn: this.options.geocoder.geocode, - resultContext: this.options.geocoder, - autocompleteFn: this.options.geocoder.suggest, - autocompleteContext: this.options.geocoder + var geocoder = this.options.createGeocoderElement(this._waypoints[i], i, this._waypoints.length, this.options); + geocoder + .on('delete', function() { + if (i > 0 || this._waypoints.length > 2) { + this.spliceWaypoints(i, 1); + } else { + this.spliceWaypoints(i, 1, new L.Routing.Waypoint()); + } + }, this) + .on('geocoded', function(e) { + this._updateMarkers(); + this._fireChanged(); + this._focusGeocoder(i + 1); + this.fire('waypointgeocoded', { + waypointIndex: i, + waypoint: e.waypoint }); + }, this) + .on('reversegeocoded', function(e) { + this.fire('waypointgeocoded', { + waypointIndex: i, + waypoint: e.waypoint + }); + }, this); - return geocoderElem; + return geocoder; }, - _updateGeocoders: function(e) { - var newElems = [], - i, - geocoderElem, - beforeElem; - for (i = e.added.length - 1; i >= 0 ; i--) { - geocoderElem = this._createGeocoder(e.index + i); - if (e.index >= this._geocoderElems.length) { - // lastChild is the "add new wp" button - beforeElem = this._geocoderContainer.lastChild; - } else { - beforeElem = this._geocoderElems[e.index]; - } - this._geocoderContainer.insertBefore(geocoderElem, beforeElem); - newElems.push(geocoderElem); - } - newElems.reverse(); - - for (i = e.index; i < e.index + e.nRemoved; i++) { - this._geocoderContainer.removeChild(this._geocoderElems[i]); - } - - newElems.splice(0, 0, e.index, e.nRemoved); - [].splice.apply(this._geocoderElems, newElems); + _updateGeocoders: function() { + var elems = [], + i, + geocoderElem; for (i = 0; i < this._geocoderElems.length; i++) { - this._geocoderElems[i].placeholder = this._geocoderPlaceholder(i); - } - }, - - _geocoderPlaceholder: function(i) { - return i === 0 ? - 'Start' : - (i < this._geocoderElems.length - 1 ? - 'Via ' + i : - 'End'); - }, - - _updateWaypointName: function(i, force) { - var wp = this._waypoints[i]; - - function updateGeocoder() { - var value = wp && wp.name ? wp.name : ''; - if (this._geocoderElems[i]) { - this._geocoderElems[i].value = value; - } + this._geocoderContainer.removeChild(this._geocoderElems[i].getContainer()); } - if (wp.latLng && (force || !wp.name)) { - if (this.options.geocoder && this.options.geocoder.reverse) { - this.options.geocoder.reverse(wp.latLng, 67108864 /* zoom 18 */, function(rs) { - if (rs.length > 0 && rs[0].center.distanceTo(wp.latLng) < 200) { - wp.name = rs[0].name; - } else { - wp.name = ''; - } - updateGeocoder.call(this); - }, this); - } else { - wp.name = ''; - } - - updateGeocoder.call(this); + for (i = this._waypoints.length - 1; i >= 0; i--) { + geocoderElem = this._createGeocoder(i); + this._geocoderContainer.insertBefore(geocoderElem.getContainer(), this._geocoderContainer.firstChild); + elems.push(geocoderElem); } + + this._geocoderElems = elems.reverse(); }, _removeMarkers: function() { @@ -980,8 +2427,6 @@ _updateMarkers: function() { var i, - icon, - options, m; if (!this._map) { @@ -992,19 +2437,12 @@ for (i = 0; i < this._waypoints.length; i++) { if (this._waypoints[i].latLng) { - icon = (typeof(this.options.waypointIcon) === 'function') ? - this.options.waypointIcon(i, this._waypoints[i].name, this._waypoints.length) : - this.options.waypointIcon; - options = { - draggable: true - }; - if (icon) { - options.icon = icon; - } - - m = L.marker(this._waypoints[i].latLng, options).addTo(this._map); - if (this.options.draggableWaypoints) { - this._hookWaypointEvents(m, i); + m = this.options.createMarker(i, this._waypoints[i], this._waypoints.length); + if (m) { + m.addTo(this._map); + if (this.options.draggableWaypoints) { + this._hookWaypointEvents(m, i); + } } } else { m = null; @@ -1025,183 +2463,146 @@ } }, - _hookWaypointEvents: function(m, i) { - m.on('dragstart', function(e) { - this.fire('waypointdragstart', this._createWaypointEvent(i, e)); - }, this); - m.on('drag', function(e) { - this.fire('waypointdrag', this._createWaypointEvent(i, e)); - }, this); - m.on('dragend', function(e) { - this.fire('waypointdragend', this._createWaypointEvent(i, e)); - this._waypoints[i].latLng = e.target.getLatLng(); - this._waypoints[i].name = ''; - this._updateWaypointName(i, true); - this._fireChanged(); - }, this); - }, + _hookWaypointEvents: function(m, i, trackMouseMove) { + var eventLatLng = function(e) { + return trackMouseMove ? e.latlng : e.target.getLatLng(); + }, + dragStart = L.bind(function(e) { + this.fire('waypointdragstart', {index: i, latlng: eventLatLng(e)}); + }, this), + drag = L.bind(function(e) { + this._waypoints[i].latLng = eventLatLng(e); + this.fire('waypointdrag', {index: i, latlng: eventLatLng(e)}); + }, this), + dragEnd = L.bind(function(e) { + this._waypoints[i].latLng = eventLatLng(e); + this._waypoints[i].name = ''; + if (this._geocoderElems) { + this._geocoderElems[i].update(true); + } + this.fire('waypointdragend', {index: i, latlng: eventLatLng(e)}); + this._fireChanged(); + }, this), + mouseMove, + mouseUp; - _createWaypointEvent: function(i, e) { - return {index: i, latlng: e.target.getLatLng()}; + if (trackMouseMove) { + mouseMove = L.bind(function(e) { + this._markers[i].setLatLng(e.latlng); + drag(e); + }, this); + mouseUp = L.bind(function(e) { + this._map.dragging.enable(); + this._map.off('mouseup', mouseUp); + this._map.off('mousemove', mouseMove); + dragEnd(e); + }, this); + this._map.dragging.disable(); + this._map.on('mousemove', mouseMove); + this._map.on('mouseup', mouseUp); + dragStart({latlng: this._waypoints[i].latLng}); + } else { + m.on('dragstart', dragStart); + m.on('drag', drag); + m.on('dragend', dragEnd); + } }, dragNewWaypoint: function(e) { - var i; - this._newWp = { - afterIndex: e.afterIndex, - marker: L.marker(e.latlng).addTo(this._map), - lines: [] - }; + var newWpIndex = e.afterIndex + 1; + if (this.options.routeWhileDragging) { + this.spliceWaypoints(newWpIndex, 0, e.latlng); + this._hookWaypointEvents(this._markers[newWpIndex], newWpIndex, true); + } else { + this._dragNewWaypoint(newWpIndex, e.latlng); + } + }, + + _dragNewWaypoint: function(newWpIndex, initialLatLng) { + var wp = new L.Routing.Waypoint(initialLatLng), + prevWp = this._waypoints[newWpIndex - 1], + nextWp = this._waypoints[newWpIndex], + marker = this.options.createMarker(newWpIndex, wp, this._waypoints.length + 1), + lines = [], + mouseMove = L.bind(function(e) { + var i; + if (marker) { + marker.setLatLng(e.latlng); + } + for (i = 0; i < lines.length; i++) { + lines[i].spliceLatLngs(1, 1, e.latlng); + } + }, this), + mouseUp = L.bind(function(e) { + var i; + if (marker) { + this._map.removeLayer(marker); + } + for (i = 0; i < lines.length; i++) { + this._map.removeLayer(lines[i]); + } + this._map.off('mousemove', mouseMove); + this._map.off('mouseup', mouseUp); + this.spliceWaypoints(newWpIndex, 0, e.latlng); + }, this), + i; + + if (marker) { + marker.addTo(this._map); + } for (i = 0; i < this.options.dragStyles.length; i++) { - this._newWp.lines.push(L.polyline([ - this._waypoints[e.afterIndex].latLng, - e.latlng, - this._waypoints[e.afterIndex + 1].latLng - ], this.options.dragStyles[i]).addTo(this._map)); + lines.push(L.polyline([prevWp.latLng, initialLatLng, nextWp.latLng], + this.options.dragStyles[i]).addTo(this._map)); } - this._markers.splice(e.afterIndex + 1, 0, this._newWp.marker); - this._map.on('mousemove', this._onDragNewWp, this); - this._map.on('mouseup', this._onWpRelease, this); + this._map.on('mousemove', mouseMove); + this._map.on('mouseup', mouseUp); }, - _onDragNewWp: function(e) { - var i; - this._newWp.marker.setLatLng(e.latlng); - for (i = 0; i < this._newWp.lines.length; i++) { - this._newWp.lines[i].spliceLatLngs(1, 1, e.latlng); + _focusGeocoder: function(i) { + if (this._geocoderElems[i]) { + this._geocoderElems[i].focus(); + } else { + document.activeElement.blur(); } - }, - - _onWpRelease: function(e) { - var i; - this._map.off('mouseup', this._onWpRelease, this); - this._map.off('mousemove', this._onDragNewWp, this); - for (i = 0; i < this._newWp.lines.length; i++) { - this._map.removeLayer(this._newWp.lines[i]); - } - this.spliceWaypoints(this._newWp.afterIndex + 1, 0, e.latlng); - delete this._newWp; } }); L.Routing.plan = function(waypoints, options) { return new L.Routing.Plan(waypoints, options); }; + + module.exports = L.Routing; })(); + +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{"./L.Routing.GeocoderElement":7,"./L.Routing.Waypoint":14}],14:[function(require,module,exports){ +(function (global){ (function() { 'use strict'; - L.Routing.Control = L.Routing.Itinerary.extend({ - options: { - fitSelectedRoutes: true - }, + var L = (typeof window !== "undefined" ? window.L : typeof global !== "undefined" ? global.L : null); + L.Routing = L.Routing || {}; - initialize: function(options) { - L.Util.setOptions(this, options); - - this._router = this.options.router || new L.Routing.OSRM(); - this._plan = this.options.plan || L.Routing.plan(undefined, { geocoder: this.options.geocoder }); - if (this.options.geocoder) { - this._plan.options.geocoder = this.options.geocoder; - } - if (this.options.waypoints) { - this._plan.setWaypoints(this.options.waypoints); + L.Routing.Waypoint = L.Class.extend({ + options: { + allowUTurn: false, + }, + initialize: function(latLng, name, options) { + L.Util.setOptions(this, options); + this.latLng = L.latLng(latLng); + this.name = name; } + }); - L.Routing.Itinerary.prototype.initialize.call(this, options); - - this.on('routeselected', this._routeSelected, this); - this._plan.on('waypointschanged', this._route, this); - - this._route(); - }, - - onAdd: function(map) { - var container = L.Routing.Itinerary.prototype.onAdd.call(this, map); - - this._map = map; - this._map.addLayer(this._plan); - - if (this.options.geocoder) { - container.insertBefore(this._plan.createGeocoders(), container.firstChild); - } - - return container; - }, - - onRemove: function(map) { - if (this._line) { - map.removeLayer(this._line); - } - map.removeLayer(this._plan); - return L.Routing.Itinerary.prototype.onRemove.call(this, map); - }, - - getWaypoints: function() { - return this._plan.getWaypoints(); - }, - - setWaypoints: function(waypoints) { - this._plan.setWaypoints(waypoints); - }, - - spliceWaypoints: function() { - var removed = this._plan.spliceWaypoints.apply(this._plan, arguments); - return removed; - }, - - getPlan: function() { - return this._plan; - }, - - _routeSelected: function(e) { - var route = e.route; - this._clearLine(); - - this._line = L.Routing.line(route, this.options.lineOptions); - this._line.addTo(this._map); - this._hookEvents(this._line); - - if (this.options.fitSelectedRoutes) { - this._map.fitBounds(this._line.getBounds()); - } - }, - - _hookEvents: function(l) { - l.on('linetouched', function(e) { - this._plan.dragNewWaypoint(e); - }, this); - }, - - _route: function() { - this._clearLine(); - this._clearAlts(); - if (this._plan.isReady()) { - this._router.route(this._plan.getWaypoints(), function(err, routes) { - if (err) { - console.log(err); - return; - } - this.setAlternatives(routes); - }, this); - } - }, - - _clearLine: function() { - if (this._line) { - this._map.removeLayer(this._line); - delete this._line; - } - } - }); - - L.Routing.control = function(options) { - return new L.Routing.Control(options); + L.Routing.waypoint = function(latLng, name, options) { + return new L.Routing.Waypoint(latLng, name, options); }; + + module.exports = L.Routing; })(); - return L.Routing; -})); -// Packaging/modules magic dance end. This code is inserted after all other -// code when the dist is built. + +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{}]},{},[4])(4) +}); \ No newline at end of file