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 '&': '&', 25 '<': '<', 26 '>': '>', 27 '"': '"', 28 "'": ''', 29 '`': '`' 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 = ' '; 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.js.map
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.