diff --git a/API.md b/API.md index b9db3a37..b37ce714 100644 --- a/API.md +++ b/API.md @@ -33,7 +33,8 @@ The Directions control - `options.congestion` **[Boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** Whether to enable congestion along the route line. (optional, default `false`) - `options.unit` **[String](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** Measurement system to be used in navigation instructions. Options: `imperial`, `metric` (optional, default `"imperial"`) - `options.compile` **[Function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function)** Provide a custom function for generating instruction, compatible with osrm-text-instructions. (optional, default `null`) - - `options.geocoder` **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)?** Accepts an object containing the query parameters as [documented here](https://www.mapbox.com/api-documentation/#search-for-places). + - `options.geocoder` **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)?** Accepts an object containing the query parameters as [documented here](https://docs.mapbox.com/api/search/#forward-geocoding) in addition to the following: + - `options.geocoder.localGeocoder` **[Function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function)** A function accepting the query string which performs local geocoding to supplement results from the Mapbox Geocoding API. Expected to return an Array of GeoJSON Features in the [Carmen GeoJSON](https://github.com/mapbox/carmen/blob/master/carmen-geojson.md) format. - `options.controls` **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)?** - `options.controls.inputs` **[Boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** Hide or display the inputs control. (optional, default `true`) - `options.controls.instructions` **[Boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** Hide or display the instructions control. (optional, default `true`) diff --git a/example/index.js b/example/index.js index c0d5362a..3f2482a1 100644 --- a/example/index.js +++ b/example/index.js @@ -31,11 +31,53 @@ removeWaypointsButton.style = 'z-index:10;position:absolute;top:30px;right:10px; removeWaypointsButton.textContent = 'Remove all waypoints'; // directions +var coordinatesGeocoder = function (query) { + var matches = query.match(/^[ ]*(-?\d+\.?\d*)[, ]+(-?\d+\.?\d*)[ ]*$/); + if (!matches) { + return null; + } + function coordinateFeature(lng, lat) { + var lng = Number(lng); + var lat = Number(lat); + return { + center: [lng, lat], + geometry: { + type: "Point", + coordinates: [lng, lat] + }, + place_name: 'Lat: ' + lat + ', Lng: ' + lng, + place_type: ['coordinate'], + properties: {}, + type: 'Feature' + }; + } + var coord1 = matches[1]; + var coord2 = matches[2]; + var geocodes = []; + if (coord1 < -90 || coord1 > 90) { + // must be lng, lat + geocodes.push(coordinateFeature(coord1, coord2)); + } + if (coord2 < -90 || coord2 > 90) { + // must be lat, lng + geocodes.push(coordinateFeature(coord2, coord1)); + } + if (geocodes.length == 0) { + // else could be either + geocodes.push(coordinateFeature(coord1, coord2)); + geocodes.push(coordinateFeature(coord2, coord1)); + } + console.log(geocodes); + return geocodes; +}; var MapboxDirections = require('../src/index'); var directions = new MapboxDirections({ accessToken: window.localStorage.getItem('MapboxAccessToken'), unit: 'metric', - profile: 'mapbox/cycling' + profile: 'mapbox/cycling', + geocoder: { + localGeocoder: coordinatesGeocoder + } }); window.directions = directions; diff --git a/src/controls/geocoder.js b/src/controls/geocoder.js index de593e54..57b481d5 100644 --- a/src/controls/geocoder.js +++ b/src/controls/geocoder.js @@ -92,8 +92,16 @@ export default class Geocoder { this._loadingEl.classList.add('active'); this.fire('loading'); + var localGeocoderRes = []; + if (this.options.localGeocoder) { + localGeocoderRes = this.options.localGeocoder(q); + if (!localGeocoderRes) { + localGeocoderRes = []; + } + } + const geocodingOptions = this.options - const exclude = ['placeholder', 'zoom', 'flyTo', 'accessToken', 'api']; + const exclude = ['placeholder', 'zoom', 'flyTo', 'accessToken', 'api', 'localGeocoder']; const options = Object.keys(this.options).filter(function(key) { return exclude.indexOf(key) === -1; }).map(function(key) { @@ -108,6 +116,10 @@ export default class Geocoder { this._loadingEl.classList.remove('active'); if (this.request.status >= 200 && this.request.status < 400) { var data = JSON.parse(this.request.responseText); + + // supplement Mapbox Geocoding API results with locally populated results + data.features = data.features ? localGeocoderRes.concat(data.features) : localGeocoderRes; + if (data.features.length) { this._clearEl.classList.add('active'); } else { @@ -119,13 +131,40 @@ export default class Geocoder { this._typeahead.update(data.features); return callback(data.features); } else { + // in the event of an error in the Mapbox Geocoding API still display results from the localGeocoder + if (localGeocoderRes.length) { + this._clearEl.classList.add('active'); + } else { + this._clearEl.classList.remove('active'); + this._typeahead.selected = null; + } + + this.fire('results', { results: localGeocoderRes }); + this._typeahead.update(localGeocoderRes); + this.fire('error', { error: JSON.parse(this.request.responseText).message }); + + return callback(localGeocoderRes); } }.bind(this); this.request.onerror = function() { this._loadingEl.classList.remove('active'); + + // in the event of an error in the Mapbox Geocoding API still display results from the localGeocoder + if (localGeocoderRes.length) { + this._clearEl.classList.add('active'); + } else { + this._clearEl.classList.remove('active'); + this._typeahead.selected = null; + } + + this.fire('results', { results: localGeocoderRes }); + this._typeahead.update(localGeocoderRes); + this.fire('error', { error: JSON.parse(this.request.responseText).message }); + + return callback(localGeocoderRes); }.bind(this); this.request.send(); @@ -237,6 +276,12 @@ export default class Geocoder { }); return this; } + + once(type, fn) { + this._ev.once(type, fn); + return this; + } + /** * Fire an event * @param {String} type event name. diff --git a/src/directions.js b/src/directions.js index bc21f57f..719c58f7 100644 --- a/src/directions.js +++ b/src/directions.js @@ -29,7 +29,8 @@ import Instructions from './controls/instructions'; * @param {Boolean} [options.congestion=false] Whether to enable congestion along the route line. * @param {String} [options.unit="imperial"] Measurement system to be used in navigation instructions. Options: `imperial`, `metric` * @param {Function} [options.compile=null] Provide a custom function for generating instruction, compatible with osrm-text-instructions. - * @param {Object} [options.geocoder] Accepts an object containing the query parameters as [documented here](https://www.mapbox.com/api-documentation/#search-for-places). + * @param {Object} [options.geocoder] Accepts an object containing the query parameters as [documented here](https://docs.mapbox.com/api/search/#forward-geocoding). + * @param {Function} [options.gocoder.localGeocoder] A function accepting the query string which performs local geocoding to supplement results from the Mapbox Geocoding API. Expected to return an Array of GeoJSON Features in the [Carmen GeoJSON](https://github.com/mapbox/carmen/blob/master/carmen-geojson.md) format. * @param {Object} [options.controls] * @param {Boolean} [options.controls.inputs=true] Hide or display the inputs control. * @param {Boolean} [options.controls.instructions=true] Hide or display the instructions control. diff --git a/test/test.geocoder.js b/test/test.geocoder.js index 0305ddea..2e97f11d 100644 --- a/test/test.geocoder.js +++ b/test/test.geocoder.js @@ -22,6 +22,45 @@ test('Geocoder#constructor', t =>{ t.end(); }); + t.test('options.localGeocoder', function(t) { + //t.plan(3); + const geocoder = new Geocoder({ + flyTo: false, + limit: 6, + localGeocoder: function(q) { + return [{place_name: q}]; + } + }); + + geocoder.onAdd(); + new Promise((resolve, reject) => { + geocoder.query('-30,150'); + geocoder.once('results', function(e) { + t.equal(e.results.length, 1, 'Local geocoder used'); + resolve(); + }); + geocoder.once('error', function(e) {}); + }).then(function(result) { + return new Promise((resolve, reject) => { + geocoder.query('London'); + geocoder.once('results', function(e) { + t.equal(e.results.length, 7, 'Local geocoder supplement remote response'); + resolve(); + }); + }); + }).then(function(result) { + return new Promise((resolve, reject) => { + geocoder.query('London'); + geocoder.once('results', function(e) { + t.equal(e.results[0].place_name, 'London', 'Local geocoder results above remote response'); + resolve(); + }); + }); + }).then(function(result) { + t.end(); + }); + }); + // TODO test to confirm the query parameters actually get passed. /* t.test('query parameters passed are added to the request', t =>{