UNPKG

44.2 kB JavaScript View Raw
1/* @preserve
2 * Leaflet Control Geocoder 1.7.0
3 * https://github.com/perliedman/leaflet-control-geocoder
4 *
5 * Copyright (c) 2012 sa3m (https://github.com/sa3m)
6 * Copyright (c) 2018 Per Liedman
7 * All rights reserved.
8 */
9
10this.L = this.L || {};
11this.L.Control = this.L.Control || {};
12this.L.Control.Geocoder = (function (L) {
13 'use strict';
14
15 L = L && L.hasOwnProperty('default') ? L['default'] : L;
16
17 var lastCallbackId = 0;
18
19 // Adapted from handlebars.js
20 // https://github.com/wycats/handlebars.js/
21 var badChars = /[&<>"'`]/g;
22 var possible = /[&<>"'`]/;
23 var escape = {
24 '&': '&amp;',
25 '<': '&lt;',
26 '>': '&gt;',
27 '"': '&quot;',
28 "'": '&#x27;',
29 '`': '&#x60;'
30 };
31
32 function escapeChar(chr) {
33 return escape[chr];
34 }
35
36 function htmlEscape(string) {
37 if (string == null) {
38 return '';
39 } else if (!string) {
40 return string + '';
41 }
42
43 // Force a string conversion as this will be done by the append regardless and
44 // the regex test will do this transparently behind the scenes, causing issues if
45 // an object's to string has escaped characters in it.
46 string = '' + string;
47
48 if (!possible.test(string)) {
49 return string;
50 }
51 return string.replace(badChars, escapeChar);
52 }
53
54 function jsonp(url, params, callback, context, jsonpParam) {
55 var callbackId = '_l_geocoder_' + lastCallbackId++;
56 params[jsonpParam || 'callback'] = callbackId;
57 window[callbackId] = L.Util.bind(callback, context);
58 var script = document.createElement('script');
59 script.type = 'text/javascript';
60 script.src = url + getParamString(params);
61 script.id = callbackId;
62 document.getElementsByTagName('head')[0].appendChild(script);
63 }
64
65 function getJSON(url, params, callback) {
66 var xmlHttp = new XMLHttpRequest();
67 xmlHttp.onreadystatechange = function() {
68 if (xmlHttp.readyState !== 4) {
69 return;
70 }
71 if (xmlHttp.status !== 200 && xmlHttp.status !== 304) {
72 callback('');
73 return;
74 }
75 callback(JSON.parse(xmlHttp.response));
76 };
77 xmlHttp.open('GET', url + getParamString(params), true);
78 xmlHttp.setRequestHeader('Accept', 'application/json');
79 xmlHttp.send(null);
80 }
81
82 function template(str, data) {
83 return str.replace(/\{ *([\w_]+) *\}/g, function(str, key) {
84 var value = data[key];
85 if (value === undefined) {
86 value = '';
87 } else if (typeof value === 'function') {
88 value = value(data);
89 }
90 return htmlEscape(value);
91 });
92 }
93
94 function getParamString(obj, existingUrl, uppercase) {
95 var params = [];
96 for (var i in obj) {
97 var key = encodeURIComponent(uppercase ? i.toUpperCase() : i);
98 var value = obj[i];
99 if (!L.Util.isArray(value)) {
100 params.push(key + '=' + encodeURIComponent(value));
101 } else {
102 for (var j = 0; j < value.length; j++) {
103 params.push(key + '=' + encodeURIComponent(value[j]));
104 }
105 }
106 }
107 return (!existingUrl || existingUrl.indexOf('?') === -1 ? '?' : '&') + params.join('&');
108 }
109
110 var Nominatim = {
111 class: L.Class.extend({
112 options: {
113 serviceUrl: 'https://nominatim.openstreetmap.org/',
114 geocodingQueryParams: {},
115 reverseQueryParams: {},
116 htmlTemplate: function(r) {
117 var a = r.address,
118 parts = [];
119 if (a.road || a.building) {
120 parts.push('{building} {road} {house_number}');
121 }
122
123 if (a.city || a.town || a.village || a.hamlet) {
124 parts.push(
125 '<span class="' +
126 (parts.length > 0 ? 'leaflet-control-geocoder-address-detail' : '') +
127 '">{postcode} {city} {town} {village} {hamlet}</span>'
128 );
129 }
130
131 if (a.state || a.country) {
132 parts.push(
133 '<span class="' +
134 (parts.length > 0 ? 'leaflet-control-geocoder-address-context' : '') +
135 '">{state} {country}</span>'
136 );
137 }
138
139 return template(parts.join('<br/>'), a, true);
140 }
141 },
142
143 initialize: function(options) {
144 L.Util.setOptions(this, options);
145 },
146
147 geocode: function(query, cb, context) {
148 getJSON(
149 this.options.serviceUrl + 'search',
150 L.extend(
151 {
152 q: query,
153 limit: 5,
154 format: 'json',
155 addressdetails: 1
156 },
157 this.options.geocodingQueryParams
158 ),
159 L.bind(function(data) {
160 var results = [];
161 for (var i = data.length - 1; i >= 0; i--) {
162 var bbox = data[i].boundingbox;
163 for (var j = 0; j < 4; j++) bbox[j] = parseFloat(bbox[j]);
164 results[i] = {
165 icon: data[i].icon,
166 name: data[i].display_name,
167 html: this.options.htmlTemplate ? this.options.htmlTemplate(data[i]) : undefined,
168 bbox: L.latLngBounds([bbox[0], bbox[2]], [bbox[1], bbox[3]]),
169 center: L.latLng(data[i].lat, data[i].lon),
170 properties: data[i]
171 };
172 }
173 cb.call(context, results);
174 }, this)
175 );
176 },
177
178 reverse: function(location, scale, cb, context) {
179 getJSON(
180 this.options.serviceUrl + 'reverse',
181 L.extend(
182 {
183 lat: location.lat,
184 lon: location.lng,
185 zoom: Math.round(Math.log(scale / 256) / Math.log(2)),
186 addressdetails: 1,
187 format: 'json'
188 },
189 this.options.reverseQueryParams
190 ),
191 L.bind(function(data) {
192 var result = [],
193 loc;
194
195 if (data && data.lat && data.lon) {
196 loc = L.latLng(data.lat, data.lon);
197 result.push({
198 name: data.display_name,
199 html: this.options.htmlTemplate ? this.options.htmlTemplate(data) : undefined,
200 center: loc,
201 bounds: L.latLngBounds(loc, loc),
202 properties: data
203 });
204 }
205
206 cb.call(context, result);
207 }, this)
208 );
209 }
210 }),
211
212 factory: function(options) {
213 return new L.Control.Geocoder.Nominatim(options);
214 }
215 };
216
217 var Control = {
218 class: L.Control.extend({
219 options: {
220 showResultIcons: false,
221 collapsed: true,
222 expand: 'touch', // options: touch, click, anythingelse
223 position: 'topright',
224 placeholder: 'Search...',
225 errorMessage: 'Nothing found.',
226 queryMinLength: 1,
227 suggestMinLength: 3,
228 suggestTimeout: 250,
229 defaultMarkGeocode: true
230 },
231
232 includes: L.Evented.prototype || L.Mixin.Events,
233
234 initialize: function(options) {
235 L.Util.setOptions(this, options);
236 if (!this.options.geocoder) {
237 this.options.geocoder = new Nominatim.class();
238 }
239
240 this._requestCount = 0;
241 },
242
243 onAdd: function(map) {
244 var className = 'leaflet-control-geocoder',
245 container = L.DomUtil.create('div', className + ' leaflet-bar'),
246 icon = L.DomUtil.create('button', className + '-icon', container),
247 form = (this._form = L.DomUtil.create('div', className + '-form', container)),
248 input;
249
250 this._map = map;
251 this._container = container;
252
253 icon.innerHTML = '&nbsp;';
254 icon.type = 'button';
255
256 input = this._input = L.DomUtil.create('input', '', form);
257 input.type = 'text';
258 input.placeholder = this.options.placeholder;
259 L.DomEvent.disableClickPropagation(input);
260
261 this._errorElement = L.DomUtil.create('div', className + '-form-no-error', container);
262 this._errorElement.innerHTML = this.options.errorMessage;
263
264 this._alts = L.DomUtil.create(
265 'ul',
266 className + '-alternatives leaflet-control-geocoder-alternatives-minimized',
267 container
268 );
269 L.DomEvent.disableClickPropagation(this._alts);
270
271 L.DomEvent.addListener(input, 'keydown', this._keydown, this);
272 if (this.options.geocoder.suggest) {
273 L.DomEvent.addListener(input, 'input', this._change, this);
274 }
275 L.DomEvent.addListener(
276 input,
277 'blur',
278 function() {
279 if (this.options.collapsed && !this._preventBlurCollapse) {
280 this._collapse();
281 }
282 this._preventBlurCollapse = false;
283 },
284 this
285 );
286
287 if (this.options.collapsed) {
288 if (this.options.expand === 'click') {
289 L.DomEvent.addListener(
290 container,
291 'click',
292 function(e) {
293 if (e.button === 0 && e.detail !== 2) {
294 this._toggle();
295 }
296 },
297 this
298 );
299 } else if (L.Browser.touch && this.options.expand === 'touch') {
300 L.DomEvent.addListener(
301 container,
302 'touchstart mousedown',
303 function(e) {
304 this._toggle();
305 e.preventDefault(); // mobile: clicking focuses the icon, so UI expands and immediately collapses
306 e.stopPropagation();
307 },
308 this
309 );
310 } else {
311 L.DomEvent.addListener(container, 'mouseover', this._expand, this);
312 L.DomEvent.addListener(container, 'mouseout', this._collapse, this);
313 this._map.on('movestart', this._collapse, this);
314 }
315 } else {
316 this._expand();
317 if (L.Browser.touch) {
318 L.DomEvent.addListener(
319 container,
320 'touchstart',
321 function() {
322 this._geocode();
323 },
324 this
325 );
326 } else {
327 L.DomEvent.addListener(
328 container,
329 'click',
330 function() {
331 this._geocode();
332 },
333 this
334 );
335 }
336 }
337
338 if (this.options.defaultMarkGeocode) {
339 this.on('markgeocode', this.markGeocode, this);
340 }
341
342 this.on(
343 'startgeocode',
344 function() {
345 L.DomUtil.addClass(this._container, 'leaflet-control-geocoder-throbber');
346 },
347 this
348 );
349 this.on(
350 'finishgeocode',
351 function() {
352 L.DomUtil.removeClass(this._container, 'leaflet-control-geocoder-throbber');
353 },
354 this
355 );
356
357 L.DomEvent.disableClickPropagation(container);
358
359 return container;
360 },
361
362 _geocodeResult: function(results, suggest) {
363 if (!suggest && results.length === 1) {
364 this._geocodeResultSelected(results[0]);
365 } else if (results.length > 0) {
366 this._alts.innerHTML = '';
367 this._results = results;
368 L.DomUtil.removeClass(this._alts, 'leaflet-control-geocoder-alternatives-minimized');
369 for (var i = 0; i < results.length; i++) {
370 this._alts.appendChild(this._createAlt(results[i], i));
371 }
372 } else {
373 L.DomUtil.addClass(this._errorElement, 'leaflet-control-geocoder-error');
374 }
375 },
376
377 markGeocode: function(result) {
378 result = result.geocode || result;
379
380 this._map.fitBounds(result.bbox);
381
382 if (this._geocodeMarker) {
383 this._map.removeLayer(this._geocodeMarker);
384 }
385
386 this._geocodeMarker = new L.Marker(result.center)
387 .bindPopup(result.html || result.name)
388 .addTo(this._map)
389 .openPopup();
390
391 return this;
392 },
393
394 _geocode: function(suggest) {
395 var value = this._input.value;
396 if (!suggest && value.length < this.options.queryMinLength) {
397 return;
398 }
399
400 var requestCount = ++this._requestCount,
401 mode = suggest ? 'suggest' : 'geocode',
402 eventData = { input: value };
403
404 this._lastGeocode = value;
405 if (!suggest) {
406 this._clearResults();
407 }
408
409 this.fire('start' + mode, eventData);
410 this.options.geocoder[mode](
411 value,
412 function(results) {
413 if (requestCount === this._requestCount) {
414 eventData.results = results;
415 this.fire('finish' + mode, eventData);
416 this._geocodeResult(results, suggest);
417 }
418 },
419 this
420 );
421 },
422
423 _geocodeResultSelected: function(result) {
424 this.fire('markgeocode', { geocode: result });
425 },
426
427 _toggle: function() {
428 if (L.DomUtil.hasClass(this._container, 'leaflet-control-geocoder-expanded')) {
429 this._collapse();
430 } else {
431 this._expand();
432 }
433 },
434
435 _expand: function() {
436 L.DomUtil.addClass(this._container, 'leaflet-control-geocoder-expanded');
437 this._input.select();
438 this.fire('expand');
439 },
440
441 _collapse: function() {
442 L.DomUtil.removeClass(this._container, 'leaflet-control-geocoder-expanded');
443 L.DomUtil.addClass(this._alts, 'leaflet-control-geocoder-alternatives-minimized');
444 L.DomUtil.removeClass(this._errorElement, 'leaflet-control-geocoder-error');
445 this._input.blur(); // mobile: keyboard shouldn't stay expanded
446 this.fire('collapse');
447 },
448
449 _clearResults: function() {
450 L.DomUtil.addClass(this._alts, 'leaflet-control-geocoder-alternatives-minimized');
451 this._selection = null;
452 L.DomUtil.removeClass(this._errorElement, 'leaflet-control-geocoder-error');
453 },
454
455 _createAlt: function(result, index) {
456 var li = L.DomUtil.create('li', ''),
457 a = L.DomUtil.create('a', '', li),
458 icon = this.options.showResultIcons && result.icon ? L.DomUtil.create('img', '', a) : null,
459 text = result.html ? undefined : document.createTextNode(result.name),
460 mouseDownHandler = function mouseDownHandler(e) {
461 // In some browsers, a click will fire on the map if the control is
462 // collapsed directly after mousedown. To work around this, we
463 // wait until the click is completed, and _then_ collapse the
464 // control. Messy, but this is the workaround I could come up with
465 // for #142.
466 this._preventBlurCollapse = true;
467 L.DomEvent.stop(e);
468 this._geocodeResultSelected(result);
469 L.DomEvent.on(
470 li,
471 'click',
472 function() {
473 if (this.options.collapsed) {
474 this._collapse();
475 } else {
476 this._clearResults();
477 }
478 },
479 this
480 );
481 };
482
483 if (icon) {
484 icon.src = result.icon;
485 }
486
487 li.setAttribute('data-result-index', index);
488
489 if (result.html) {
490 a.innerHTML = a.innerHTML + result.html;
491 } else {
492 a.appendChild(text);
493 }
494
495 // Use mousedown and not click, since click will fire _after_ blur,
496 // causing the control to have collapsed and removed the items
497 // before the click can fire.
498 L.DomEvent.addListener(li, 'mousedown touchstart', mouseDownHandler, this);
499
500 return li;
501 },
502
503 _keydown: function(e) {
504 var _this = this,
505 select = function select(dir) {
506 if (_this._selection) {
507 L.DomUtil.removeClass(_this._selection, 'leaflet-control-geocoder-selected');
508 _this._selection = _this._selection[dir > 0 ? 'nextSibling' : 'previousSibling'];
509 }
510 if (!_this._selection) {
511 _this._selection = _this._alts[dir > 0 ? 'firstChild' : 'lastChild'];
512 }
513
514 if (_this._selection) {
515 L.DomUtil.addClass(_this._selection, 'leaflet-control-geocoder-selected');
516 }
517 };
518
519 switch (e.keyCode) {
520 // Escape
521 case 27:
522 if (this.options.collapsed) {
523 this._collapse();
524 }
525 break;
526 // Up
527 case 38:
528 select(-1);
529 break;
530 // Up
531 case 40:
532 select(1);
533 break;
534 // Enter
535 case 13:
536 if (this._selection) {
537 var index = parseInt(this._selection.getAttribute('data-result-index'), 10);
538 this._geocodeResultSelected(this._results[index]);
539 this._clearResults();
540 } else {
541 this._geocode();
542 }
543 break;
544 default:
545 return;
546 }
547
548 L.DomEvent.preventDefault(e);
549 },
550 _change: function() {
551 var v = this._input.value;
552 if (v !== this._lastGeocode) {
553 clearTimeout(this._suggestTimeout);
554 if (v.length >= this.options.suggestMinLength) {
555 this._suggestTimeout = setTimeout(
556 L.bind(function() {
557 this._geocode(true);
558 }, this),
559 this.options.suggestTimeout
560 );
561 } else {
562 this._clearResults();
563 }
564 }
565 }
566 }),
567 factory: function(options) {
568 return new L.Control.Geocoder(options);
569 }
570 };
571
572 var Bing = {
573 class: L.Class.extend({
574 initialize: function(key) {
575 this.key = key;
576 },
577
578 geocode: function(query, cb, context) {
579 jsonp(
580 'https://dev.virtualearth.net/REST/v1/Locations',
581 {
582 query: query,
583 key: this.key
584 },
585 function(data) {
586 var results = [];
587 if (data.resourceSets.length > 0) {
588 for (var i = data.resourceSets[0].resources.length - 1; i >= 0; i--) {
589 var resource = data.resourceSets[0].resources[i],
590 bbox = resource.bbox;
591 results[i] = {
592 name: resource.name,
593 bbox: L.latLngBounds([bbox[0], bbox[1]], [bbox[2], bbox[3]]),
594 center: L.latLng(resource.point.coordinates)
595 };
596 }
597 }
598 cb.call(context, results);
599 },
600 this,
601 'jsonp'
602 );
603 },
604
605 reverse: function(location, scale, cb, context) {
606 jsonp(
607 '//dev.virtualearth.net/REST/v1/Locations/' + location.lat + ',' + location.lng,
608 {
609 key: this.key
610 },
611 function(data) {
612 var results = [];
613 for (var i = data.resourceSets[0].resources.length - 1; i >= 0; i--) {
614 var resource = data.resourceSets[0].resources[i],
615 bbox = resource.bbox;
616 results[i] = {
617 name: resource.name,
618 bbox: L.latLngBounds([bbox[0], bbox[1]], [bbox[2], bbox[3]]),
619 center: L.latLng(resource.point.coordinates)
620 };
621 }
622 cb.call(context, results);
623 },
624 this,
625 'jsonp'
626 );
627 }
628 }),
629
630 factory: function(key) {
631 return new L.Control.Geocoder.Bing(key);
632 }
633 };
634
635 var MapQuest = {
636 class: L.Class.extend({
637 options: {
638 serviceUrl: 'https://www.mapquestapi.com/geocoding/v1'
639 },
640
641 initialize: function(key, options) {
642 // MapQuest seems to provide URI encoded API keys,
643 // so to avoid encoding them twice, we decode them here
644 this._key = decodeURIComponent(key);
645
646 L.Util.setOptions(this, options);
647 },
648
649 _formatName: function() {
650 var r = [],
651 i;
652 for (i = 0; i < arguments.length; i++) {
653 if (arguments[i]) {
654 r.push(arguments[i]);
655 }
656 }
657
658 return r.join(', ');
659 },
660
661 geocode: function(query, cb, context) {
662 getJSON(
663 this.options.serviceUrl + '/address',
664 {
665 key: this._key,
666 location: query,
667 limit: 5,
668 outFormat: 'json'
669 },
670 L.bind(function(data) {
671 var results = [],
672 loc,
673 latLng;
674 if (data.results && data.results[0].locations) {
675 for (var i = data.results[0].locations.length - 1; i >= 0; i--) {
676 loc = data.results[0].locations[i];
677 latLng = L.latLng(loc.latLng);
678 results[i] = {
679 name: this._formatName(loc.street, loc.adminArea4, loc.adminArea3, loc.adminArea1),
680 bbox: L.latLngBounds(latLng, latLng),
681 center: latLng
682 };
683 }
684 }
685
686 cb.call(context, results);
687 }, this)
688 );
689 },
690
691 reverse: function(location, scale, cb, context) {
692 getJSON(
693 this.options.serviceUrl + '/reverse',
694 {
695 key: this._key,
696 location: location.lat + ',' + location.lng,
697 outputFormat: 'json'
698 },
699 L.bind(function(data) {
700 var results = [],
701 loc,
702 latLng;
703 if (data.results && data.results[0].locations) {
704 for (var i = data.results[0].locations.length - 1; i >= 0; i--) {
705 loc = data.results[0].locations[i];
706 latLng = L.latLng(loc.latLng);
707 results[i] = {
708 name: this._formatName(loc.street, loc.adminArea4, loc.adminArea3, loc.adminArea1),
709 bbox: L.latLngBounds(latLng, latLng),
710 center: latLng
711 };
712 }
713 }
714
715 cb.call(context, results);
716 }, this)
717 );
718 }
719 }),
720
721 factory: function(key, options) {
722 return new L.Control.Geocoder.MapQuest(key, options);
723 }
724 };
725
726 var Mapbox = {
727 class: L.Class.extend({
728 options: {
729 serviceUrl: 'https://api.mapbox.com/geocoding/v5/mapbox.places/',
730 geocodingQueryParams: {},
731 reverseQueryParams: {}
732 },
733
734 initialize: function(accessToken, options) {
735 L.setOptions(this, options);
736 this.options.geocodingQueryParams.access_token = accessToken;
737 this.options.reverseQueryParams.access_token = accessToken;
738 },
739
740 geocode: function(query, cb, context) {
741 var params = this.options.geocodingQueryParams;
742 if (
743 typeof params.proximity !== 'undefined' &&
744 params.proximity.hasOwnProperty('lat') &&
745 params.proximity.hasOwnProperty('lng')
746 ) {
747 params.proximity = params.proximity.lng + ',' + params.proximity.lat;
748 }
749 getJSON(this.options.serviceUrl + encodeURIComponent(query) + '.json', params, function(
750 data
751 ) {
752 var results = [],
753 loc,
754 latLng,
755 latLngBounds;
756 if (data.features && data.features.length) {
757 for (var i = 0; i <= data.features.length - 1; i++) {
758 loc = data.features[i];
759 latLng = L.latLng(loc.center.reverse());
760 if (loc.hasOwnProperty('bbox')) {
761 latLngBounds = L.latLngBounds(
762 L.latLng(loc.bbox.slice(0, 2).reverse()),
763 L.latLng(loc.bbox.slice(2, 4).reverse())
764 );
765 } else {
766 latLngBounds = L.latLngBounds(latLng, latLng);
767 }
768
769 var properties = {
770 text: loc.text,
771 address: loc.address
772 };
773
774 for (var j = 0; j < loc.context.length; j++) {
775 var id = loc.context[j].id.split('.')[0];
776 properties[id] = loc.context[j].text;
777 }
778
779 results[i] = {
780 name: loc.place_name,
781 bbox: latLngBounds,
782 center: latLng,
783 properties: properties
784 };
785 }
786 }
787
788 cb.call(context, results);
789 });
790 },
791
792 suggest: function(query, cb, context) {
793 return this.geocode(query, cb, context);
794 },
795
796 reverse: function(location, scale, cb, context) {
797 getJSON(
798 this.options.serviceUrl +
799 encodeURIComponent(location.lng) +
800 ',' +
801 encodeURIComponent(location.lat) +
802 '.json',
803 this.options.reverseQueryParams,
804 function(data) {
805 var results = [],
806 loc,
807 latLng,
808 latLngBounds;
809 if (data.features && data.features.length) {
810 for (var i = 0; i <= data.features.length - 1; i++) {
811 loc = data.features[i];
812 latLng = L.latLng(loc.center.reverse());
813 if (loc.hasOwnProperty('bbox')) {
814 latLngBounds = L.latLngBounds(
815 L.latLng(loc.bbox.slice(0, 2).reverse()),
816 L.latLng(loc.bbox.slice(2, 4).reverse())
817 );
818 } else {
819 latLngBounds = L.latLngBounds(latLng, latLng);
820 }
821 results[i] = {
822 name: loc.place_name,
823 bbox: latLngBounds,
824 center: latLng
825 };
826 }
827 }
828
829 cb.call(context, results);
830 }
831 );
832 }
833 }),
834
835 factory: function(accessToken, options) {
836 return new L.Control.Geocoder.Mapbox(accessToken, options);
837 }
838 };
839
840 var What3Words = {
841 class: L.Class.extend({
842 options: {
843 serviceUrl: 'https://api.what3words.com/v2/'
844 },
845
846 initialize: function(accessToken) {
847 this._accessToken = accessToken;
848 },
849
850 geocode: function(query, cb, context) {
851 //get three words and make a dot based string
852 getJSON(
853 this.options.serviceUrl + 'forward',
854 {
855 key: this._accessToken,
856 addr: query.split(/\s+/).join('.')
857 },
858 function(data) {
859 var results = [],
860 latLng,
861 latLngBounds;
862 if (data.hasOwnProperty('geometry')) {
863 latLng = L.latLng(data.geometry['lat'], data.geometry['lng']);
864 latLngBounds = L.latLngBounds(latLng, latLng);
865 results[0] = {
866 name: data.words,
867 bbox: latLngBounds,
868 center: latLng
869 };
870 }
871
872 cb.call(context, results);
873 }
874 );
875 },
876
877 suggest: function(query, cb, context) {
878 return this.geocode(query, cb, context);
879 },
880
881 reverse: function(location, scale, cb, context) {
882 getJSON(
883 this.options.serviceUrl + 'reverse',
884 {
885 key: this._accessToken,
886 coords: [location.lat, location.lng].join(',')
887 },
888 function(data) {
889 var results = [],
890 latLng,
891 latLngBounds;
892 if (data.status.status == 200) {
893 latLng = L.latLng(data.geometry['lat'], data.geometry['lng']);
894 latLngBounds = L.latLngBounds(latLng, latLng);
895 results[0] = {
896 name: data.words,
897 bbox: latLngBounds,
898 center: latLng
899 };
900 }
901 cb.call(context, results);
902 }
903 );
904 }
905 }),
906
907 factory: function(accessToken) {
908 return new L.Control.Geocoder.What3Words(accessToken);
909 }
910 };
911
912 var Google = {
913 class: L.Class.extend({
914 options: {
915 serviceUrl: 'https://maps.googleapis.com/maps/api/geocode/json',
916 geocodingQueryParams: {},
917 reverseQueryParams: {}
918 },
919
920 initialize: function(key, options) {
921 this._key = key;
922 L.setOptions(this, options);
923 // Backwards compatibility
924 this.options.serviceUrl = this.options.service_url || this.options.serviceUrl;
925 },
926
927 geocode: function(query, cb, context) {
928 var params = {
929 address: query
930 };
931
932 if (this._key && this._key.length) {
933 params.key = this._key;
934 }
935
936 params = L.Util.extend(params, this.options.geocodingQueryParams);
937
938 getJSON(this.options.serviceUrl, params, function(data) {
939 var results = [],
940 loc,
941 latLng,
942 latLngBounds;
943 if (data.results && data.results.length) {
944 for (var i = 0; i <= data.results.length - 1; i++) {
945 loc = data.results[i];
946 latLng = L.latLng(loc.geometry.location);
947 latLngBounds = L.latLngBounds(
948 L.latLng(loc.geometry.viewport.northeast),
949 L.latLng(loc.geometry.viewport.southwest)
950 );
951 results[i] = {
952 name: loc.formatted_address,
953 bbox: latLngBounds,
954 center: latLng,
955 properties: loc.address_components
956 };
957 }
958 }
959
960 cb.call(context, results);
961 });
962 },
963
964 reverse: function(location, scale, cb, context) {
965 var params = {
966 latlng: encodeURIComponent(location.lat) + ',' + encodeURIComponent(location.lng)
967 };
968 params = L.Util.extend(params, this.options.reverseQueryParams);
969 if (this._key && this._key.length) {
970 params.key = this._key;
971 }
972
973 getJSON(this.options.serviceUrl, params, function(data) {
974 var results = [],
975 loc,
976 latLng,
977 latLngBounds;
978 if (data.results && data.results.length) {
979 for (var i = 0; i <= data.results.length - 1; i++) {
980 loc = data.results[i];
981 latLng = L.latLng(loc.geometry.location);
982 latLngBounds = L.latLngBounds(
983 L.latLng(loc.geometry.viewport.northeast),
984 L.latLng(loc.geometry.viewport.southwest)
985 );
986 results[i] = {
987 name: loc.formatted_address,
988 bbox: latLngBounds,
989 center: latLng,
990 properties: loc.address_components
991 };
992 }
993 }
994
995 cb.call(context, results);
996 });
997 }
998 }),
999
1000 factory: function(key, options) {
1001 return new L.Control.Geocoder.Google(key, options);
1002 }
1003 };
1004
1005 var Photon = {
1006 class: L.Class.extend({
1007 options: {
1008 serviceUrl: 'https://photon.komoot.de/api/',
1009 reverseUrl: 'https://photon.komoot.de/reverse/',
1010 nameProperties: ['name', 'street', 'suburb', 'hamlet', 'town', 'city', 'state', 'country']
1011 },
1012
1013 initialize: function(options) {
1014 L.setOptions(this, options);
1015 },
1016
1017 geocode: function(query, cb, context) {
1018 var params = L.extend(
1019 {
1020 q: query
1021 },
1022 this.options.geocodingQueryParams
1023 );
1024
1025 getJSON(
1026 this.options.serviceUrl,
1027 params,
1028 L.bind(function(data) {
1029 cb.call(context, this._decodeFeatures(data));
1030 }, this)
1031 );
1032 },
1033
1034 suggest: function(query, cb, context) {
1035 return this.geocode(query, cb, context);
1036 },
1037
1038 reverse: function(latLng, scale, cb, context) {
1039 var params = L.extend(
1040 {
1041 lat: latLng.lat,
1042 lon: latLng.lng
1043 },
1044 this.options.reverseQueryParams
1045 );
1046
1047 getJSON(
1048 this.options.reverseUrl,
1049 params,
1050 L.bind(function(data) {
1051 cb.call(context, this._decodeFeatures(data));
1052 }, this)
1053 );
1054 },
1055
1056 _decodeFeatures: function(data) {
1057 var results = [],
1058 i,
1059 f,
1060 c,
1061 latLng,
1062 extent,
1063 bbox;
1064
1065 if (data && data.features) {
1066 for (i = 0; i < data.features.length; i++) {
1067 f = data.features[i];
1068 c = f.geometry.coordinates;
1069 latLng = L.latLng(c[1], c[0]);
1070 extent = f.properties.extent;
1071
1072 if (extent) {
1073 bbox = L.latLngBounds([extent[1], extent[0]], [extent[3], extent[2]]);
1074 } else {
1075 bbox = L.latLngBounds(latLng, latLng);
1076 }
1077
1078 results.push({
1079 name: this._decodeFeatureName(f),
1080 html: this.options.htmlTemplate ? this.options.htmlTemplate(f) : undefined,
1081 center: latLng,
1082 bbox: bbox,
1083 properties: f.properties
1084 });
1085 }
1086 }
1087
1088 return results;
1089 },
1090
1091 _decodeFeatureName: function(f) {
1092 return (this.options.nameProperties || [])
1093 .map(function(p) {
1094 return f.properties[p];
1095 })
1096 .filter(function(v) {
1097 return !!v;
1098 })
1099 .join(', ');
1100 }
1101 }),
1102
1103 factory: function(options) {
1104 return new L.Control.Geocoder.Photon(options);
1105 }
1106 };
1107
1108 var Mapzen = {
1109 class: L.Class.extend({
1110 options: {
1111 serviceUrl: 'https://api.geocode.earth/v1',
1112 geocodingQueryParams: {},
1113 reverseQueryParams: {}
1114 },
1115
1116 initialize: function(apiKey, options) {
1117 L.Util.setOptions(this, options);
1118 this._apiKey = apiKey;
1119 this._lastSuggest = 0;
1120 },
1121
1122 geocode: function(query, cb, context) {
1123 var _this = this;
1124 getJSON(
1125 this.options.serviceUrl + '/search',
1126 L.extend(
1127 {
1128 api_key: this._apiKey,
1129 text: query
1130 },
1131 this.options.geocodingQueryParams
1132 ),
1133 function(data) {
1134 cb.call(context, _this._parseResults(data, 'bbox'));
1135 }
1136 );
1137 },
1138
1139 suggest: function(query, cb, context) {
1140 var _this = this;
1141 getJSON(
1142 this.options.serviceUrl + '/autocomplete',
1143 L.extend(
1144 {
1145 api_key: this._apiKey,
1146 text: query
1147 },
1148 this.options.geocodingQueryParams
1149 ),
1150 L.bind(function(data) {
1151 if (data.geocoding.timestamp > this._lastSuggest) {
1152 this._lastSuggest = data.geocoding.timestamp;
1153 cb.call(context, _this._parseResults(data, 'bbox'));
1154 }
1155 }, this)
1156 );
1157 },
1158
1159 reverse: function(location, scale, cb, context) {
1160 var _this = this;
1161 getJSON(
1162 this.options.serviceUrl + '/reverse',
1163 L.extend(
1164 {
1165 api_key: this._apiKey,
1166 'point.lat': location.lat,
1167 'point.lon': location.lng
1168 },
1169 this.options.reverseQueryParams
1170 ),
1171 function(data) {
1172 cb.call(context, _this._parseResults(data, 'bounds'));
1173 }
1174 );
1175 },
1176
1177 _parseResults: function(data, bboxname) {
1178 var results = [];
1179 L.geoJson(data, {
1180 pointToLayer: function(feature, latlng) {
1181 return L.circleMarker(latlng);
1182 },
1183 onEachFeature: function(feature, layer) {
1184 var result = {},
1185 bbox,
1186 center;
1187
1188 if (layer.getBounds) {
1189 bbox = layer.getBounds();
1190 center = bbox.getCenter();
1191 } else {
1192 center = layer.getLatLng();
1193 bbox = L.latLngBounds(center, center);
1194 }
1195
1196 result.name = layer.feature.properties.label;
1197 result.center = center;
1198 result[bboxname] = bbox;
1199 result.properties = layer.feature.properties;
1200 results.push(result);
1201 }
1202 });
1203 return results;
1204 }
1205 }),
1206
1207 factory: function(apiKey, options) {
1208 return new L.Control.Geocoder.Mapzen(apiKey, options);
1209 }
1210 };
1211
1212 var ArcGis = {
1213 class: L.Class.extend({
1214 options: {
1215 service_url: 'https://geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer'
1216 },
1217
1218 initialize: function(accessToken, options) {
1219 L.setOptions(this, options);
1220 this._accessToken = accessToken;
1221 },
1222
1223 geocode: function(query, cb, context) {
1224 var params = {
1225 SingleLine: query,
1226 outFields: 'Addr_Type',
1227 forStorage: false,
1228 maxLocations: 10,
1229 f: 'json'
1230 };
1231
1232 if (this._key && this._key.length) {
1233 params.token = this._key;
1234 }
1235
1236 getJSON(this.options.service_url + '/findAddressCandidates', L.extend(params, this.options.geocodingQueryParams), function(data) {
1237 var results = [],
1238 loc,
1239 latLng,
1240 latLngBounds;
1241
1242 if (data.candidates && data.candidates.length) {
1243 for (var i = 0; i <= data.candidates.length - 1; i++) {
1244 loc = data.candidates[i];
1245 latLng = L.latLng(loc.location.y, loc.location.x);
1246 latLngBounds = L.latLngBounds(
1247 L.latLng(loc.extent.ymax, loc.extent.xmax),
1248 L.latLng(loc.extent.ymin, loc.extent.xmin)
1249 );
1250 results[i] = {
1251 name: loc.address,
1252 bbox: latLngBounds,
1253 center: latLng
1254 };
1255 }
1256 }
1257
1258 cb.call(context, results);
1259 });
1260 },
1261
1262 suggest: function(query, cb, context) {
1263 return this.geocode(query, cb, context);
1264 },
1265
1266 reverse: function(location, scale, cb, context) {
1267 var params = {
1268 location: encodeURIComponent(location.lng) + ',' + encodeURIComponent(location.lat),
1269 distance: 100,
1270 f: 'json'
1271 };
1272
1273 getJSON(this.options.service_url + '/reverseGeocode', params, function(data) {
1274 var result = [],
1275 loc;
1276
1277 if (data && !data.error) {
1278 loc = L.latLng(data.location.y, data.location.x);
1279 result.push({
1280 name: data.address.Match_addr,
1281 center: loc,
1282 bounds: L.latLngBounds(loc, loc)
1283 });
1284 }
1285
1286 cb.call(context, result);
1287 });
1288 }
1289 }),
1290
1291 factory: function(accessToken, options) {
1292 return new L.Control.Geocoder.ArcGis(accessToken, options);
1293 }
1294 };
1295
1296 var HERE = {
1297 class: L.Class.extend({
1298 options: {
1299 geocodeUrl: 'http://geocoder.api.here.com/6.2/geocode.json',
1300 reverseGeocodeUrl: 'http://reverse.geocoder.api.here.com/6.2/reversegeocode.json',
1301 app_id: '<insert your app_id here>',
1302 app_code: '<insert your app_code here>',
1303 geocodingQueryParams: {},
1304 reverseQueryParams: {}
1305 },
1306
1307 initialize: function(options) {
1308 L.setOptions(this, options);
1309 },
1310
1311 geocode: function(query, cb, context) {
1312 var params = {
1313 searchtext: query,
1314 gen: 9,
1315 app_id: this.options.app_id,
1316 app_code: this.options.app_code,
1317 jsonattributes: 1
1318 };
1319 params = L.Util.extend(params, this.options.geocodingQueryParams);
1320 this.getJSON(this.options.geocodeUrl, params, cb, context);
1321 },
1322
1323 reverse: function(location, scale, cb, context) {
1324 var params = {
1325 prox: encodeURIComponent(location.lat) + ',' + encodeURIComponent(location.lng),
1326 mode: 'retrieveAddresses',
1327 app_id: this.options.app_id,
1328 app_code: this.options.app_code,
1329 gen: 9,
1330 jsonattributes: 1
1331 };
1332 params = L.Util.extend(params, this.options.reverseQueryParams);
1333 this.getJSON(this.options.reverseGeocodeUrl, params, cb, context);
1334 },
1335
1336 getJSON: function(url, params, cb, context) {
1337 getJSON(url, params, function(data) {
1338 var results = [],
1339 loc,
1340 latLng,
1341 latLngBounds;
1342 if (data.response.view && data.response.view.length) {
1343 for (var i = 0; i <= data.response.view[0].result.length - 1; i++) {
1344 loc = data.response.view[0].result[i].location;
1345 latLng = L.latLng(loc.displayPosition.latitude, loc.displayPosition.longitude);
1346 latLngBounds = L.latLngBounds(
1347 L.latLng(loc.mapView.topLeft.latitude, loc.mapView.topLeft.longitude),
1348 L.latLng(loc.mapView.bottomRight.latitude, loc.mapView.bottomRight.longitude)
1349 );
1350 results[i] = {
1351 name: loc.address.label,
1352 bbox: latLngBounds,
1353 center: latLng
1354 };
1355 }
1356 }
1357 cb.call(context, results);
1358 });
1359 }
1360 }),
1361
1362 factory: function(options) {
1363 return new L.Control.Geocoder.HERE(options);
1364 }
1365 };
1366
1367 var Neutrino = {
1368 class: L.Class.extend({
1369 options: {
1370 userId: '<insert your userId here>',
1371 apiKey: '<insert your apiKey here>',
1372 serviceUrl: 'https://neutrinoapi.com/'
1373 },
1374
1375 initialize: function(options) {
1376 L.Util.setOptions(this, options);
1377 },
1378
1379 // https://www.neutrinoapi.com/api/geocode-address/
1380 geocode: function(query, cb, context) {
1381 getJSON(
1382 this.options.serviceUrl + 'geocode-address',
1383 {
1384 apiKey: this.options.apiKey,
1385 userId: this.options.userId,
1386 //get three words and make a dot based string
1387 address: query.split(/\s+/).join('.')
1388 },
1389 function(data) {
1390 var results = [],
1391 latLng,
1392 latLngBounds;
1393 if (data.hasOwnProperty('locations')) {
1394 data.geometry = data.locations[0];
1395 latLng = L.latLng(data.geometry['latitude'], data.geometry['longitude']);
1396 latLngBounds = L.latLngBounds(latLng, latLng);
1397 results[0] = {
1398 name: data.geometry.address,
1399 bbox: latLngBounds,
1400 center: latLng
1401 };
1402 }
1403
1404 cb.call(context, results);
1405 }
1406 );
1407 },
1408
1409 suggest: function(query, cb, context) {
1410 return this.geocode(query, cb, context);
1411 },
1412
1413 // https://www.neutrinoapi.com/api/geocode-reverse/
1414 reverse: function(location, scale, cb, context) {
1415 getJSON(
1416 this.options.serviceUrl + 'geocode-reverse',
1417 {
1418 apiKey: this.options.apiKey,
1419 userId: this.options.userId,
1420 latitude: location.lat,
1421 longitude: location.lng
1422 },
1423 function(data) {
1424 var results = [],
1425 latLng,
1426 latLngBounds;
1427 if (data.status.status == 200 && data.found) {
1428 latLng = L.latLng(location.lat, location.lng);
1429 latLngBounds = L.latLngBounds(latLng, latLng);
1430 results[0] = {
1431 name: data.address,
1432 bbox: latLngBounds,
1433 center: latLng
1434 };
1435 }
1436 cb.call(context, results);
1437 }
1438 );
1439 }
1440 }),
1441
1442 factory: function(accessToken) {
1443 return new L.Control.Geocoder.Neutrino(accessToken);
1444 }
1445 };
1446
1447 var Geocoder = L.Util.extend(Control.class, {
1448 Nominatim: Nominatim.class,
1449 nominatim: Nominatim.factory,
1450 Bing: Bing.class,
1451 bing: Bing.factory,
1452 MapQuest: MapQuest.class,
1453 mapQuest: MapQuest.factory,
1454 Mapbox: Mapbox.class,
1455 mapbox: Mapbox.factory,
1456 What3Words: What3Words.class,
1457 what3words: What3Words.factory,
1458 Google: Google.class,
1459 google: Google.factory,
1460 Photon: Photon.class,
1461 photon: Photon.factory,
1462 Mapzen: Mapzen.class,
1463 GeocodeEarth: Mapzen.class,
1464 Pelias: Mapzen.class,
1465 mapzen: Mapzen.factory,
1466 geocodeEarth: Mapzen.factory,
1467 pelias: Mapzen.factory,
1468 ArcGis: ArcGis.class,
1469 arcgis: ArcGis.factory,
1470 HERE: HERE.class,
1471 here: HERE.factory,
1472 Neutrino: Neutrino.class,
1473 neutrino: Neutrino.factory,
1474 });
1475
1476 L.Util.extend(L.Control, {
1477 Geocoder: Geocoder,
1478 geocoder: Control.factory
1479 });
1480
1481 return Geocoder;
1482
1483}(L));
1484//# sourceMappingURL=Control.Geocoder.bundle.js.map