first commit

This commit is contained in:
2025-07-18 16:20:14 +07:00
commit 98af45c018
16382 changed files with 3148096 additions and 0 deletions

View File

@@ -0,0 +1,23 @@
Copyright (c) 2012 sa3m (https://github.com/sa3m)
Copyright (c) 2013 Per Liedman
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are
permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of
conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list
of conditions and the following disclaimer in the documentation and/or other materials
provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -0,0 +1,126 @@
.leaflet-control-geocoder {
border-radius: 4px;
background: white;
min-width: 26px;
min-height: 26px;
}
.leaflet-touch .leaflet-control-geocoder {
min-width: 30px;
min-height: 30px;
}
.leaflet-control-geocoder a,
.leaflet-control-geocoder .leaflet-control-geocoder-icon {
border-bottom: none;
display: inline-block;
}
.leaflet-control-geocoder .leaflet-control-geocoder-alternatives a {
width: inherit;
height: inherit;
line-height: inherit;
}
.leaflet-control-geocoder a:hover,
.leaflet-control-geocoder .leaflet-control-geocoder-icon:hover {
border-bottom: none;
display: inline-block;
}
.leaflet-control-geocoder-form {
display: none;
vertical-align: middle;
}
.leaflet-control-geocoder-expanded .leaflet-control-geocoder-form {
display: inline-block;
}
.leaflet-control-geocoder-form input {
font-size: 120%;
border: 0;
background-color: transparent;
width: 246px;
}
.leaflet-control-geocoder-icon {
border-radius: 4px;
width: 26px;
height: 26px;
border: none;
background-color: white;
background-image: url(images/geocoder.png);
background-repeat: no-repeat;
background-position: center;
cursor: pointer;
}
.leaflet-touch .leaflet-control-geocoder-icon {
width: 30px;
height: 30px;
}
.leaflet-control-geocoder-throbber .leaflet-control-geocoder-icon {
background-image: url(images/throbber.gif);
}
.leaflet-control-geocoder-form-no-error {
display: none;
}
.leaflet-control-geocoder-form input:focus {
outline: none;
}
.leaflet-control-geocoder-form button {
display: none;
}
.leaflet-control-geocoder-error {
margin-top: 8px;
margin-left: 8px;
display: block;
color: #444;
}
.leaflet-control-geocoder-alternatives {
display: block;
width: 272px;
list-style: none;
padding: 0;
margin: 0;
}
.leaflet-control-geocoder-alternatives-minimized {
display: none;
height: 0;
}
.leaflet-control-geocoder-alternatives li {
white-space: nowrap;
display: block;
overflow: hidden;
padding: 5px 8px;
text-overflow: ellipsis;
border-bottom: 1px solid #ccc;
cursor: pointer;
}
.leaflet-control-geocoder-alternatives li a,
.leaflet-control-geocoder-alternatives li a:hover {
width: inherit;
height: inherit;
line-height: inherit;
background: inherit;
border-radius: inherit;
text-align: left;
}
.leaflet-control-geocoder-alternatives li:last-child {
border-bottom: none;
}
.leaflet-control-geocoder-alternatives li:hover,
.leaflet-control-geocoder-selected {
background-color: #f5f5f5;
}
.leaflet-control-geocoder-address-detail {
}
.leaflet-control-geocoder-address-context {
color: #666;
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 490 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

View File

@@ -0,0 +1,363 @@
import L from 'leaflet';
import { Nominatim } from './geocoders/index';
export var Geocoder = L.Control.extend({
options: {
showUniqueResult: true,
showResultIcons: false,
collapsed: true,
expand: 'touch', // options: touch, click, anythingelse
position: 'topright',
placeholder: 'Search...',
errorMessage: 'Nothing found.',
queryMinLength: 1,
suggestMinLength: 3,
suggestTimeout: 250,
defaultMarkGeocode: true
},
includes: L.Evented.prototype || L.Mixin.Events,
initialize: function(options) {
L.Util.setOptions(this, options);
if (!this.options.geocoder) {
this.options.geocoder = new Nominatim();
}
this._requestCount = 0;
},
addThrobberClass: function() {
L.DomUtil.addClass(this._container, 'leaflet-control-geocoder-throbber');
},
removeThrobberClass: function() {
L.DomUtil.removeClass(this._container, 'leaflet-control-geocoder-throbber');
},
onAdd: function(map) {
var className = 'leaflet-control-geocoder',
container = L.DomUtil.create('div', className + ' leaflet-bar'),
icon = L.DomUtil.create('button', className + '-icon', container),
form = (this._form = L.DomUtil.create('div', className + '-form', container)),
input;
this._map = map;
this._container = container;
icon.innerHTML = ' ';
icon.type = 'button';
input = this._input = L.DomUtil.create('input', '', form);
input.type = 'text';
input.placeholder = this.options.placeholder;
L.DomEvent.disableClickPropagation(input);
this._errorElement = L.DomUtil.create('div', className + '-form-no-error', container);
this._errorElement.innerHTML = this.options.errorMessage;
this._alts = L.DomUtil.create(
'ul',
className + '-alternatives leaflet-control-geocoder-alternatives-minimized',
container
);
L.DomEvent.disableClickPropagation(this._alts);
L.DomEvent.addListener(input, 'keydown', this._keydown, this);
if (this.options.geocoder.suggest) {
L.DomEvent.addListener(input, 'input', this._change, this);
}
L.DomEvent.addListener(
input,
'blur',
function() {
if (this.options.collapsed && !this._preventBlurCollapse) {
this._collapse();
}
this._preventBlurCollapse = false;
},
this
);
if (this.options.collapsed) {
if (this.options.expand === 'click') {
L.DomEvent.addListener(
container,
'click',
function(e) {
if (e.button === 0 && e.detail !== 2) {
this._toggle();
}
},
this
);
} else if (L.Browser.touch && this.options.expand === 'touch') {
L.DomEvent.addListener(
container,
'touchstart mousedown',
function(e) {
this._toggle();
e.preventDefault(); // mobile: clicking focuses the icon, so UI expands and immediately collapses
e.stopPropagation();
},
this
);
} else {
L.DomEvent.addListener(container, 'mouseover', this._expand, this);
L.DomEvent.addListener(container, 'mouseout', this._collapse, this);
this._map.on('movestart', this._collapse, this);
}
} else {
this._expand();
if (L.Browser.touch) {
L.DomEvent.addListener(
container,
'touchstart',
function() {
this._geocode();
},
this
);
} else {
L.DomEvent.addListener(
container,
'click',
function() {
this._geocode();
},
this
);
}
}
if (this.options.defaultMarkGeocode) {
this.on('markgeocode', this.markGeocode, this);
}
this.on('startgeocode', this.addThrobberClass, this);
this.on('finishgeocode', this.removeThrobberClass, this);
this.on('startsuggest', this.addThrobberClass, this);
this.on('finishsuggest', this.removeThrobberClass, this);
L.DomEvent.disableClickPropagation(container);
return container;
},
_geocodeResult: function(results, suggest) {
if (!suggest && this.options.showUniqueResult && results.length === 1) {
this._geocodeResultSelected(results[0]);
} else if (results.length > 0) {
this._alts.innerHTML = '';
this._results = results;
L.DomUtil.removeClass(this._alts, 'leaflet-control-geocoder-alternatives-minimized');
L.DomUtil.addClass(this._container, 'leaflet-control-geocoder-options-open');
for (var i = 0; i < results.length; i++) {
this._alts.appendChild(this._createAlt(results[i], i));
}
} else {
L.DomUtil.addClass(this._container, 'leaflet-control-geocoder-options-error');
L.DomUtil.addClass(this._errorElement, 'leaflet-control-geocoder-error');
}
},
markGeocode: function(result) {
result = result.geocode || result;
this._map.fitBounds(result.bbox);
if (this._geocodeMarker) {
this._map.removeLayer(this._geocodeMarker);
}
this._geocodeMarker = new L.Marker(result.center)
.bindPopup(result.html || result.name)
.addTo(this._map)
.openPopup();
return this;
},
_geocode: function(suggest) {
var value = this._input.value;
if (!suggest && value.length < this.options.queryMinLength) {
return;
}
var requestCount = ++this._requestCount,
mode = suggest ? 'suggest' : 'geocode',
eventData = { input: value };
this._lastGeocode = value;
if (!suggest) {
this._clearResults();
}
this.fire('start' + mode, eventData);
this.options.geocoder[mode](
value,
function(results) {
if (requestCount === this._requestCount) {
eventData.results = results;
this.fire('finish' + mode, eventData);
this._geocodeResult(results, suggest);
}
},
this
);
},
_geocodeResultSelected: function(result) {
this.fire('markgeocode', { geocode: result });
},
_toggle: function() {
if (L.DomUtil.hasClass(this._container, 'leaflet-control-geocoder-expanded')) {
this._collapse();
} else {
this._expand();
}
},
_expand: function() {
L.DomUtil.addClass(this._container, 'leaflet-control-geocoder-expanded');
this._input.select();
this.fire('expand');
},
_collapse: function() {
L.DomUtil.removeClass(this._container, 'leaflet-control-geocoder-expanded');
L.DomUtil.addClass(this._alts, 'leaflet-control-geocoder-alternatives-minimized');
L.DomUtil.removeClass(this._errorElement, 'leaflet-control-geocoder-error');
L.DomUtil.removeClass(this._container, 'leaflet-control-geocoder-options-open');
L.DomUtil.removeClass(this._container, 'leaflet-control-geocoder-options-error');
this._input.blur(); // mobile: keyboard shouldn't stay expanded
this.fire('collapse');
},
_clearResults: function() {
L.DomUtil.addClass(this._alts, 'leaflet-control-geocoder-alternatives-minimized');
this._selection = null;
L.DomUtil.removeClass(this._errorElement, 'leaflet-control-geocoder-error');
L.DomUtil.removeClass(this._container, 'leaflet-control-geocoder-options-open');
L.DomUtil.removeClass(this._container, 'leaflet-control-geocoder-options-error');
},
_createAlt: function(result, index) {
var li = L.DomUtil.create('li', ''),
a = L.DomUtil.create('a', '', li),
icon = this.options.showResultIcons && result.icon ? L.DomUtil.create('img', '', a) : null,
text = result.html ? undefined : document.createTextNode(result.name),
mouseDownHandler = function mouseDownHandler(e) {
// In some browsers, a click will fire on the map if the control is
// collapsed directly after mousedown. To work around this, we
// wait until the click is completed, and _then_ collapse the
// control. Messy, but this is the workaround I could come up with
// for #142.
this._preventBlurCollapse = true;
L.DomEvent.stop(e);
this._geocodeResultSelected(result);
L.DomEvent.on(
li,
'click',
function() {
if (this.options.collapsed) {
this._collapse();
} else {
this._clearResults();
}
},
this
);
};
if (icon) {
icon.src = result.icon;
}
li.setAttribute('data-result-index', index);
if (result.html) {
a.innerHTML = a.innerHTML + result.html;
} else {
a.appendChild(text);
}
// Use mousedown and not click, since click will fire _after_ blur,
// causing the control to have collapsed and removed the items
// before the click can fire.
L.DomEvent.addListener(li, 'mousedown touchstart', mouseDownHandler, this);
return li;
},
_keydown: function(e) {
var _this = this,
select = function select(dir) {
if (_this._selection) {
L.DomUtil.removeClass(_this._selection, 'leaflet-control-geocoder-selected');
_this._selection = _this._selection[dir > 0 ? 'nextSibling' : 'previousSibling'];
}
if (!_this._selection) {
_this._selection = _this._alts[dir > 0 ? 'firstChild' : 'lastChild'];
}
if (_this._selection) {
L.DomUtil.addClass(_this._selection, 'leaflet-control-geocoder-selected');
}
};
switch (e.keyCode) {
// Escape
case 27:
if (this.options.collapsed) {
this._collapse();
} else {
this._clearResults();
}
break;
// Up
case 38:
select(-1);
break;
// Up
case 40:
select(1);
break;
// Enter
case 13:
if (this._selection) {
var index = parseInt(this._selection.getAttribute('data-result-index'), 10);
this._geocodeResultSelected(this._results[index]);
this._clearResults();
} else {
this._geocode();
}
break;
default:
return;
}
L.DomEvent.preventDefault(e);
},
_change: function() {
var v = this._input.value;
if (v !== this._lastGeocode) {
clearTimeout(this._suggestTimeout);
if (v.length >= this.options.suggestMinLength) {
this._suggestTimeout = setTimeout(
L.bind(function() {
this._geocode(true);
}, this),
this.options.suggestTimeout
);
} else {
this._clearResults();
}
}
}
});
export function geocoder(options) {
return new Geocoder(options);
}

View File

@@ -0,0 +1,88 @@
import L from 'leaflet';
import { getJSON } from '../util';
export var ArcGis = L.Class.extend({
options: {
service_url: 'https://geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer'
},
initialize: function(accessToken, options) {
L.setOptions(this, options);
this._accessToken = accessToken;
},
geocode: function(query, cb, context) {
var params = {
SingleLine: query,
outFields: 'Addr_Type',
forStorage: false,
maxLocations: 10,
f: 'json'
};
if (this._key && this._key.length) {
params.token = this._key;
}
getJSON(
this.options.service_url + '/findAddressCandidates',
L.extend(params, this.options.geocodingQueryParams),
function(data) {
var results = [],
loc,
latLng,
latLngBounds;
if (data.candidates && data.candidates.length) {
for (var i = 0; i <= data.candidates.length - 1; i++) {
loc = data.candidates[i];
latLng = L.latLng(loc.location.y, loc.location.x);
latLngBounds = L.latLngBounds(
L.latLng(loc.extent.ymax, loc.extent.xmax),
L.latLng(loc.extent.ymin, loc.extent.xmin)
);
results[i] = {
name: loc.address,
bbox: latLngBounds,
center: latLng
};
}
}
cb.call(context, results);
}
);
},
suggest: function(query, cb, context) {
return this.geocode(query, cb, context);
},
reverse: function(location, scale, cb, context) {
var params = {
location: encodeURIComponent(location.lng) + ',' + encodeURIComponent(location.lat),
distance: 100,
f: 'json'
};
getJSON(this.options.service_url + '/reverseGeocode', params, function(data) {
var result = [],
loc;
if (data && !data.error) {
loc = L.latLng(data.location.y, data.location.x);
result.push({
name: data.address.Match_addr,
center: loc,
bounds: L.latLngBounds(loc, loc)
});
}
cb.call(context, result);
});
}
});
export function arcgis(accessToken, options) {
return new ArcGis(accessToken, options);
}

View File

@@ -0,0 +1,63 @@
import L from 'leaflet';
import { jsonp } from '../util';
export var Bing = L.Class.extend({
initialize: function(key) {
this.key = key;
},
geocode: function(query, cb, context) {
jsonp(
'https://dev.virtualearth.net/REST/v1/Locations',
{
query: query,
key: this.key
},
function(data) {
var results = [];
if (data.resourceSets.length > 0) {
for (var i = data.resourceSets[0].resources.length - 1; i >= 0; i--) {
var resource = data.resourceSets[0].resources[i],
bbox = resource.bbox;
results[i] = {
name: resource.name,
bbox: L.latLngBounds([bbox[0], bbox[1]], [bbox[2], bbox[3]]),
center: L.latLng(resource.point.coordinates)
};
}
}
cb.call(context, results);
},
this,
'jsonp'
);
},
reverse: function(location, scale, cb, context) {
jsonp(
'//dev.virtualearth.net/REST/v1/Locations/' + location.lat + ',' + location.lng,
{
key: this.key
},
function(data) {
var results = [];
for (var i = data.resourceSets[0].resources.length - 1; i >= 0; i--) {
var resource = data.resourceSets[0].resources[i],
bbox = resource.bbox;
results[i] = {
name: resource.name,
bbox: L.latLngBounds([bbox[0], bbox[1]], [bbox[2], bbox[3]]),
center: L.latLng(resource.point.coordinates)
};
}
cb.call(context, results);
},
this,
'jsonp'
);
}
});
export function bing(key) {
return new Bing(key);
}

View File

@@ -0,0 +1,93 @@
import L from 'leaflet';
import { getJSON } from '../util';
export var Google = L.Class.extend({
options: {
serviceUrl: 'https://maps.googleapis.com/maps/api/geocode/json',
geocodingQueryParams: {},
reverseQueryParams: {}
},
initialize: function(key, options) {
this._key = key;
L.setOptions(this, options);
// Backwards compatibility
this.options.serviceUrl = this.options.service_url || this.options.serviceUrl;
},
geocode: function(query, cb, context) {
var params = {
address: query
};
if (this._key && this._key.length) {
params.key = this._key;
}
params = L.Util.extend(params, this.options.geocodingQueryParams);
getJSON(this.options.serviceUrl, params, function(data) {
var results = [],
loc,
latLng,
latLngBounds;
if (data.results && data.results.length) {
for (var i = 0; i <= data.results.length - 1; i++) {
loc = data.results[i];
latLng = L.latLng(loc.geometry.location);
latLngBounds = L.latLngBounds(
L.latLng(loc.geometry.viewport.northeast),
L.latLng(loc.geometry.viewport.southwest)
);
results[i] = {
name: loc.formatted_address,
bbox: latLngBounds,
center: latLng,
properties: loc.address_components
};
}
}
cb.call(context, results);
});
},
reverse: function(location, scale, cb, context) {
var params = {
latlng: encodeURIComponent(location.lat) + ',' + encodeURIComponent(location.lng)
};
params = L.Util.extend(params, this.options.reverseQueryParams);
if (this._key && this._key.length) {
params.key = this._key;
}
getJSON(this.options.serviceUrl, params, function(data) {
var results = [],
loc,
latLng,
latLngBounds;
if (data.results && data.results.length) {
for (var i = 0; i <= data.results.length - 1; i++) {
loc = data.results[i];
latLng = L.latLng(loc.geometry.location);
latLngBounds = L.latLngBounds(
L.latLng(loc.geometry.viewport.northeast),
L.latLng(loc.geometry.viewport.southwest)
);
results[i] = {
name: loc.formatted_address,
bbox: latLngBounds,
center: latLng,
properties: loc.address_components
};
}
}
cb.call(context, results);
});
}
});
export function google(key, options) {
return new Google(key, options);
}

View File

@@ -0,0 +1,70 @@
import L from 'leaflet';
import { getJSON } from '../util';
export var HERE = L.Class.extend({
options: {
geocodeUrl: 'https://geocoder.api.here.com/6.2/geocode.json',
reverseGeocodeUrl: 'https://reverse.geocoder.api.here.com/6.2/reversegeocode.json',
app_id: '<insert your app_id here>',
app_code: '<insert your app_code here>',
geocodingQueryParams: {},
reverseQueryParams: {},
reverseGeocodeProxRadius: null
},
initialize: function(options) {
L.setOptions(this, options);
},
geocode: function(query, cb, context) {
var params = {
searchtext: query,
gen: 9,
app_id: this.options.app_id,
app_code: this.options.app_code,
jsonattributes: 1
};
params = L.Util.extend(params, this.options.geocodingQueryParams);
this.getJSON(this.options.geocodeUrl, params, cb, context);
},
reverse: function(location, scale, cb, context) {
var _proxRadius = this.options.reverseGeocodeProxRadius
? this.options.reverseGeocodeProxRadius
: null;
var proxRadius = _proxRadius ? ',' + encodeURIComponent(_proxRadius) : '';
var params = {
prox: encodeURIComponent(location.lat) + ',' + encodeURIComponent(location.lng) + proxRadius,
mode: 'retrieveAddresses',
app_id: this.options.app_id,
app_code: this.options.app_code,
gen: 9,
jsonattributes: 1
};
params = L.Util.extend(params, this.options.reverseQueryParams);
this.getJSON(this.options.reverseGeocodeUrl, params, cb, context);
},
getJSON: function(url, params, cb, context) {
getJSON(url, params, function(data) {
var results = [],
loc,
latLng,
latLngBounds;
if (data.response.view && data.response.view.length) {
for (var i = 0; i <= data.response.view[0].result.length - 1; i++) {
loc = data.response.view[0].result[i].location;
latLng = L.latLng(loc.displayPosition.latitude, loc.displayPosition.longitude);
latLngBounds = L.latLngBounds(
L.latLng(loc.mapView.topLeft.latitude, loc.mapView.topLeft.longitude),
L.latLng(loc.mapView.bottomRight.latitude, loc.mapView.bottomRight.longitude)
);
results[i] = {
name: loc.address.label,
bbox: latLngBounds,
center: latLng
};
}
}
cb.call(context, results);
});
}
});
export function here(options) {
return new HERE(options);
}

View File

@@ -0,0 +1,14 @@
export * from './arcgis';
export * from './bing';
export * from './google';
export * from './here';
export * from './latlng';
export * from './mapbox';
export * from './mapquest';
export * from './neutrino';
export * from './nominatim';
export * from './open-location-code';
export * from './opencage';
export * from './pelias';
export * from './photon';
export * from './what3words';

View File

@@ -0,0 +1,98 @@
import L from 'leaflet';
export var LatLng = L.Class.extend({
options: {
// the next geocoder to use
next: undefined,
sizeInMeters: 10000
},
initialize: function(options) {
L.Util.setOptions(this, options);
},
geocode: function(query, cb, context) {
var match;
var center;
// regex from https://github.com/openstreetmap/openstreetmap-website/blob/master/app/controllers/geocoder_controller.rb
if ((match = query.match(/^([NS])\s*(\d{1,3}(?:\.\d*)?)\W*([EW])\s*(\d{1,3}(?:\.\d*)?)$/))) {
// [NSEW] decimal degrees
center = L.latLng(
(/N/i.test(match[1]) ? 1 : -1) * parseFloat(match[2]),
(/E/i.test(match[3]) ? 1 : -1) * parseFloat(match[4])
);
} else if (
(match = query.match(/^(\d{1,3}(?:\.\d*)?)\s*([NS])\W*(\d{1,3}(?:\.\d*)?)\s*([EW])$/))
) {
// decimal degrees [NSEW]
center = L.latLng(
(/N/i.test(match[2]) ? 1 : -1) * parseFloat(match[1]),
(/E/i.test(match[4]) ? 1 : -1) * parseFloat(match[3])
);
} else if (
(match = query.match(
/^([NS])\s*(\d{1,3})°?\s*(\d{1,3}(?:\.\d*)?)?[']?\W*([EW])\s*(\d{1,3})°?\s*(\d{1,3}(?:\.\d*)?)?[']?$/
))
) {
// [NSEW] degrees, decimal minutes
center = L.latLng(
(/N/i.test(match[1]) ? 1 : -1) * (parseFloat(match[2]) + parseFloat(match[3] / 60)),
(/E/i.test(match[4]) ? 1 : -1) * (parseFloat(match[5]) + parseFloat(match[6] / 60))
);
} else if (
(match = query.match(
/^(\d{1,3})°?\s*(\d{1,3}(?:\.\d*)?)?[']?\s*([NS])\W*(\d{1,3})°?\s*(\d{1,3}(?:\.\d*)?)?[']?\s*([EW])$/
))
) {
// degrees, decimal minutes [NSEW]
center = L.latLng(
(/N/i.test(match[3]) ? 1 : -1) * (parseFloat(match[1]) + parseFloat(match[2] / 60)),
(/E/i.test(match[6]) ? 1 : -1) * (parseFloat(match[4]) + parseFloat(match[5] / 60))
);
} else if (
(match = query.match(
/^([NS])\s*(\d{1,3})°?\s*(\d{1,2})[']?\s*(\d{1,3}(?:\.\d*)?)?["″]?\W*([EW])\s*(\d{1,3})°?\s*(\d{1,2})[']?\s*(\d{1,3}(?:\.\d*)?)?["″]?$/
))
) {
// [NSEW] degrees, minutes, decimal seconds
center = L.latLng(
(/N/i.test(match[1]) ? 1 : -1) *
(parseFloat(match[2]) + parseFloat(match[3] / 60 + parseFloat(match[4] / 3600))),
(/E/i.test(match[5]) ? 1 : -1) *
(parseFloat(match[6]) + parseFloat(match[7] / 60) + parseFloat(match[8] / 3600))
);
} else if (
(match = query.match(
/^(\d{1,3})°?\s*(\d{1,2})[']?\s*(\d{1,3}(?:\.\d*)?)?["″]\s*([NS])\W*(\d{1,3})°?\s*(\d{1,2})[']?\s*(\d{1,3}(?:\.\d*)?)?["″]?\s*([EW])$/
))
) {
// degrees, minutes, decimal seconds [NSEW]
center = L.latLng(
(/N/i.test(match[4]) ? 1 : -1) *
(parseFloat(match[1]) + parseFloat(match[2] / 60 + parseFloat(match[3] / 3600))),
(/E/i.test(match[8]) ? 1 : -1) *
(parseFloat(match[5]) + parseFloat(match[6] / 60) + parseFloat(match[7] / 3600))
);
} else if (
(match = query.match(/^\s*([+-]?\d+(?:\.\d*)?)\s*[\s,]\s*([+-]?\d+(?:\.\d*)?)\s*$/))
) {
center = L.latLng(parseFloat(match[1]), parseFloat(match[2]));
}
if (center) {
var results = [
{
name: query,
center: center,
bbox: center.toBounds(this.options.sizeInMeters)
}
];
cb.call(context, results);
} else if (this.options.next) {
this.options.next.geocode(query, cb, context);
}
}
});
export function latLng(options) {
return new LatLng(options);
}

View File

@@ -0,0 +1,112 @@
import L from 'leaflet';
import { getJSON } from '../util';
export var Mapbox = L.Class.extend({
options: {
serviceUrl: 'https://api.mapbox.com/geocoding/v5/mapbox.places/',
geocodingQueryParams: {},
reverseQueryParams: {}
},
initialize: function(accessToken, options) {
L.setOptions(this, options);
this.options.geocodingQueryParams.access_token = accessToken;
this.options.reverseQueryParams.access_token = accessToken;
},
geocode: function(query, cb, context) {
var params = this.options.geocodingQueryParams;
if (
params.proximity !== undefined &&
params.proximity.lat !== undefined &&
params.proximity.lng !== undefined
) {
params.proximity = params.proximity.lng + ',' + params.proximity.lat;
}
getJSON(this.options.serviceUrl + encodeURIComponent(query) + '.json', params, function(data) {
var results = [],
loc,
latLng,
latLngBounds;
if (data.features && data.features.length) {
for (var i = 0; i <= data.features.length - 1; i++) {
loc = data.features[i];
latLng = L.latLng(loc.center.reverse());
if (loc.bbox) {
latLngBounds = L.latLngBounds(
L.latLng(loc.bbox.slice(0, 2).reverse()),
L.latLng(loc.bbox.slice(2, 4).reverse())
);
} else {
latLngBounds = L.latLngBounds(latLng, latLng);
}
var properties = {
text: loc.text,
address: loc.address
};
for (var j = 0; j < (loc.context || []).length; j++) {
var id = loc.context[j].id.split('.')[0];
properties[id] = loc.context[j].text;
}
results[i] = {
name: loc.place_name,
bbox: latLngBounds,
center: latLng,
properties: properties
};
}
}
cb.call(context, results);
});
},
suggest: function(query, cb, context) {
return this.geocode(query, cb, context);
},
reverse: function(location, scale, cb, context) {
getJSON(
this.options.serviceUrl +
encodeURIComponent(location.lng) +
',' +
encodeURIComponent(location.lat) +
'.json',
this.options.reverseQueryParams,
function(data) {
var results = [],
loc,
latLng,
latLngBounds;
if (data.features && data.features.length) {
for (var i = 0; i <= data.features.length - 1; i++) {
loc = data.features[i];
latLng = L.latLng(loc.center.reverse());
if (loc.bbox) {
latLngBounds = L.latLngBounds(
L.latLng(loc.bbox.slice(0, 2).reverse()),
L.latLng(loc.bbox.slice(2, 4).reverse())
);
} else {
latLngBounds = L.latLngBounds(latLng, latLng);
}
results[i] = {
name: loc.place_name,
bbox: latLngBounds,
center: latLng
};
}
}
cb.call(context, results);
}
);
}
});
export function mapbox(accessToken, options) {
return new Mapbox(accessToken, options);
}

View File

@@ -0,0 +1,91 @@
import L from 'leaflet';
import { getJSON } from '../util';
export var MapQuest = L.Class.extend({
options: {
serviceUrl: 'https://www.mapquestapi.com/geocoding/v1'
},
initialize: function(key, options) {
// MapQuest seems to provide URI encoded API keys,
// so to avoid encoding them twice, we decode them here
this._key = decodeURIComponent(key);
L.Util.setOptions(this, options);
},
_formatName: function() {
var r = [],
i;
for (i = 0; i < arguments.length; i++) {
if (arguments[i]) {
r.push(arguments[i]);
}
}
return r.join(', ');
},
geocode: function(query, cb, context) {
getJSON(
this.options.serviceUrl + '/address',
{
key: this._key,
location: query,
limit: 5,
outFormat: 'json'
},
L.bind(function(data) {
var results = [],
loc,
latLng;
if (data.results && data.results[0].locations) {
for (var i = data.results[0].locations.length - 1; i >= 0; i--) {
loc = data.results[0].locations[i];
latLng = L.latLng(loc.latLng);
results[i] = {
name: this._formatName(loc.street, loc.adminArea4, loc.adminArea3, loc.adminArea1),
bbox: L.latLngBounds(latLng, latLng),
center: latLng
};
}
}
cb.call(context, results);
}, this)
);
},
reverse: function(location, scale, cb, context) {
getJSON(
this.options.serviceUrl + '/reverse',
{
key: this._key,
location: location.lat + ',' + location.lng,
outputFormat: 'json'
},
L.bind(function(data) {
var results = [],
loc,
latLng;
if (data.results && data.results[0].locations) {
for (var i = data.results[0].locations.length - 1; i >= 0; i--) {
loc = data.results[0].locations[i];
latLng = L.latLng(loc.latLng);
results[i] = {
name: this._formatName(loc.street, loc.adminArea4, loc.adminArea3, loc.adminArea1),
bbox: L.latLngBounds(latLng, latLng),
center: latLng
};
}
}
cb.call(context, results);
}, this)
);
}
});
export function mapQuest(key, options) {
return new MapQuest(key, options);
}

View File

@@ -0,0 +1,80 @@
import L from 'leaflet';
import { getJSON } from '../util';
export var Neutrino = L.Class.extend({
options: {
userId: '<insert your userId here>',
apiKey: '<insert your apiKey here>',
serviceUrl: 'https://neutrinoapi.com/'
},
initialize: function(options) {
L.Util.setOptions(this, options);
},
// https://www.neutrinoapi.com/api/geocode-address/
geocode: function(query, cb, context) {
getJSON(
this.options.serviceUrl + 'geocode-address',
{
apiKey: this.options.apiKey,
userId: this.options.userId,
//get three words and make a dot based string
address: query.split(/\s+/).join('.')
},
function(data) {
var results = [],
latLng,
latLngBounds;
if (data.locations) {
data.geometry = data.locations[0];
latLng = L.latLng(data.geometry['latitude'], data.geometry['longitude']);
latLngBounds = L.latLngBounds(latLng, latLng);
results[0] = {
name: data.geometry.address,
bbox: latLngBounds,
center: latLng
};
}
cb.call(context, results);
}
);
},
suggest: function(query, cb, context) {
return this.geocode(query, cb, context);
},
// https://www.neutrinoapi.com/api/geocode-reverse/
reverse: function(location, scale, cb, context) {
getJSON(
this.options.serviceUrl + 'geocode-reverse',
{
apiKey: this.options.apiKey,
userId: this.options.userId,
latitude: location.lat,
longitude: location.lng
},
function(data) {
var results = [],
latLng,
latLngBounds;
if (data.status.status == 200 && data.found) {
latLng = L.latLng(location.lat, location.lng);
latLngBounds = L.latLngBounds(latLng, latLng);
results[0] = {
name: data.address,
bbox: latLngBounds,
center: latLng
};
}
cb.call(context, results);
}
);
}
});
export function neutrino(accessToken) {
return new Neutrino(accessToken);
}

View File

@@ -0,0 +1,107 @@
import L from 'leaflet';
import { template, getJSON } from '../util';
export var Nominatim = L.Class.extend({
options: {
serviceUrl: 'https://nominatim.openstreetmap.org/',
geocodingQueryParams: {},
reverseQueryParams: {},
htmlTemplate: function(r) {
var a = r.address,
parts = [];
if (a.road || a.building) {
parts.push('{building} {road} {house_number}');
}
if (a.city || a.town || a.village || a.hamlet) {
parts.push(
'<span class="' +
(parts.length > 0 ? 'leaflet-control-geocoder-address-detail' : '') +
'">{postcode} {city} {town} {village} {hamlet}</span>'
);
}
if (a.state || a.country) {
parts.push(
'<span class="' +
(parts.length > 0 ? 'leaflet-control-geocoder-address-context' : '') +
'">{state} {country}</span>'
);
}
return template(parts.join('<br/>'), a, true);
}
},
initialize: function(options) {
L.Util.setOptions(this, options);
},
geocode: function(query, cb, context) {
getJSON(
this.options.serviceUrl + 'search',
L.extend(
{
q: query,
limit: 5,
format: 'json',
addressdetails: 1
},
this.options.geocodingQueryParams
),
L.bind(function(data) {
var results = [];
for (var i = data.length - 1; i >= 0; i--) {
var bbox = data[i].boundingbox;
for (var j = 0; j < 4; j++) bbox[j] = parseFloat(bbox[j]);
results[i] = {
icon: data[i].icon,
name: data[i].display_name,
html: this.options.htmlTemplate ? this.options.htmlTemplate(data[i]) : undefined,
bbox: L.latLngBounds([bbox[0], bbox[2]], [bbox[1], bbox[3]]),
center: L.latLng(data[i].lat, data[i].lon),
properties: data[i]
};
}
cb.call(context, results);
}, this)
);
},
reverse: function(location, scale, cb, context) {
getJSON(
this.options.serviceUrl + 'reverse',
L.extend(
{
lat: location.lat,
lon: location.lng,
zoom: Math.round(Math.log(scale / 256) / Math.log(2)),
addressdetails: 1,
format: 'json'
},
this.options.reverseQueryParams
),
L.bind(function(data) {
var result = [],
loc;
if (data && data.lat && data.lon) {
loc = L.latLng(data.lat, data.lon);
result.push({
name: data.display_name,
html: this.options.htmlTemplate ? this.options.htmlTemplate(data) : undefined,
center: loc,
bounds: L.latLngBounds(loc, loc),
properties: data
});
}
cb.call(context, result);
}, this)
);
}
});
export function nominatim(options) {
return new Nominatim(options);
}

View File

@@ -0,0 +1,55 @@
import L from 'leaflet';
export var OpenLocationCode = L.Class.extend({
options: {
OpenLocationCode: undefined,
codeLength: undefined
},
initialize: function(options) {
L.setOptions(this, options);
},
geocode: function(query, cb, context) {
try {
var decoded = this.options.OpenLocationCode.decode(query);
var result = {
name: query,
center: L.latLng(decoded.latitudeCenter, decoded.longitudeCenter),
bbox: L.latLngBounds(
L.latLng(decoded.latitudeLo, decoded.longitudeLo),
L.latLng(decoded.latitudeHi, decoded.longitudeHi)
)
};
cb.call(context, [result]);
} catch (e) {
console.warn(e); // eslint-disable-line no-console
cb.call(context, []);
}
},
reverse: function(location, scale, cb, context) {
try {
var code = this.options.OpenLocationCode.encode(
location.lat,
location.lng,
this.options.codeLength
);
var result = {
name: code,
center: L.latLng(location.lat, location.lng),
bbox: L.latLngBounds(
L.latLng(location.lat, location.lng),
L.latLng(location.lat, location.lng)
)
};
cb.call(context, [result]);
} catch (e) {
console.warn(e); // eslint-disable-line no-console
cb.call(context, []);
}
}
});
export function openLocationCode(options) {
return new OpenLocationCode(options);
}

View File

@@ -0,0 +1,92 @@
import L from 'leaflet';
import { getJSON } from '../util';
export var OpenCage = L.Class.extend({
options: {
serviceUrl: 'https://api.opencagedata.com/geocode/v1/json'
},
initialize: function(apiKey) {
this._accessToken = apiKey;
},
geocode: function(query, cb, context) {
getJSON(
this.options.serviceUrl,
{
key: this._accessToken,
q: query
},
function(data) {
var results = [],
latLng,
latLngBounds,
loc;
if (data.results && data.results.length) {
for (var i = 0; i < data.results.length; i++) {
loc = data.results[i];
latLng = L.latLng(loc.geometry);
if (loc.annotations && loc.annotations.bounds) {
latLngBounds = L.latLngBounds(
L.latLng(loc.annotations.bounds.northeast),
L.latLng(loc.annotations.bounds.southwest)
);
} else {
latLngBounds = L.latLngBounds(latLng, latLng);
}
results.push({
name: loc.formatted,
bbox: latLngBounds,
center: latLng
});
}
}
cb.call(context, results);
}
);
},
suggest: function(query, cb, context) {
return this.geocode(query, cb, context);
},
reverse: function(location, scale, cb, context) {
getJSON(
this.options.serviceUrl,
{
key: this._accessToken,
q: [location.lat, location.lng].join(',')
},
function(data) {
var results = [],
latLng,
latLngBounds,
loc;
if (data.results && data.results.length) {
for (var i = 0; i < data.results.length; i++) {
loc = data.results[i];
latLng = L.latLng(loc.geometry);
if (loc.annotations && loc.annotations.bounds) {
latLngBounds = L.latLngBounds(
L.latLng(loc.annotations.bounds.northeast),
L.latLng(loc.annotations.bounds.southwest)
);
} else {
latLngBounds = L.latLngBounds(latLng, latLng);
}
results.push({
name: loc.formatted,
bbox: latLngBounds,
center: latLng
});
}
}
cb.call(context, results);
}
);
}
});
export function opencage(apiKey) {
return new OpenCage(apiKey);
}

View File

@@ -0,0 +1,124 @@
import L from 'leaflet';
import { getJSON } from '../util';
export var Pelias = L.Class.extend({
options: {
serviceUrl: 'https://api.geocode.earth/v1',
geocodingQueryParams: {},
reverseQueryParams: {}
},
initialize: function(apiKey, options) {
L.Util.setOptions(this, options);
this._apiKey = apiKey;
this._lastSuggest = 0;
},
geocode: function(query, cb, context) {
var _this = this;
getJSON(
this.options.serviceUrl + '/search',
L.extend(
{
api_key: this._apiKey,
text: query
},
this.options.geocodingQueryParams
),
function(data) {
cb.call(context, _this._parseResults(data, 'bbox'));
}
);
},
suggest: function(query, cb, context) {
var _this = this;
getJSON(
this.options.serviceUrl + '/autocomplete',
L.extend(
{
api_key: this._apiKey,
text: query
},
this.options.geocodingQueryParams
),
L.bind(function(data) {
if (data.geocoding.timestamp > this._lastSuggest) {
this._lastSuggest = data.geocoding.timestamp;
cb.call(context, _this._parseResults(data, 'bbox'));
}
}, this)
);
},
reverse: function(location, scale, cb, context) {
var _this = this;
getJSON(
this.options.serviceUrl + '/reverse',
L.extend(
{
api_key: this._apiKey,
'point.lat': location.lat,
'point.lon': location.lng
},
this.options.reverseQueryParams
),
function(data) {
cb.call(context, _this._parseResults(data, 'bounds'));
}
);
},
_parseResults: function(data, bboxname) {
var results = [];
L.geoJson(data, {
pointToLayer: function(feature, latlng) {
return L.circleMarker(latlng);
},
onEachFeature: function(feature, layer) {
var result = {},
bbox,
center;
if (layer.getBounds) {
bbox = layer.getBounds();
center = bbox.getCenter();
} else if (layer.feature.bbox) {
center = layer.getLatLng();
bbox = L.latLngBounds(
L.GeoJSON.coordsToLatLng(layer.feature.bbox.slice(0, 2)),
L.GeoJSON.coordsToLatLng(layer.feature.bbox.slice(2, 4))
);
} else {
center = layer.getLatLng();
bbox = L.latLngBounds(center, center);
}
result.name = layer.feature.properties.label;
result.center = center;
result[bboxname] = bbox;
result.properties = layer.feature.properties;
results.push(result);
}
});
return results;
}
});
export function pelias(apiKey, options) {
return new Pelias(apiKey, options);
}
export var GeocodeEarth = Pelias;
export var geocodeEarth = pelias;
export var Mapzen = Pelias; // r.i.p.
export var mapzen = pelias;
export var Openrouteservice = Mapzen.extend({
options: {
serviceUrl: 'https://api.openrouteservice.org/geocode'
}
});
export function openrouteservice(apiKey, options) {
return new Openrouteservice(apiKey, options);
}

View File

@@ -0,0 +1,103 @@
import L from 'leaflet';
import { getJSON } from '../util';
export var Photon = L.Class.extend({
options: {
serviceUrl: 'https://photon.komoot.de/api/',
reverseUrl: 'https://photon.komoot.de/reverse/',
nameProperties: ['name', 'street', 'suburb', 'hamlet', 'town', 'city', 'state', 'country']
},
initialize: function(options) {
L.setOptions(this, options);
},
geocode: function(query, cb, context) {
var params = L.extend(
{
q: query
},
this.options.geocodingQueryParams
);
getJSON(
this.options.serviceUrl,
params,
L.bind(function(data) {
cb.call(context, this._decodeFeatures(data));
}, this)
);
},
suggest: function(query, cb, context) {
return this.geocode(query, cb, context);
},
reverse: function(latLng, scale, cb, context) {
var params = L.extend(
{
lat: latLng.lat,
lon: latLng.lng
},
this.options.reverseQueryParams
);
getJSON(
this.options.reverseUrl,
params,
L.bind(function(data) {
cb.call(context, this._decodeFeatures(data));
}, this)
);
},
_decodeFeatures: function(data) {
var results = [],
i,
f,
c,
latLng,
extent,
bbox;
if (data && data.features) {
for (i = 0; i < data.features.length; i++) {
f = data.features[i];
c = f.geometry.coordinates;
latLng = L.latLng(c[1], c[0]);
extent = f.properties.extent;
if (extent) {
bbox = L.latLngBounds([extent[1], extent[0]], [extent[3], extent[2]]);
} else {
bbox = L.latLngBounds(latLng, latLng);
}
results.push({
name: this._decodeFeatureName(f),
html: this.options.htmlTemplate ? this.options.htmlTemplate(f) : undefined,
center: latLng,
bbox: bbox,
properties: f.properties
});
}
}
return results;
},
_decodeFeatureName: function(f) {
return (this.options.nameProperties || [])
.map(function(p) {
return f.properties[p];
})
.filter(function(v) {
return !!v;
})
.join(', ');
}
});
export function photon(options) {
return new Photon(options);
}

View File

@@ -0,0 +1,72 @@
import L from 'leaflet';
import { getJSON } from '../util';
export var What3Words = L.Class.extend({
options: {
serviceUrl: 'https://api.what3words.com/v2/'
},
initialize: function(accessToken) {
this._accessToken = accessToken;
},
geocode: function(query, cb, context) {
//get three words and make a dot based string
getJSON(
this.options.serviceUrl + 'forward',
{
key: this._accessToken,
addr: query.split(/\s+/).join('.')
},
function(data) {
var results = [],
latLng,
latLngBounds;
if (data.geometry) {
latLng = L.latLng(data.geometry['lat'], data.geometry['lng']);
latLngBounds = L.latLngBounds(latLng, latLng);
results[0] = {
name: data.words,
bbox: latLngBounds,
center: latLng
};
}
cb.call(context, results);
}
);
},
suggest: function(query, cb, context) {
return this.geocode(query, cb, context);
},
reverse: function(location, scale, cb, context) {
getJSON(
this.options.serviceUrl + 'reverse',
{
key: this._accessToken,
coords: [location.lat, location.lng].join(',')
},
function(data) {
var results = [],
latLng,
latLngBounds;
if (data.status.status == 200) {
latLng = L.latLng(data.geometry['lat'], data.geometry['lng']);
latLngBounds = L.latLngBounds(latLng, latLng);
results[0] = {
name: data.words,
bbox: latLngBounds,
center: latLng
};
}
cb.call(context, results);
}
);
}
});
export function what3words(accessToken) {
return new What3Words(accessToken);
}

View File

@@ -0,0 +1,11 @@
import L from 'leaflet';
import { Geocoder, geocoder } from './control';
import * as geocoders from './geocoders/index';
L.Util.extend(Geocoder, geocoders);
export default Geocoder;
L.Util.extend(L.Control, {
Geocoder: Geocoder,
geocoder: geocoder
});

View File

@@ -0,0 +1,104 @@
import L from 'leaflet';
var lastCallbackId = 0;
// Adapted from handlebars.js
// https://github.com/wycats/handlebars.js/
var badChars = /[&<>"'`]/g;
var possible = /[&<>"'`]/;
var escape = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#x27;',
'`': '&#x60;'
};
function escapeChar(chr) {
return escape[chr];
}
export function htmlEscape(string) {
if (string == null) {
return '';
} else if (!string) {
return string + '';
}
// Force a string conversion as this will be done by the append regardless and
// the regex test will do this transparently behind the scenes, causing issues if
// an object's to string has escaped characters in it.
string = '' + string;
if (!possible.test(string)) {
return string;
}
return string.replace(badChars, escapeChar);
}
export function jsonp(url, params, callback, context, jsonpParam) {
var callbackId = '_l_geocoder_' + lastCallbackId++;
params[jsonpParam || 'callback'] = callbackId;
window[callbackId] = L.Util.bind(callback, context);
var script = document.createElement('script');
script.type = 'text/javascript';
script.src = url + getParamString(params);
script.id = callbackId;
document.getElementsByTagName('head')[0].appendChild(script);
}
export function getJSON(url, params, callback) {
var xmlHttp = new XMLHttpRequest();
xmlHttp.onreadystatechange = function() {
if (xmlHttp.readyState !== 4) {
return;
}
var message;
if (xmlHttp.status !== 200 && xmlHttp.status !== 304) {
message = '';
} else if (typeof xmlHttp.response === 'string') {
// IE doesn't parse JSON responses even with responseType: 'json'.
try {
message = JSON.parse(xmlHttp.response);
} catch (e) {
// Not a JSON response
message = xmlHttp.response;
}
} else {
message = xmlHttp.response;
}
callback(message);
};
xmlHttp.open('GET', url + getParamString(params), true);
xmlHttp.responseType = 'json';
xmlHttp.setRequestHeader('Accept', 'application/json');
xmlHttp.send(null);
}
export function template(str, data) {
return str.replace(/\{ *([\w_]+) *\}/g, function(str, key) {
var value = data[key];
if (value === undefined) {
value = '';
} else if (typeof value === 'function') {
value = value(data);
}
return htmlEscape(value);
});
}
export function getParamString(obj, existingUrl, uppercase) {
var params = [];
for (var i in obj) {
var key = encodeURIComponent(uppercase ? i.toUpperCase() : i);
var value = obj[i];
if (!L.Util.isArray(value)) {
params.push(key + '=' + encodeURIComponent(value));
} else {
for (var j = 0; j < value.length; j++) {
params.push(key + '=' + encodeURIComponent(value[j]));
}
}
}
return (!existingUrl || existingUrl.indexOf('?') === -1 ? '?' : '&') + params.join('&');
}