diff --git a/spec/__snapshots__/nominatim.spec.ts.snap b/spec/__snapshots__/nominatim.spec.ts.snap index c12d617..b678951 100644 --- a/spec/__snapshots__/nominatim.spec.ts.snap +++ b/spec/__snapshots__/nominatim.spec.ts.snap @@ -32,10 +32,10 @@ exports[`L.Control.Geocoder.Nominatim > geocodes Innsbruck 1`] = ` "state": "Tyrol", }, "boundingbox": [ - 47.2583715, - 47.2808566, - 11.3811871, - 11.418183, + "47.2583715", + "47.2808566", + "11.3811871", + "11.418183", ], "class": "boundary", "display_name": "Innsbruck, Tyrol, Austria", diff --git a/spec/arcgis.spec.ts b/spec/arcgis.spec.ts index 8230b3a..4fc373b 100644 --- a/spec/arcgis.spec.ts +++ b/spec/arcgis.spec.ts @@ -1,12 +1,12 @@ -import { describe, expect, it, vi } from 'vitest'; -import { testXMLHttpRequest } from './mockXMLHttpRequest'; -import { ArcGis } from '../src/geocoders/arcgis'; +import { afterEach, describe, expect, it, vi } from 'vitest'; +import { mockFetchRequest } from './mockFetchRequest'; +import { ArcGis, ArcGisResponse } from '../src/geocoders/arcgis'; describe('L.Control.Geocoder.ArcGis', () => { - it('geocodes Innsbruck', () => { + afterEach(() => vi.clearAllMocks()); + it('geocodes Innsbruck', async () => { const geocoder = new ArcGis(); - const callback = vi.fn(); - testXMLHttpRequest( + const result = await mockFetchRequest( 'https://geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer/findAddressCandidates?token=&SingleLine=Innsbruck&outFields=Addr_Type&forStorage=false&maxLocations=10&f=json', { spatialReference: { wkid: 4326, latestWkid: 4326 }, @@ -24,17 +24,17 @@ describe('L.Control.Geocoder.ArcGis', () => { } } ] - }, - () => geocoder.geocode('Innsbruck', callback) + } satisfies ArcGisResponse, + () => geocoder.geocode('Innsbruck') ); - const feature = callback.mock.calls[0][0][0]; + const feature = result[0]; expect(feature.name).toBe('Innsbruck, Innsbruck-Stadt, Tirol'); expect(feature.center).toStrictEqual({ lat: 47.26800000000003, lng: 11.391300000000058 }); expect(feature.bbox).toStrictEqual({ _northEast: { lat: 47.34400000000003, lng: 11.467300000000058 }, _southWest: { lat: 47.19200000000003, lng: 11.315300000000057 } }); - expect(callback.mock.calls).toMatchSnapshot(); + expect([[result]]).toMatchSnapshot(); }); }); diff --git a/spec/google.spec.ts b/spec/google.spec.ts index 16226af..0654564 100644 --- a/spec/google.spec.ts +++ b/spec/google.spec.ts @@ -1,13 +1,13 @@ -import { describe, expect, it, vi } from 'vitest'; -import { testXMLHttpRequest } from './mockXMLHttpRequest'; -import { Google } from '../src/geocoders/google'; +import { afterEach, describe, expect, it, vi } from 'vitest'; +import { mockFetchRequest } from './mockFetchRequest'; +import { Google, GoogleResponse } from '../src/geocoders/google'; import { GeocodingResult } from '../src/geocoders/api'; describe('L.Control.Geocoder.Google', () => { - it('geocodes Innsbruck', () => { + afterEach(() => vi.clearAllMocks()); + it('geocodes Innsbruck', async () => { const geocoder = new Google({ apiKey: '0123xyz' }); - const callback = vi.fn(); - testXMLHttpRequest( + const result = await mockFetchRequest( 'https://maps.googleapis.com/maps/api/geocode/json?key=0123xyz&address=Innsbruck', { results: [ @@ -67,17 +67,17 @@ describe('L.Control.Geocoder.Google', () => { } ], status: 'OK' - }, - () => geocoder.geocode('Innsbruck', callback) + } satisfies GoogleResponse, + () => geocoder.geocode('Innsbruck') ); - const feature: GeocodingResult = callback.mock.calls[0][0][0]; + const feature: GeocodingResult = result[0]; expect(feature.name).toBe('Innsbruck, Austria'); expect(feature.center).toStrictEqual({ lat: 47.2692124, lng: 11.4041024 }); expect(feature.bbox).toStrictEqual({ _northEast: { lat: 47.3599301, lng: 11.45593 }, _southWest: { lat: 47.21098000000001, lng: 11.3016499 } }); - expect(callback.mock.calls).toMatchSnapshot(); + expect([[result]]).toMatchSnapshot(); }); }); diff --git a/spec/here.spec.ts b/spec/here.spec.ts index 88e4426..d765ed7 100644 --- a/spec/here.spec.ts +++ b/spec/here.spec.ts @@ -1,14 +1,14 @@ -import { describe, expect, it, vi } from 'vitest'; -import { testXMLHttpRequest } from './mockXMLHttpRequest'; -import { HERE } from '../src/geocoders/here'; +import { afterEach, describe, expect, it, vi } from 'vitest'; +import { mockFetchRequest } from './mockFetchRequest'; +import { HERE, HEREv2Response } from '../src/geocoders/here'; import { HEREv2 } from '../src/geocoders/here'; import { GeocodingResult } from '../src/geocoders/api'; describe('L.Control.Geocoder.HERE', () => { - it('geocodes Innsbruck', () => { + afterEach(() => vi.clearAllMocks()); + it('geocodes Innsbruck', async () => { const geocoder = new HERE({ app_id: 'xxx', app_code: 'yyy' }); - const callback = vi.fn(); - testXMLHttpRequest( + const result = await mockFetchRequest( 'https://geocoder.api.here.com/6.2/geocode.json?searchtext=Innsbruck&gen=9&app_id=xxx&app_code=yyy&jsonattributes=1&maxresults=5', { response: { @@ -74,26 +74,25 @@ describe('L.Control.Geocoder.HERE', () => { ] } }, - () => geocoder.geocode('Innsbruck', callback) + () => geocoder.geocode('Innsbruck') ); - const feature: GeocodingResult = callback.mock.calls[0][0][0]; + const feature: GeocodingResult = result[0]; expect(feature.name).toBe('Innsbruck, Tirol, Österreich'); expect(feature.center).toStrictEqual({ lat: 47.268, lng: 11.3913 }); expect(feature.bbox).toStrictEqual({ _northEast: { lat: 47.35922, lng: 11.45587 }, _southWest: { lat: 47.21082, lng: 11.30194 } }); - expect(callback.mock.calls).toMatchSnapshot(); + expect([[result]]).toMatchSnapshot(); }); }); describe('L.Control.Geocoder.HEREv2', () => { - it('geocodes Innsbruck', () => { + it('geocodes Innsbruck', async () => { const geocodingParams = { at: '50.62925,3.057256' }; const geocoder = new HEREv2({ apiKey: 'xxx', geocodingQueryParams: geocodingParams }); - const callback = vi.fn(); - testXMLHttpRequest( + const result = await mockFetchRequest( 'https://geocode.search.hereapi.com/v1/discover?q=Innsbruck&apiKey=xxx&limit=10&at=50.62925%2C3.057256', { items: [ @@ -163,15 +162,15 @@ describe('L.Control.Geocoder.HEREv2', () => { ] } ] - }, - () => geocoder.geocode('Innsbruck', callback) + } satisfies HEREv2Response, + () => geocoder.geocode('Innsbruck') ); - const feature: GeocodingResult = callback.mock.calls[0][0][0]; + const feature: GeocodingResult = result[0]; expect(feature.name).toBe( 'Salumeria Italiana, 151 Richmond St, Boston, MA 02109, United States' ); expect(feature.center).toStrictEqual({ lat: 42.36355, lng: -71.05439 }); - expect(callback.mock.calls).toMatchSnapshot(); + expect([[result]]).toMatchSnapshot(); }); }); diff --git a/spec/latlng.spec.ts b/spec/latlng.spec.ts index 3e5e3ad..5493722 100644 --- a/spec/latlng.spec.ts +++ b/spec/latlng.spec.ts @@ -1,8 +1,10 @@ -import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import * as L from 'leaflet'; import { LatLng } from '../src/geocoders/latlng'; +import { IGeocoder } from '../src/geocoders/api'; describe('LatLng', () => { + afterEach(() => vi.clearAllMocks()); // test cases from https://github.com/openstreetmap/openstreetmap-website/blob/master/test/controllers/geocoder_controller_test.rb let expected; beforeEach(() => { @@ -16,21 +18,20 @@ describe('LatLng', () => { geocode('+50.06773, +14.37742'); }); - it('does not geocode no-lat-lng', () => { + it('does not geocode no-lat-lng', async () => { const geocoder = new LatLng(); - const callback = vi.fn(); - geocoder.geocode('no-lat-lng', callback); - expect(callback).toHaveBeenCalledTimes(0); + const result = await geocoder.geocode('no-lat-lng'); + expect(result).toHaveLength(0); }); - it('passes unsupported queries to the next geocoder', () => { + it('passes unsupported queries to the next geocoder', async () => { + const xxx = [Symbol()] as any; const next = { - geocode: (_query, cb) => cb('XXX') - }; + geocode: _query => xxx + } as IGeocoder; const geocoder = new LatLng({ next: next }); - const callback = vi.fn(); - geocoder.geocode('no-lat-lng', callback); - expect(callback).toHaveBeenCalledWith('XXX'); + const result = await geocoder.geocode('no-lat-lng'); + expect(result).toBe(xxx); }); it('geocodes lat/lon pairs using N/E with degrees', () => { @@ -138,12 +139,10 @@ describe('LatLng', () => { geocode('50°4\'3.828"S 14°22\'38.712"W'); }); - function geocode(query) { + async function geocode(query) { const geocoder = new LatLng(); - const callback = vi.fn(); - geocoder.geocode(query, callback); - expect(callback).toBeCalledTimes(1); - const feature = callback.mock.calls[0][0][0]; + const result = await geocoder.geocode(query); + const feature = result[0]; expect(feature.name).toBe(query); expect(feature.center.lat).toBeCloseTo(expected.lat); expect(feature.center.lng).toBeCloseTo(expected.lng); diff --git a/spec/mapbox.spec.ts b/spec/mapbox.spec.ts index a0f6c21..43dbf0d 100644 --- a/spec/mapbox.spec.ts +++ b/spec/mapbox.spec.ts @@ -1,12 +1,12 @@ -import { describe, expect, it, vi } from 'vitest'; -import { testXMLHttpRequest } from './mockXMLHttpRequest'; -import { Mapbox } from '../src/geocoders/mapbox'; +import { afterEach, describe, expect, it, vi } from 'vitest'; +import { mockFetchRequest } from './mockFetchRequest'; +import { Mapbox, MapboxResponse } from '../src/geocoders/mapbox'; describe('L.Control.Geocoder.Mapbox', () => { - it('geocodes Milwaukee Ave', () => { + afterEach(() => vi.clearAllMocks()); + it('geocodes Milwaukee Ave', async () => { const geocoder = new Mapbox({ apiKey: '0123' }); - const callback = vi.fn(); - testXMLHttpRequest( + const result = await mockFetchRequest( 'https://api.mapbox.com/geocoding/v5/mapbox.places/Milwaukee%20Ave.json?access_token=0123', { type: 'FeatureCollection', @@ -62,17 +62,17 @@ describe('L.Control.Geocoder.Mapbox', () => { ], attribution: 'NOTICE: © 2018 Mapbox and its suppliers. All rights reserved. Use of this data is subject to the Mapbox Terms of Service (https://www.mapbox.com/about/maps/). This response and the information it contains may not be retained. POI(s) provided by Foursquare.' - }, - () => geocoder.geocode('Milwaukee Ave', callback) + } satisfies MapboxResponse, + () => geocoder.geocode('Milwaukee Ave') ); - const feature = callback.mock.calls[0][0][0]; + const feature = result[0]; expect(feature.name).toBe('825 Milwaukee Ave, Deerfield, Illinois 60015, United States'); expect(feature.center).toStrictEqual({ lat: 42.166602, lng: -87.921434 }); expect(feature.bbox).toStrictEqual({ _northEast: { lat: 42.166602, lng: -87.921434 }, _southWest: { lat: 42.166602, lng: -87.921434 } }); - expect(callback.mock.calls).toMatchSnapshot(); + expect([[result]]).toMatchSnapshot(); }); }); diff --git a/spec/mockFetchRequest.ts b/spec/mockFetchRequest.ts new file mode 100644 index 0000000..6d7411f --- /dev/null +++ b/spec/mockFetchRequest.ts @@ -0,0 +1,19 @@ +import { expect, vi } from 'vitest'; + +export async function mockFetchRequest( + url: string, + response: unknown, + trigger: () => T +): Promise { + const headers = { Accept: 'application/json' }; + global.fetch = vi.fn(() => + Promise.resolve({ + ok: true, + json: () => Promise.resolve(response) + }) + ) as any; + const result = await trigger(); + expect(fetch).toBeCalledTimes(1); + expect(fetch).toBeCalledWith(url, { headers }); + return result; +} diff --git a/spec/mockXMLHttpRequest.ts b/spec/mockXMLHttpRequest.ts deleted file mode 100644 index 1bb25ea..0000000 --- a/spec/mockXMLHttpRequest.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { expect, vi } from 'vitest'; - -export function mockXMLHttpRequest(response: T): XMLHttpRequest { - const xhrMock: Partial = { - open: vi.fn(), - send: vi.fn(), - setRequestHeader: vi.fn(), - readyState: 4, - status: 200, - response - }; - vi.spyOn(window, 'XMLHttpRequest').mockImplementation(() => xhrMock as XMLHttpRequest); - return xhrMock as XMLHttpRequest; -} - -export function testXMLHttpRequest(url: string, response: T, trigger: () => void) { - const xhrMock = mockXMLHttpRequest(response); - trigger(); - expect(xhrMock.open).toBeCalledWith('GET', url, true); - expect(xhrMock.setRequestHeader).toBeCalledWith('Accept', 'application/json'); - (xhrMock.onreadystatechange as EventListener)(new Event('')); -} diff --git a/spec/nominatim.spec.ts b/spec/nominatim.spec.ts index 1fecd11..4de236e 100644 --- a/spec/nominatim.spec.ts +++ b/spec/nominatim.spec.ts @@ -1,14 +1,13 @@ -import { describe, expect, it, vi } from 'vitest'; -import { testXMLHttpRequest } from './mockXMLHttpRequest'; -import { Nominatim } from '../src/geocoders/nominatim'; +import { afterEach, describe, expect, it, vi } from 'vitest'; +import { mockFetchRequest } from './mockFetchRequest'; +import { Nominatim, NominatimResponse } from '../src/geocoders/nominatim'; describe('L.Control.Geocoder.Nominatim', () => { + afterEach(() => vi.clearAllMocks()); const geocoder = new Nominatim(); - it('geocodes Innsbruck', () => { - const callback = vi.fn(); - - testXMLHttpRequest( + it('geocodes Innsbruck', async () => { + const result = await mockFetchRequest( 'https://nominatim.openstreetmap.org/search?q=innsbruck&limit=5&format=json&addressdetails=1', [ { @@ -23,8 +22,7 @@ describe('L.Control.Geocoder.Nominatim', () => { class: 'boundary', type: 'administrative', importance: 0.763909048330467, - icon: - 'https://nominatim.openstreetmap.org/images/mapicons/poi_boundary_administrative.p.20.png', + icon: 'https://nominatim.openstreetmap.org/images/mapicons/poi_boundary_administrative.p.20.png', address: { city_district: 'Innsbruck', city: 'Innsbruck', @@ -34,11 +32,11 @@ describe('L.Control.Geocoder.Nominatim', () => { country_code: 'at' } } - ], - () => geocoder.geocode('innsbruck', callback) + ] satisfies NominatimResponse, + () => geocoder.geocode('innsbruck') ); - const feature = callback.mock.calls[0][0][0]; + const feature = result[0]; expect(feature.name).toBe('Innsbruck, Tyrol, Austria'); expect(feature.html).toBe( ' Innsbruck
Tyrol Austria' @@ -51,13 +49,11 @@ describe('L.Control.Geocoder.Nominatim', () => { country: 'Austria', country_code: 'at' }); - expect(callback.mock.calls).toMatchSnapshot(); + expect([[result]]).toMatchSnapshot(); }); - it('reverse geocodes 47.3/11.3', () => { - const callback = vi.fn(); - - testXMLHttpRequest( + it('reverse geocodes 47.3/11.3', async () => { + const result = await mockFetchRequest( 'https://nominatim.openstreetmap.org/reverse?lat=47.3&lon=11.3&zoom=9&addressdetails=1&format=json', { place_id: 197718025, @@ -74,11 +70,11 @@ describe('L.Control.Geocoder.Nominatim', () => { country_code: 'at' }, boundingbox: ['46.9624854', '47.4499229', '10.9896868', '11.7051742'] - }, - () => geocoder.reverse({ lat: 47.3, lng: 11.3 }, 131000, callback) + } satisfies NominatimResponse[number], + () => geocoder.reverse({ lat: 47.3, lng: 11.3 }, 131000) ); - const feature = callback.mock.calls[0][0][0]; + const feature = result[0]; expect(feature.name).toBe('Innsbruck-Land, Tyrol, Austria'); expect(feature.html).toBe('Tyrol Austria'); expect(feature.properties.address).toStrictEqual({ @@ -87,6 +83,6 @@ describe('L.Control.Geocoder.Nominatim', () => { country: 'Austria', country_code: 'at' }); - expect(callback.mock.calls).toMatchSnapshot(); + expect([[result]]).toMatchSnapshot(); }); }); diff --git a/spec/open-location-code.spec.ts b/spec/open-location-code.spec.ts index 097fa9b..a02d1bf 100644 --- a/spec/open-location-code.spec.ts +++ b/spec/open-location-code.spec.ts @@ -6,19 +6,17 @@ import { OpenLocationCode as Geocoder } from '../src/geocoders/open-location-cod describe('L.Control.Geocoder.OpenLocationCode', () => { const geocoder = new Geocoder({ OpenLocationCode: OpenLocationCode }); - it('geocodes 9C3XGW4F+5V', () => { - const callback = vi.fn(); - geocoder.geocode('9C3XGW4F+5V', callback); - const feature = callback.mock.calls[0][0][0]; + it('geocodes 9C3XGW4F+5V', async () => { + const result = await geocoder.geocode('9C3XGW4F+5V'); + const feature = result[0]; expect(feature.name).toBe('9C3XGW4F+5V'); expect(feature.center.lat).toBeCloseTo(51.505437499999985); expect(feature.center.lng).toBeCloseTo(-0.07531249999998124); }); - it('reverse geocodes 47.3/11.3', () => { - const callback = vi.fn(); - geocoder.reverse({ lat: 47.3, lng: 11.3 }, 131000, callback); - const feature = callback.mock.calls[0][0][0]; + it('reverse geocodes 47.3/11.3', async () => { + const result = await geocoder.reverse({ lat: 47.3, lng: 11.3 }, 131000); + const feature = result[0]; expect(feature.name).toBe('8FVH8822+22'); }); }); diff --git a/spec/pelias.spec.ts b/spec/pelias.spec.ts index 32e8f1c..7d119b3 100644 --- a/spec/pelias.spec.ts +++ b/spec/pelias.spec.ts @@ -1,13 +1,13 @@ -import { describe, expect, it, vi } from 'vitest'; -import { testXMLHttpRequest } from './mockXMLHttpRequest'; -import { Openrouteservice } from '../src/geocoders/pelias'; +import { afterEach, describe, expect, it, vi } from 'vitest'; +import { mockFetchRequest } from './mockFetchRequest'; +import { Openrouteservice, PeliasResponse } from '../src/geocoders/pelias'; describe('L.Control.Geocoder.Openrouteservice', () => { + afterEach(() => vi.clearAllMocks()); const geocoder = new Openrouteservice({ apiKey: '0123' }); - it('geocodes Innsbruck', () => { - const callback = vi.fn(); - testXMLHttpRequest( + it('geocodes Innsbruck', async () => { + const result = await mockFetchRequest( 'https://api.openrouteservice.org/geocode/search?api_key=0123&text=innsbruck', { geocoding: { @@ -45,17 +45,17 @@ describe('L.Control.Geocoder.Openrouteservice', () => { } ], bbox: [10.9896885523, 46.9624806033, 11.7051690163, 47.4499185397] - }, - () => geocoder.geocode('innsbruck', callback) + } satisfies PeliasResponse, + () => geocoder.geocode('innsbruck') ); - const feature = callback.mock.calls[0][0][0]; + const feature = result[0]; expect(feature.name).toBe('Innsbruck, Austria'); expect(feature.center).toStrictEqual({ lat: 47.272308, lng: 11.407851 }); expect(feature.bbox).toStrictEqual({ _southWest: { lat: 47.2470573997, lng: 11.3218091258 }, _northEast: { lat: 47.29398, lng: 11.452584553 } }); - expect(callback.mock.calls).toMatchSnapshot(); + expect([[result]]).toMatchSnapshot(); }); }); diff --git a/spec/photon.spec.ts b/spec/photon.spec.ts index 463720a..3b14327 100644 --- a/spec/photon.spec.ts +++ b/spec/photon.spec.ts @@ -1,13 +1,13 @@ -import { describe, expect, it, vi } from 'vitest'; -import { testXMLHttpRequest } from './mockXMLHttpRequest'; -import { Photon } from '../src/geocoders/photon'; +import { afterEach, describe, expect, it, vi } from 'vitest'; +import { mockFetchRequest } from './mockFetchRequest'; +import { Photon, PhotonResponse } from '../src/geocoders/photon'; import { GeocodingResult } from '../src/geocoders/api'; describe('L.Control.Geocoder.Photon', () => { - it('geocodes Innsbruck', () => { + afterEach(() => vi.clearAllMocks()); + it('geocodes Innsbruck', async () => { const geocoder = new Photon(); - const callback = vi.fn(); - testXMLHttpRequest( + const result = await mockFetchRequest( 'https://photon.komoot.io/api/?q=Innsbruck', { features: [ @@ -50,17 +50,17 @@ describe('L.Control.Geocoder.Photon', () => { } ], type: 'FeatureCollection' - }, - () => geocoder.geocode('Innsbruck', callback) + } satisfies PhotonResponse, + () => geocoder.geocode('Innsbruck') ); - const feature: GeocodingResult = callback.mock.calls[0][0][0]; + const feature: GeocodingResult = result[0]; expect(feature.name).toBe('Innsbruck, Innsbruck, Tyrol, Austria'); expect(feature.center).toStrictEqual({ lat: 47.2654296, lng: 11.3927685 }); expect(feature.bbox).toStrictEqual({ _northEast: { lat: 47.2808566, lng: 11.4181209 }, _southWest: { lat: 47.2583715, lng: 11.3811871 } }); - expect(callback.mock.calls).toMatchSnapshot(); + expect([[result]]).toMatchSnapshot(); }); }); diff --git a/src/control.ts b/src/control.ts index 103e40c..02c8118 100644 --- a/src/control.ts +++ b/src/control.ts @@ -200,7 +200,7 @@ export class GeocoderControl extends EventedControl { L.DomEvent.disableClickPropagation(this._alts); L.DomEvent.addListener(input, 'keydown', this._keydown, this); - if (this.options.geocoder.suggest) { + if (this.options.geocoder?.suggest) { L.DomEvent.addListener(input, 'input', this._change, this); } L.DomEvent.addListener(input, 'blur', () => { @@ -303,21 +303,13 @@ export class GeocoderControl extends EventedControl { return this; } - private _geocode(suggest?: boolean) { + private async _geocode(suggest: boolean = false) { const value = this._input.value; if (!suggest && value.length < this.options.queryMinLength) { return; } const requestCount = ++this._requestCount; - const cb = (results: GeocodingResult[]) => { - if (requestCount === this._requestCount) { - const event: FinishGeocodeEvent = { input: value, results }; - this.fire(suggest ? 'finishsuggest' : 'finishgeocode', event); - this._geocodeResult(results, suggest); - } - }; - this._lastGeocode = value; if (!suggest) { this._clearResults(); @@ -325,10 +317,15 @@ export class GeocoderControl extends EventedControl { const event: StartGeocodeEvent = { input: value }; this.fire(suggest ? 'startsuggest' : 'startgeocode', event); - if (suggest) { - this.options.geocoder.suggest(value, cb); - } else { - this.options.geocoder.geocode(value, cb); + + const results = suggest + ? await this.options.geocoder!.suggest!(value) + : await this.options.geocoder!.geocode(value); + + if (requestCount === this._requestCount) { + const event: FinishGeocodeEvent = { input: value, results }; + this.fire(suggest ? 'finishsuggest' : 'finishgeocode', event); + this._geocodeResult(results, suggest); } } @@ -396,7 +393,7 @@ export class GeocoderControl extends EventedControl { }; if (icon) { - icon.src = result.icon; + icon.src = result.icon!; } li.setAttribute('data-result-index', String(index)); diff --git a/src/geocoders/api.ts b/src/geocoders/api.ts index 84dad38..0bdf642 100644 --- a/src/geocoders/api.ts +++ b/src/geocoders/api.ts @@ -30,37 +30,26 @@ export interface GeocodingResult { properties?: any; } -/** - * A callback function used in {@link IGeocoder.geocode} and {@link IGeocoder.suggest} and {@link IGeocoder.reverse} - */ -export type GeocodingCallback = (result: GeocodingResult[]) => void; - /** * An interface implemented to respond to geocoding queries */ export interface IGeocoder { /** - * Performs a geocoding query and returns the results to the callback in the provided context + * Performs a geocoding query and returns the results as promise * @param query the query - * @param cb the callback function - * @param context the `this` context in the callback */ - geocode(query: string, cb: GeocodingCallback, context?: any): void; + geocode(query: string): Promise; /** - * Performs a geocoding query suggestion (this happens while typing) and returns the results to the callback in the provided context + * Performs a geocoding query suggestion (this happens while typing) and returns the results as promise * @param query the query - * @param cb the callback function - * @param context the `this` context in the callback */ - suggest?(query: string, cb: GeocodingCallback, context?: any): void; + suggest?(query: string): Promise; /** - * Performs a reverse geocoding query and returns the results to the callback in the provided context + * Performs a reverse geocoding query and returns the results as promise * @param location the coordinate to reverse geocode * @param scale the map scale possibly used for reverse geocoding - * @param cb the callback function - * @param context the `this` context in the callback */ - reverse?(location: L.LatLngLiteral, scale: number, cb: GeocodingCallback, context?: any): void; + reverse?(location: L.LatLngLiteral, scale: number): Promise; } export interface GeocoderOptions { diff --git a/src/geocoders/arcgis.ts b/src/geocoders/arcgis.ts index 1af78f8..8e41744 100644 --- a/src/geocoders/arcgis.ts +++ b/src/geocoders/arcgis.ts @@ -1,16 +1,11 @@ import * as L from 'leaflet'; import { getJSON } from '../util'; -import { - IGeocoder, - GeocoderOptions, - GeocodingCallback, - geocodingParams, - GeocodingResult, - reverseParams -} from './api'; +import { IGeocoder, GeocoderOptions, geocodingParams, GeocodingResult, reverseParams } from './api'; export interface ArcGisOptions extends GeocoderOptions {} + + /** * Implementation of the [ArcGIS geocoder](https://developers.arcgis.com/features/geocoding/) */ @@ -24,7 +19,7 @@ export class ArcGis implements IGeocoder { L.Util.setOptions(this, options); } - geocode(query: string, cb: GeocodingCallback, context?: any): void { + async geocode(query: string): Promise { const params = geocodingParams(this.options, { token: this.options.apiKey, SingleLine: query, @@ -34,52 +29,53 @@ export class ArcGis implements IGeocoder { f: 'json' }); - getJSON(this.options.serviceUrl + '/findAddressCandidates', params, data => { - const results: GeocodingResult[] = []; - if (data.candidates && data.candidates.length) { - for (let i = 0; i <= data.candidates.length - 1; i++) { - const loc = data.candidates[i]; - const latLng = L.latLng(loc.location.y, loc.location.x); - const 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 - }; - } + const data = await getJSON( + this.options.serviceUrl + '/findAddressCandidates', + params + ); + const results: GeocodingResult[] = []; + if (data.candidates && data.candidates.length) { + for (let i = 0; i <= data.candidates.length - 1; i++) { + const loc = data.candidates[i]; + const latLng = L.latLng(loc.location.y, loc.location.x); + const 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); - }); + return results; } - suggest(query: string, cb: GeocodingCallback, context?: any): void { - return this.geocode(query, cb, context); + suggest(query: string): Promise { + return this.geocode(query); } - reverse(location: L.LatLngLiteral, scale: number, cb: GeocodingCallback, context?: any): void { + async reverse(location: L.LatLngLiteral, scale: number): Promise { const params = reverseParams(this.options, { location: location.lng + ',' + location.lat, distance: 100, f: 'json' }); - getJSON(this.options.serviceUrl + '/reverseGeocode', params, data => { - const result: GeocodingResult[] = []; - if (data && !data.error) { - const center = L.latLng(data.location.y, data.location.x); - const bbox = L.latLngBounds(center, center); - result.push({ - name: data.address.Match_addr, - center: center, - bbox: bbox - }); - } + const data = await getJSON(this.options.serviceUrl + '/reverseGeocode', params); + const result: GeocodingResult[] = []; + if (data && !data.error) { + const center = L.latLng(data.location.y, data.location.x); + const bbox = L.latLngBounds(center, center); + result.push({ + name: data.address.Match_addr, + center: center, + bbox: bbox + }); + } - cb.call(context, result); - }); + return result; } } @@ -90,3 +86,33 @@ export class ArcGis implements IGeocoder { export function arcgis(options?: Partial) { return new ArcGis(options); } + +/** + * @internal + */ +export interface ArcGisResponse { + spatialReference: { + wkid: number; + latestWkid: number; + }; + candidates: Candidate[]; +} + +interface Candidate { + address: string; + location: { + x: number; + y: number; + }; + score: number; + attributes: { + Addr_Type: string; + }; + extent: { + xmin: number; + ymin: number; + xmax: number; + ymax: number; + }; +} + diff --git a/src/geocoders/bing.ts b/src/geocoders/bing.ts index 0d13c31..082adeb 100644 --- a/src/geocoders/bing.ts +++ b/src/geocoders/bing.ts @@ -1,13 +1,6 @@ import * as L from 'leaflet'; import { getJSON } from '../util'; -import { - IGeocoder, - GeocoderOptions, - GeocodingCallback, - geocodingParams, - GeocodingResult, - reverseParams -} from './api'; +import { IGeocoder, GeocoderOptions, geocodingParams, GeocodingResult, reverseParams } from './api'; export interface BingOptions extends GeocoderOptions {} @@ -27,34 +20,14 @@ export class Bing implements IGeocoder { L.Util.setOptions(this, options); } - geocode(query: string, cb: GeocodingCallback, context?: any): void { + async geocode(query: string): Promise { const params = geocodingParams(this.options, { query: query, key: this.options.apiKey }); - getJSON(this.options.serviceUrl, params, data => { - const results: GeocodingResult[] = []; - if (data.resourceSets.length > 0) { - for (let i = data.resourceSets[0].resources.length - 1; i >= 0; i--) { - const 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); - }); - } - - reverse(location: L.LatLngLiteral, scale: number, cb: GeocodingCallback, context?: any): void { - const params = reverseParams(this.options, { - key: this.options.apiKey - }); - getJSON(this.options.serviceUrl + location.lat + ',' + location.lng, params, data => { - const results: GeocodingResult[] = []; + const data = await getJSON(this.options.serviceUrl, params); + const results: GeocodingResult[] = []; + if (data.resourceSets.length > 0) { for (let i = data.resourceSets[0].resources.length - 1; i >= 0; i--) { const resource = data.resourceSets[0].resources[i], bbox = resource.bbox; @@ -64,8 +37,29 @@ export class Bing implements IGeocoder { center: L.latLng(resource.point.coordinates) }; } - cb.call(context, results); + } + return results; + } + + async reverse(location: L.LatLngLiteral, scale: number): Promise { + const params = reverseParams(this.options, { + key: this.options.apiKey }); + const data = await getJSON( + this.options.serviceUrl + location.lat + ',' + location.lng, + params + ); + const results: GeocodingResult[] = []; + for (let i = data.resourceSets[0].resources.length - 1; i >= 0; i--) { + const 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) + }; + } + return results; } } diff --git a/src/geocoders/google.ts b/src/geocoders/google.ts index 85ae8d5..85e633b 100644 --- a/src/geocoders/google.ts +++ b/src/geocoders/google.ts @@ -1,13 +1,6 @@ import * as L from 'leaflet'; import { getJSON } from '../util'; -import { - IGeocoder, - GeocoderOptions, - GeocodingCallback, - geocodingParams, - GeocodingResult, - reverseParams -} from './api'; +import { IGeocoder, GeocoderOptions, geocodingParams, GeocodingResult, reverseParams } from './api'; /** * Implementation of the [Google Geocoding API](https://developers.google.com/maps/documentation/geocoding/) @@ -23,60 +16,58 @@ export class Google implements IGeocoder { L.Util.setOptions(this, options); } - geocode(query: string, cb: GeocodingCallback, context?: any): void { + async geocode(query: string): Promise { const params = geocodingParams(this.options, { key: this.options.apiKey, address: query }); - getJSON(this.options.serviceUrl, params, data => { - const results: GeocodingResult[] = []; - if (data.results && data.results.length) { - for (let i = 0; i <= data.results.length - 1; i++) { - const loc = data.results[i]; - const latLng = L.latLng(loc.geometry.location); - const 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 - }; - } + const data = await getJSON(this.options.serviceUrl, params); + const results: GeocodingResult[] = []; + if (data.results && data.results.length) { + for (let i = 0; i <= data.results.length - 1; i++) { + const loc = data.results[i]; + const latLng = L.latLng(loc.geometry.location); + const 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); - }); + return results; } - reverse(location: L.LatLngLiteral, scale: number, cb: GeocodingCallback, context?: any): void { + async reverse(location: L.LatLngLiteral, scale: number): Promise { const params = reverseParams(this.options, { key: this.options.apiKey, latlng: location.lat + ',' + location.lng }); - getJSON(this.options.serviceUrl, params, data => { - const results: GeocodingResult[] = []; - if (data.results && data.results.length) { - for (let i = 0; i <= data.results.length - 1; i++) { - const loc = data.results[i]; - const center = L.latLng(loc.geometry.location); - const bbox = L.latLngBounds( - L.latLng(loc.geometry.viewport.northeast), - L.latLng(loc.geometry.viewport.southwest) - ); - results[i] = { - name: loc.formatted_address, - bbox: bbox, - center: center, - properties: loc.address_components - }; - } + const data = await getJSON(this.options.serviceUrl, params); + const results: GeocodingResult[] = []; + if (data.results && data.results.length) { + for (let i = 0; i <= data.results.length - 1; i++) { + const loc = data.results[i]; + const center = L.latLng(loc.geometry.location); + const bbox = L.latLngBounds( + L.latLng(loc.geometry.viewport.northeast), + L.latLng(loc.geometry.viewport.southwest) + ); + results[i] = { + name: loc.formatted_address, + bbox: bbox, + center: center, + properties: loc.address_components + }; } + } - cb.call(context, results); - }); + return results; } } @@ -87,3 +78,42 @@ export class Google implements IGeocoder { export function google(options?: Partial) { return new Google(options); } + +/** + * @internal + */ +export interface GoogleResponse { + results: Result[]; + status: string; +} + +interface Result { + address_components: AddressComponent[]; + formatted_address: string; + geometry: Geometry; + place_id: string; + types: string[]; +} + +interface AddressComponent { + long_name: string; + short_name: string; + types: string[]; +} + +interface Geometry { + bounds: Bounds; + location: Location; + location_type: string; + viewport: Bounds; +} + +interface Bounds { + northeast: Location; + southwest: Location; +} + +interface Location { + lat: number; + lng: number; +} diff --git a/src/geocoders/here.ts b/src/geocoders/here.ts index 859d7d4..17db95f 100755 --- a/src/geocoders/here.ts +++ b/src/geocoders/here.ts @@ -1,13 +1,6 @@ import * as L from 'leaflet'; import { getJSON } from '../util'; -import { - IGeocoder, - GeocoderOptions, - GeocodingCallback, - geocodingParams, - GeocodingResult, - reverseParams -} from './api'; +import { IGeocoder, GeocoderOptions, geocodingParams, GeocodingResult, reverseParams } from './api'; export interface HereOptions extends GeocoderOptions { /** @@ -39,10 +32,10 @@ export class HERE implements IGeocoder { constructor(options?: Partial) { L.Util.setOptions(this, options); - if (options.apiKey) throw Error('apiKey is not supported, use app_id/app_code instead!'); + if (options?.apiKey) throw Error('apiKey is not supported, use app_id/app_code instead!'); } - geocode(query: string, cb: GeocodingCallback, context?: any): void { + geocode(query: string): Promise { const params = geocodingParams(this.options, { searchtext: query, gen: 9, @@ -51,10 +44,10 @@ export class HERE implements IGeocoder { jsonattributes: 1, maxresults: this.options.maxResults }); - this.getJSON(this.options.serviceUrl + 'geocode.json', params, cb, context); + return this.getJSON(this.options.serviceUrl + 'geocode.json', params); } - reverse(location: L.LatLngLiteral, scale: number, cb: GeocodingCallback, context?: any): void { + reverse(location: L.LatLngLiteral, scale: number): Promise { let prox = location.lat + ',' + location.lng; if (this.options.reverseGeocodeProxRadius) { prox += ',' + this.options.reverseGeocodeProxRadius; @@ -68,31 +61,30 @@ export class HERE implements IGeocoder { jsonattributes: 1, maxresults: this.options.maxResults }); - this.getJSON(this.options.serviceUrl + 'reversegeocode.json', params, cb, context); + return this.getJSON(this.options.serviceUrl + 'reversegeocode.json', params); } - getJSON(url: string, params: any, cb: GeocodingCallback, context?: any) { - getJSON(url, params, data => { - const results: GeocodingResult[] = []; - - if (data.response.view && data.response.view.length) { - for (let i = 0; i <= data.response.view[0].result.length - 1; i++) { - const loc = data.response.view[0].result[i].location; - const center = L.latLng(loc.displayPosition.latitude, loc.displayPosition.longitude); - const bbox = 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, - properties: loc.address, - bbox: bbox, - center: center - }; - } + async getJSON(url: string, params: any): Promise { + const data = await getJSON(url, params); + const results: GeocodingResult[] = []; + + if (data.response.view && data.response.view.length) { + for (let i = 0; i <= data.response.view[0].result.length - 1; i++) { + const loc = data.response.view[0].result[i].location; + const center = L.latLng(loc.displayPosition.latitude, loc.displayPosition.longitude); + const bbox = 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, + properties: loc.address, + bbox: bbox, + center: center + }; } - cb.call(context, results); - }); + } + return results; } } @@ -112,7 +104,7 @@ export class HEREv2 implements IGeocoder { L.Util.setOptions(this, options); } - geocode(query: string, cb: GeocodingCallback, context?: any): void { + geocode(query: string): Promise { const params = geocodingParams(this.options, { q: query, apiKey: this.options.apiKey, @@ -125,49 +117,48 @@ export class HEREv2 implements IGeocoder { ); } - this.getJSON(this.options.serviceUrl + '/discover', params, cb, context); + return this.getJSON(this.options.serviceUrl + '/discover', params); } - reverse(location: L.LatLngLiteral, scale: number, cb: GeocodingCallback, context?: any): void { + reverse(location: L.LatLngLiteral, scale: number): Promise { const params = reverseParams(this.options, { at: location.lat + ',' + location.lng, limit: this.options.reverseGeocodeProxRadius, apiKey: this.options.apiKey }); - this.getJSON(this.options.serviceUrl + '/revgeocode', params, cb, context); + return this.getJSON(this.options.serviceUrl + '/revgeocode', params); } - getJSON(url: string, params: any, cb: GeocodingCallback, context?: any) { - getJSON(url, params, data => { - const results: GeocodingResult[] = []; - - if (data.items && data.items.length) { - for (let i = 0; i <= data.items.length - 1; i++) { - const item = data.items[i]; - const latLng = L.latLng(item.position.lat, item.position.lng); - let bbox: L.LatLngBounds; - if (item.mapView) { - bbox = L.latLngBounds( - L.latLng(item.mapView.south, item.mapView.west), - L.latLng(item.mapView.north, item.mapView.east) - ); - } else { - // Using only position when not provided - bbox = L.latLngBounds( - L.latLng(item.position.lat, item.position.lng), - L.latLng(item.position.lat, item.position.lng) - ); - } - results[i] = { - name: item.address.label, - properties: item.address, - bbox: bbox, - center: latLng - }; + async getJSON(url: string, params: any): Promise { + const data = await getJSON(url, params); + const results: GeocodingResult[] = []; + + if (data.items && data.items.length) { + for (let i = 0; i <= data.items.length - 1; i++) { + const item = data.items[i]; + const latLng = L.latLng(item.position.lat, item.position.lng); + let bbox: L.LatLngBounds; + if (item.mapView) { + bbox = L.latLngBounds( + L.latLng(item.mapView.south, item.mapView.west), + L.latLng(item.mapView.north, item.mapView.east) + ); + } else { + // Using only position when not provided + bbox = L.latLngBounds( + L.latLng(item.position.lat, item.position.lng), + L.latLng(item.position.lat, item.position.lng) + ); } + results[i] = { + name: item.address.label, + properties: item.address, + bbox: bbox, + center: latLng + }; } - cb.call(context, results); - }); + } + return results; } } @@ -176,9 +167,97 @@ export class HEREv2 implements IGeocoder { * @param options the options */ export function here(options?: Partial) { - if (options.apiKey) { + if (options?.apiKey) { return new HEREv2(options); } else { return new HERE(options); } } + +/** + * @internal + */ +export interface HEREv2Response { + items: Item[]; +} + +interface Item { + title: string; + id: string; + ontologyId: string; + resultType: string; + address: Address; + mapView?: MapView; + position: Position; + access: Position[]; + distance: number; + categories: Category[]; + references: Reference[]; + foodTypes: Category[]; + contacts: Contact[]; + openingHours: OpeningHour[]; +} + +interface MapView { + east: number; + north: number; + south: number; + west: number; +} + +interface Position { + lat: number; + lng: number; +} + +interface Address { + label: string; + countryCode: string; + countryName: string; + stateCode: string; + state: string; + county: string; + city: string; + district: string; + street: string; + postalCode: string; + houseNumber: string; +} + +interface Category { + id: string; + name: string; + primary?: boolean; +} + +interface Contact { + phone: Email[]; + fax: Email[]; + www: Email[]; + email: Email[]; +} + +interface Email { + value: string; +} + +interface OpeningHour { + text: string[]; + isOpen: boolean; + structured: Structured[]; +} + +interface Structured { + start: string; + duration: string; + recurrence: string; +} + +interface Reference { + supplier: Supplier; + id: string; +} + +interface Supplier { + id: string; +} diff --git a/src/geocoders/latlng.ts b/src/geocoders/latlng.ts index ef93a5f..4deef2a 100644 --- a/src/geocoders/latlng.ts +++ b/src/geocoders/latlng.ts @@ -1,5 +1,5 @@ import * as L from 'leaflet'; -import { IGeocoder, GeocodingCallback, GeocodingResult } from './api'; +import { IGeocoder, GeocodingResult } from './api'; export interface LatLngOptions { /** @@ -92,7 +92,7 @@ export class LatLng implements IGeocoder { L.Util.setOptions(this, options); } - geocode(query: string, cb: GeocodingCallback, context?: any) { + async geocode(query: string) { const center = parseLatLng(query); if (center) { const results: GeocodingResult[] = [ @@ -102,9 +102,11 @@ export class LatLng implements IGeocoder { bbox: center.toBounds(this.options.sizeInMeters) } ]; - cb.call(context, results); + return results; } else if (this.options.next) { - this.options.next.geocode(query, cb, context); + return this.options.next.geocode(query); + } else { + return []; } } } diff --git a/src/geocoders/mapbox.ts b/src/geocoders/mapbox.ts index 1d5ea48..6705a59 100644 --- a/src/geocoders/mapbox.ts +++ b/src/geocoders/mapbox.ts @@ -1,13 +1,6 @@ import * as L from 'leaflet'; import { getJSON } from '../util'; -import { - IGeocoder, - GeocoderOptions, - GeocodingCallback, - geocodingParams, - GeocodingResult, - reverseParams -} from './api'; +import { IGeocoder, GeocoderOptions, geocodingParams, GeocodingResult, reverseParams } from './api'; export interface MapboxOptions extends GeocoderOptions {} @@ -41,7 +34,8 @@ export class Mapbox implements IGeocoder { return properties; } - geocode(query: string, cb: GeocodingCallback, context?: any): void { + async geocode(query: string): Promise { + const url = this.options.serviceUrl + encodeURIComponent(query) + '.json'; const params: any = geocodingParams(this.options, { access_token: this.options.apiKey }); @@ -52,70 +46,49 @@ export class Mapbox implements IGeocoder { ) { params.proximity = params.proximity.lng + ',' + params.proximity.lat; } - getJSON(this.options.serviceUrl + encodeURIComponent(query) + '.json', params, data => { - const results: GeocodingResult[] = []; - if (data.features && data.features.length) { - for (let i = 0; i <= data.features.length - 1; i++) { - const loc = data.features[i]; - const center = L.latLng(loc.center.reverse()); - let bbox: L.LatLngBounds; - if (loc.bbox) { - bbox = L.latLngBounds( - L.latLng(loc.bbox.slice(0, 2).reverse()), - L.latLng(loc.bbox.slice(2, 4).reverse()) - ); - } else { - bbox = L.latLngBounds(center, center); - } - - results[i] = { - name: loc.place_name, - bbox: bbox, - center: center, - properties: this._getProperties(loc) - }; - } - } - - cb.call(context, results); - }); + const data = await getJSON(url, params); + return this._parseResults(data); } - suggest(query: string, cb: GeocodingCallback, context?: any): void { - return this.geocode(query, cb, context); + suggest(query: string): Promise { + return this.geocode(query); } - reverse(location: L.LatLngLiteral, scale: number, cb: GeocodingCallback, context?: any): void { + async reverse(location: L.LatLngLiteral, scale: number): Promise { const url = this.options.serviceUrl + location.lng + ',' + location.lat + '.json'; const param = reverseParams(this.options, { access_token: this.options.apiKey }); - getJSON(url, param, data => { - const results: GeocodingResult[] = []; - if (data.features && data.features.length) { - for (let i = 0; i <= data.features.length - 1; i++) { - const loc = data.features[i]; - const center = L.latLng(loc.center.reverse()); - let bbox: L.LatLngBounds; - if (loc.bbox) { - bbox = L.latLngBounds( - L.latLng(loc.bbox.slice(0, 2).reverse()), - L.latLng(loc.bbox.slice(2, 4).reverse()) - ); - } else { - bbox = L.latLngBounds(center, center); - } - results[i] = { - name: loc.place_name, - bbox: bbox, - center: center, - properties: this._getProperties(loc) - }; - } + const data = await getJSON(url, param); + return this._parseResults(data); + } + + private _parseResults(data: MapboxResponse): any[] | GeocodingResult[] { + if (!data.features?.length) { + return []; + } + const results: GeocodingResult[] = []; + for (let i = 0; i <= data.features.length - 1; i++) { + const loc = data.features[i]; + const center = L.latLng(loc.center.reverse() as [number, number]); + let bbox: L.LatLngBounds; + if (loc.bbox) { + bbox = L.latLngBounds( + L.latLng(loc.bbox.slice(0, 2).reverse() as [number, number]), + L.latLng(loc.bbox.slice(2, 4).reverse() as [number, number]) + ); + } else { + bbox = L.latLngBounds(center, center); } + results[i] = { + name: loc.place_name, + bbox: bbox, + center: center, + properties: this._getProperties(loc) + }; + } - cb.call(context, results); - }); + return results; } } @@ -126,3 +99,46 @@ export class Mapbox implements IGeocoder { export function mapbox(options?: Partial) { return new Mapbox(options); } + +/** + * @internal + */ +export interface MapboxResponse { + type: string; + query: string[]; + features: Feature[]; + attribution: string; +} + +interface Feature { + id: string; + type: string; + place_type: string[]; + relevance: number; + properties: Properties; + text: string; + place_name: string; + matching_text: string; + matching_place_name: string; + center: [number, number]; + bbox?: [number, number, number, number]; + geometry: Geometry; + address: string; + context: Context[]; +} + +interface Context { + id: string; + text: string; + wikidata?: string; + short_code?: string; +} + +interface Geometry { + type: string; + coordinates: number[]; + interpolated: boolean; + omitted: boolean; +} + +interface Properties {} diff --git a/src/geocoders/mapquest.ts b/src/geocoders/mapquest.ts index dac8def..cfcc31c 100644 --- a/src/geocoders/mapquest.ts +++ b/src/geocoders/mapquest.ts @@ -1,13 +1,6 @@ import * as L from 'leaflet'; import { getJSON } from '../util'; -import { - IGeocoder, - GeocoderOptions, - GeocodingCallback, - geocodingParams, - GeocodingResult, - reverseParams -} from './api'; +import { IGeocoder, GeocoderOptions, geocodingParams, GeocodingResult, reverseParams } from './api'; export interface MapQuestOptions extends GeocoderOptions {} @@ -23,60 +16,48 @@ export class MapQuest implements IGeocoder { L.Util.setOptions(this, options); // MapQuest seems to provide URI encoded API keys, // so to avoid encoding them twice, we decode them here - this.options.apiKey = decodeURIComponent(this.options.apiKey); + this.options.apiKey = decodeURIComponent(this.options.apiKey!); } _formatName(...parts: string[]) { return parts.filter(s => !!s).join(', '); } - geocode(query: string, cb: GeocodingCallback, context?: any): void { + async geocode(query: string): Promise { const params = geocodingParams(this.options, { key: this.options.apiKey, location: query, limit: 5, outFormat: 'json' }); - getJSON(this.options.serviceUrl + '/address', params, data => { - const results: GeocodingResult[] = []; - if (data.results && data.results[0].locations) { - for (let i = data.results[0].locations.length - 1; i >= 0; i--) { - const loc = data.results[0].locations[i]; - const center = L.latLng(loc.latLng); - results[i] = { - name: this._formatName(loc.street, loc.adminArea4, loc.adminArea3, loc.adminArea1), - bbox: L.latLngBounds(center, center), - center: center - }; - } - } - - cb.call(context, results); - }); + const data = await getJSON(this.options.serviceUrl + '/address', params); + return this._parseResults(data); } - reverse(location: L.LatLngLiteral, scale: number, cb: GeocodingCallback, context?: any): void { + async reverse(location: L.LatLngLiteral, scale: number): Promise { const params = reverseParams(this.options, { key: this.options.apiKey, location: location.lat + ',' + location.lng, outputFormat: 'json' }); - getJSON(this.options.serviceUrl + '/reverse', params, data => { - const results: GeocodingResult[] = []; - if (data.results && data.results[0].locations) { - for (let i = data.results[0].locations.length - 1; i >= 0; i--) { - const loc = data.results[0].locations[i]; - const center = L.latLng(loc.latLng); - results[i] = { - name: this._formatName(loc.street, loc.adminArea4, loc.adminArea3, loc.adminArea1), - bbox: L.latLngBounds(center, center), - center: center - }; - } - } + const data = await getJSON(this.options.serviceUrl + '/reverse', params); + return this._parseResults(data); + } - cb.call(context, results); - }); + private _parseResults(data): GeocodingResult[] { + const results: GeocodingResult[] = []; + if (data.results && data.results[0].locations) { + for (let i = data.results[0].locations.length - 1; i >= 0; i--) { + const loc = data.results[0].locations[i]; + const center = L.latLng(loc.latLng); + results[i] = { + name: this._formatName(loc.street, loc.adminArea4, loc.adminArea3, loc.adminArea1), + bbox: L.latLngBounds(center, center), + center: center + }; + } + } + return results; } } diff --git a/src/geocoders/neutrino.ts b/src/geocoders/neutrino.ts index 1efe96b..e957177 100644 --- a/src/geocoders/neutrino.ts +++ b/src/geocoders/neutrino.ts @@ -1,13 +1,6 @@ import * as L from 'leaflet'; import { getJSON } from '../util'; -import { - IGeocoder, - GeocoderOptions, - GeocodingCallback, - geocodingParams, - GeocodingResult, - reverseParams -} from './api'; +import { IGeocoder, GeocoderOptions, geocodingParams, GeocodingResult, reverseParams } from './api'; export interface NeutrinoOptions extends GeocoderOptions { userId: string; @@ -18,8 +11,8 @@ export interface NeutrinoOptions extends GeocoderOptions { */ export class Neutrino implements IGeocoder { options: NeutrinoOptions = { - userId: undefined, - apiKey: undefined, + userId: '', + apiKey: '', serviceUrl: 'https://neutrinoapi.com/' }; @@ -28,55 +21,53 @@ export class Neutrino implements IGeocoder { } // https://www.neutrinoapi.com/api/geocode-address/ - geocode(query: string, cb: GeocodingCallback, context?: any): void { + async geocode(query: string): Promise { const params = geocodingParams(this.options, { apiKey: this.options.apiKey, userId: this.options.userId, //get three words and make a dot based string address: query.split(/\s+/).join('.') }); - getJSON(this.options.serviceUrl + 'geocode-address', params, data => { - const results: GeocodingResult[] = []; - if (data.locations) { - data.geometry = data.locations[0]; - const center = L.latLng(data.geometry['latitude'], data.geometry['longitude']); - const bbox = L.latLngBounds(center, center); - results[0] = { - name: data.geometry.address, - bbox: bbox, - center: center - }; - } + const data = await getJSON(this.options.serviceUrl + 'geocode-address', params); + const results: GeocodingResult[] = []; + if (data.locations) { + data.geometry = data.locations[0]; + const center = L.latLng(data.geometry['latitude'], data.geometry['longitude']); + const bbox = L.latLngBounds(center, center); + results[0] = { + name: data.geometry.address, + bbox: bbox, + center: center + }; + } - cb.call(context, results); - }); + return results; } - suggest(query: string, cb: GeocodingCallback, context?: any): void { - return this.geocode(query, cb, context); + suggest(query: string): Promise { + return this.geocode(query); } // https://www.neutrinoapi.com/api/geocode-reverse/ - reverse(location: L.LatLngLiteral, scale: number, cb: GeocodingCallback, context?: any): void { + async reverse(location: L.LatLngLiteral, scale: number): Promise { const params = reverseParams(this.options, { apiKey: this.options.apiKey, userId: this.options.userId, latitude: location.lat, longitude: location.lng }); - getJSON(this.options.serviceUrl + 'geocode-reverse', params, data => { - const results: GeocodingResult[] = []; - if (data.status.status == 200 && data.found) { - const center = L.latLng(location.lat, location.lng); - const bbox = L.latLngBounds(center, center); - results[0] = { - name: data.address, - bbox: bbox, - center: center - }; - } - cb.call(context, results); - }); + const data = await getJSON(this.options.serviceUrl + 'geocode-reverse', params); + const results: GeocodingResult[] = []; + if (data.status.status == 200 && data.found) { + const center = L.latLng(location.lat, location.lng); + const bbox = L.latLngBounds(center, center); + results[0] = { + name: data.address, + bbox: bbox, + center: center + }; + } + return results; } } diff --git a/src/geocoders/nominatim.ts b/src/geocoders/nominatim.ts index 0e2ab05..943de12 100644 --- a/src/geocoders/nominatim.ts +++ b/src/geocoders/nominatim.ts @@ -1,13 +1,8 @@ import * as L from 'leaflet'; import { template, getJSON } from '../util'; -import { - IGeocoder, - GeocoderOptions, - GeocodingCallback, - geocodingParams, - GeocodingResult, - reverseParams -} from './api'; +import { IGeocoder, GeocoderOptions, geocodingParams, GeocodingResult, reverseParams } from './api'; + +export type NominatimResponse = NominatimResult[]; export interface NominatimResult { place_id: number; @@ -18,9 +13,9 @@ export interface NominatimResult { lat: string; lon: string; display_name: string; - class: string; - type: string; - importance: number; + class?: string; + type?: string; + importance?: number; icon?: string; address: NominatimAddress; } @@ -67,7 +62,7 @@ export class Nominatim implements IGeocoder { htmlTemplate: function (r: NominatimResult) { const address = r.address; let className: string; - const parts = []; + const parts: string[] = []; if (address.road || address.building) { parts.push('{building} {road} {house_number}'); } @@ -92,32 +87,30 @@ export class Nominatim implements IGeocoder { L.Util.setOptions(this, options || {}); } - geocode(query: string, cb: GeocodingCallback, context?: any) { + async geocode(query: string) { const params = geocodingParams(this.options, { q: query, limit: 5, format: 'json', addressdetails: 1 }); - getJSON(this.options.serviceUrl + 'search', params, data => { - const results: GeocodingResult[] = []; - for (let i = data.length - 1; i >= 0; i--) { - const bbox = data[i].boundingbox; - for (let j = 0; j < 4; j++) bbox[j] = +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); - }); + const data = await getJSON(this.options.serviceUrl + 'search', params); + const results: GeocodingResult[] = []; + for (let i = data.length - 1; i >= 0; i--) { + const bbox = data[i].boundingbox; + 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] + }; + } + return results; } - reverse(location: L.LatLngLiteral, scale: number, cb: GeocodingCallback, context?: any) { + async reverse(location: L.LatLngLiteral, scale: number) { const params = reverseParams(this.options, { lat: location.lat, lon: location.lng, @@ -125,21 +118,20 @@ export class Nominatim implements IGeocoder { addressdetails: 1, format: 'json' }); - getJSON(this.options.serviceUrl + 'reverse', params, data => { - const result: GeocodingResult[] = []; - if (data && data.lat && data.lon) { - const center = L.latLng(data.lat, data.lon); - const bbox = L.latLngBounds(center, center); - result.push({ - name: data.display_name, - html: this.options.htmlTemplate ? this.options.htmlTemplate(data) : undefined, - center: center, - bbox: bbox, - properties: data - }); - } - cb.call(context, result); - }); + const data = await getJSON(this.options.serviceUrl + 'reverse', params); + const results: GeocodingResult[] = []; + if (data && data.lat && data.lon) { + const center = L.latLng(+data.lat, +data.lon); + const bbox = L.latLngBounds(center, center); + results.push({ + name: data.display_name, + html: this.options.htmlTemplate ? this.options.htmlTemplate(data) : undefined, + center: center, + bbox: bbox, + properties: data + }); + } + return results; } } diff --git a/src/geocoders/open-location-code.ts b/src/geocoders/open-location-code.ts index 205399c..ae0bdb5 100644 --- a/src/geocoders/open-location-code.ts +++ b/src/geocoders/open-location-code.ts @@ -1,5 +1,5 @@ import * as L from 'leaflet'; -import { IGeocoder, GeocodingCallback, GeocodingResult } from './api'; +import { IGeocoder, GeocodingResult } from './api'; export interface OpenLocationCodeOptions { OpenLocationCode: OpenLocationCodeApi; @@ -30,7 +30,7 @@ export class OpenLocationCode implements IGeocoder { L.Util.setOptions(this, options); } - geocode(query: string, cb: GeocodingCallback, context?: any) { + async geocode(query: string) { try { const decoded = this.options.OpenLocationCode.decode(query); const result: GeocodingResult = { @@ -41,13 +41,13 @@ export class OpenLocationCode implements IGeocoder { L.latLng(decoded.latitudeHi, decoded.longitudeHi) ) }; - cb.call(context, [result]); + return [result]; } catch (e) { console.warn(e); // eslint-disable-line no-console - cb.call(context, []); + return []; } } - reverse(location: L.LatLngLiteral, scale: number, cb: GeocodingCallback, context?: any) { + async reverse(location: L.LatLngLiteral, scale: number) { try { const code = this.options.OpenLocationCode.encode( location.lat, @@ -62,10 +62,10 @@ export class OpenLocationCode implements IGeocoder { L.latLng(location.lat, location.lng) ) }; - cb.call(context, [result]); + return [result]; } catch (e) { console.warn(e); // eslint-disable-line no-console - cb.call(context, []); + return []; } } } diff --git a/src/geocoders/opencage.ts b/src/geocoders/opencage.ts index af2c74c..a51175e 100644 --- a/src/geocoders/opencage.ts +++ b/src/geocoders/opencage.ts @@ -1,13 +1,6 @@ import * as L from 'leaflet'; import { getJSON } from '../util'; -import { - IGeocoder, - GeocoderOptions, - GeocodingCallback, - geocodingParams, - GeocodingResult, - reverseParams -} from './api'; +import { IGeocoder, GeocoderOptions, geocodingParams, GeocodingResult, reverseParams } from './api'; export interface OpenCageOptions extends GeocoderOptions {} @@ -23,70 +16,51 @@ export class OpenCage implements IGeocoder { L.Util.setOptions(this, options); } - geocode(query: string, cb: GeocodingCallback, context?: any): void { + async geocode(query: string): Promise { const params = geocodingParams(this.options, { key: this.options.apiKey, q: query }); - getJSON(this.options.serviceUrl, params, data => { - const results: GeocodingResult[] = []; - if (data.results && data.results.length) { - for (let i = 0; i < data.results.length; i++) { - const loc = data.results[i]; - const center = L.latLng(loc.geometry); - let bbox: L.LatLngBounds; - if (loc.annotations && loc.annotations.bounds) { - bbox = L.latLngBounds( - L.latLng(loc.annotations.bounds.northeast), - L.latLng(loc.annotations.bounds.southwest) - ); - } else { - bbox = L.latLngBounds(center, center); - } - results.push({ - name: loc.formatted, - bbox: bbox, - center: center - }); - } - } - cb.call(context, results); - }); + const data = await getJSON(this.options.serviceUrl, params); + return this._parseResults(data); } - suggest(query: string, cb: GeocodingCallback, context?: any): void { - return this.geocode(query, cb, context); + suggest(query: string): Promise { + return this.geocode(query); } - reverse(location: L.LatLngLiteral, scale: number, cb: GeocodingCallback, context?: any): void { + async reverse(location: L.LatLngLiteral, scale: number): Promise { const params = reverseParams(this.options, { key: this.options.apiKey, q: [location.lat, location.lng].join(',') }); - getJSON(this.options.serviceUrl, params, data => { - const results: GeocodingResult[] = []; - if (data.results && data.results.length) { - for (let i = 0; i < data.results.length; i++) { - const loc = data.results[i]; - const center = L.latLng(loc.geometry); - let bbox: L.LatLngBounds; - if (loc.annotations && loc.annotations.bounds) { - bbox = L.latLngBounds( - L.latLng(loc.annotations.bounds.northeast), - L.latLng(loc.annotations.bounds.southwest) - ); - } else { - bbox = L.latLngBounds(center, center); - } - results.push({ - name: loc.formatted, - bbox: bbox, - center: center - }); + const data = await getJSON(this.options.serviceUrl, params); + return this._parseResults(data); + } + + private _parseResults(data): GeocodingResult[] { + const results: GeocodingResult[] = []; + if (data.results && data.results.length) { + for (let i = 0; i < data.results.length; i++) { + const loc = data.results[i]; + const center = L.latLng(loc.geometry); + let bbox: L.LatLngBounds; + if (loc.annotations && loc.annotations.bounds) { + bbox = L.latLngBounds( + L.latLng(loc.annotations.bounds.northeast), + L.latLng(loc.annotations.bounds.southwest) + ); + } else { + bbox = L.latLngBounds(center, center); } + results.push({ + name: loc.formatted, + bbox: bbox, + center: center + }); } - cb.call(context, results); - }); + } + return results; } } diff --git a/src/geocoders/pelias.ts b/src/geocoders/pelias.ts index 42b7c04..7e73554 100644 --- a/src/geocoders/pelias.ts +++ b/src/geocoders/pelias.ts @@ -1,13 +1,6 @@ import * as L from 'leaflet'; import { getJSON } from '../util'; -import { - IGeocoder, - GeocoderOptions, - GeocodingCallback, - geocodingParams, - GeocodingResult, - reverseParams -} from './api'; +import { IGeocoder, GeocoderOptions, geocodingParams, GeocodingResult, reverseParams } from './api'; export interface PeliasOptions extends GeocoderOptions {} @@ -19,47 +12,39 @@ export class Pelias implements IGeocoder { serviceUrl: 'https://api.geocode.earth/v1' }; - private _lastSuggest = 0; - constructor(options?: Partial) { L.Util.setOptions(this, options); } - geocode(query: string, cb: GeocodingCallback, context?: any): void { + async geocode(query: string): Promise { const params = geocodingParams(this.options, { api_key: this.options.apiKey, text: query }); - getJSON(this.options.serviceUrl + '/search', params, data => { - cb.call(context, this._parseResults(data, 'bbox')); - }); + const data = await getJSON(this.options.serviceUrl + '/search', params); + return this._parseResults(data, 'bbox'); } - suggest(query: string, cb: GeocodingCallback, context?: any): void { + async suggest(query: string): Promise { const params = geocodingParams(this.options, { api_key: this.options.apiKey, text: query }); - getJSON(this.options.serviceUrl + '/autocomplete', params, data => { - if (data.geocoding.timestamp > this._lastSuggest) { - this._lastSuggest = data.geocoding.timestamp; - cb.call(context, this._parseResults(data, 'bbox')); - } - }); + const data = await getJSON(this.options.serviceUrl + '/autocomplete', params); + return this._parseResults(data, 'bbox'); } - reverse(location: L.LatLngLiteral, scale: number, cb: GeocodingCallback, context?: any): void { + async reverse(location: L.LatLngLiteral, scale: number): Promise { const params = reverseParams(this.options, { api_key: this.options.apiKey, 'point.lat': location.lat, 'point.lon': location.lng }); - getJSON(this.options.serviceUrl + '/reverse', params, data => { - cb.call(context, this._parseResults(data, 'bounds')); - }); + const data = await getJSON(this.options.serviceUrl + '/reverse', params); + return this._parseResults(data, 'bounds'); } - _parseResults(data, bboxname) { + _parseResults(data, bboxname): GeocodingResult[] { const results: GeocodingResult[] = []; L.geoJSON(data, { pointToLayer(feature, latlng) { @@ -140,3 +125,47 @@ export class Openrouteservice extends Pelias { export function openrouteservice(options?: Partial) { return new Openrouteservice(options); } + +/** + * @internal + */ +export type PeliasResponse = GeoJSON.FeatureCollection & { + geocoding: Geocoding; +}; + +interface Properties { + id: string; + layer: string; + source_id: string; + name: string; + confidence: number; + match_type: string; + accuracy: string; + country: string; + country_a: string; + region: string; + region_a: string; + county: string; + county_a: string; + localadmin: string; + locality: string; + continent: string; + label: string; +} + +interface Geocoding { + version: string; + attribution: string; + query: Query; + warnings: string[]; + engine: Engine; +} + +interface Engine { + name: string; + author: string; + version: string; +} + +interface Query { +} diff --git a/src/geocoders/photon.ts b/src/geocoders/photon.ts index 77791b4..cacdcdd 100644 --- a/src/geocoders/photon.ts +++ b/src/geocoders/photon.ts @@ -1,13 +1,6 @@ import * as L from 'leaflet'; import { getJSON } from '../util'; -import { - IGeocoder, - GeocoderOptions, - GeocodingCallback, - geocodingParams, - GeocodingResult, - reverseParams -} from './api'; +import { IGeocoder, GeocoderOptions, geocodingParams, GeocodingResult, reverseParams } from './api'; export interface PhotonOptions extends GeocoderOptions { reverseUrl: string; @@ -29,28 +22,26 @@ export class Photon implements IGeocoder { L.Util.setOptions(this, options); } - geocode(query: string, cb: GeocodingCallback, context?: any): void { + async geocode(query: string): Promise { const params = geocodingParams(this.options, { q: query }); - getJSON(this.options.serviceUrl, params, data => { - cb.call(context, this._decodeFeatures(data)); - }); + const data = await getJSON(this.options.serviceUrl, params); + return this._parseResults(data); } - suggest(query: string, cb: GeocodingCallback, context?: any): void { - return this.geocode(query, cb, context); + suggest(query: string): Promise { + return this.geocode(query); } - reverse(latLng: L.LatLngLiteral, scale: number, cb: GeocodingCallback, context?: any): void { + async reverse(latLng: L.LatLngLiteral, scale: number): Promise { const params = reverseParams(this.options, { lat: latLng.lat, lon: latLng.lng }); - getJSON(this.options.reverseUrl, params, data => { - cb.call(context, this._decodeFeatures(data)); - }); + const data = await getJSON(this.options.reverseUrl, params); + return this._parseResults(data); } - _decodeFeatures(data: GeoJSON.FeatureCollection) { + _parseResults(data: GeoJSON.FeatureCollection): GeocodingResult[] { const results: GeocodingResult[] = []; if (data && data.features) { @@ -58,7 +49,7 @@ export class Photon implements IGeocoder { const f = data.features[i]; const c = f.geometry.coordinates; const center = L.latLng(c[1], c[0]); - const extent = f.properties.extent; + const extent = f.properties?.extent; const bbox = extent ? L.latLngBounds([extent[1], extent[0]], [extent[3], extent[2]]) @@ -79,12 +70,8 @@ export class Photon implements IGeocoder { _decodeFeatureName(f: GeoJSON.Feature) { return (this.options.nameProperties || []) - .map(p => { - return f.properties[p]; - }) - .filter(v => { - return !!v; - }) + .map(p => f.properties?.[p]) + .filter(v => !!v) .join(', '); } } @@ -96,3 +83,26 @@ export class Photon implements IGeocoder { export function photon(options?: Partial) { return new Photon(options); } + +/** + * @internal + */ +export type PhotonResponse = GeoJSON.FeatureCollection; + +interface PhotonProperties { + osm_id: number; + osm_type: string; + extent?: number[]; + country: string; + osm_key: string; + city: string; + countrycode: string; + osm_value: string; + name: string; + state: string; + type: string; + postcode?: string; + housenumber?: string; + street?: string; + district?: string; +} diff --git a/src/geocoders/what3words.ts b/src/geocoders/what3words.ts index 8d86a6e..c3ed0dd 100644 --- a/src/geocoders/what3words.ts +++ b/src/geocoders/what3words.ts @@ -1,13 +1,6 @@ import * as L from 'leaflet'; import { getJSON } from '../util'; -import { - IGeocoder, - GeocoderOptions, - GeocodingCallback, - geocodingParams, - GeocodingResult, - reverseParams -} from './api'; +import { IGeocoder, GeocoderOptions, geocodingParams, GeocodingResult, reverseParams } from './api'; export interface What3WordsOptions extends GeocoderOptions {} @@ -23,56 +16,51 @@ export class What3Words implements IGeocoder { L.Util.setOptions(this, options); } - geocode(query: string, cb: GeocodingCallback, context?: any): void { - //get three words and make a dot based string - getJSON( + async geocode(query: string): Promise { + const data = await getJSON( this.options.serviceUrl + 'forward', geocodingParams(this.options, { key: this.options.apiKey, + //get three words and make a dot based string addr: query.split(/\s+/).join('.') - }), - data => { - const results: GeocodingResult[] = []; - if (data.geometry) { - const latLng = L.latLng(data.geometry['lat'], data.geometry['lng']); - const latLngBounds = L.latLngBounds(latLng, latLng); - results[0] = { - name: data.words, - bbox: latLngBounds, - center: latLng - }; - } - - cb.call(context, results); - } + }) ); + const results: GeocodingResult[] = []; + if (data.geometry) { + const latLng = L.latLng(data.geometry['lat'], data.geometry['lng']); + const latLngBounds = L.latLngBounds(latLng, latLng); + results[0] = { + name: data.words, + bbox: latLngBounds, + center: latLng + }; + } + return results; } - suggest(query: string, cb: GeocodingCallback, context?: any): void { - return this.geocode(query, cb, context); + suggest(query: string): Promise { + return this.geocode(query); } - reverse(location: L.LatLngLiteral, scale: number, cb: GeocodingCallback, context?: any): void { - getJSON( + async reverse(location: L.LatLngLiteral, scale: number): Promise { + const data = await getJSON( this.options.serviceUrl + 'reverse', reverseParams(this.options, { key: this.options.apiKey, coords: [location.lat, location.lng].join(',') - }), - data => { - const results: GeocodingResult[] = []; - if (data.status.status == 200) { - const center = L.latLng(data.geometry['lat'], data.geometry['lng']); - const bbox = L.latLngBounds(center, center); - results[0] = { - name: data.words, - bbox: bbox, - center: center - }; - } - cb.call(context, results); - } + }) ); + const results: GeocodingResult[] = []; + if (data.status.status == 200) { + const center = L.latLng(data.geometry['lat'], data.geometry['lng']); + const bbox = L.latLngBounds(center, center); + results[0] = { + name: data.words, + bbox: bbox, + center: center + }; + } + return results; } } diff --git a/src/util.ts b/src/util.ts index 0bf149c..f4e9379 100644 --- a/src/util.ts +++ b/src/util.ts @@ -51,36 +51,15 @@ export function htmlEscape(string?: string): string { /** * @internal */ -export function getJSON( - url: string, - params: Record, - callback: (message: any) => void -): void { - const xmlHttp = new XMLHttpRequest(); - xmlHttp.onreadystatechange = () => { - if (xmlHttp.readyState !== 4) { - return; - } - let 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 getJSON(url: string, params: Record): Promise { + const headers = { Accept: 'application/json' }; + const request = new URL(url); + Object.entries(params).forEach(([key, value]) => { + (Array.isArray(value) ? value : [value]).forEach(v => { + request.searchParams.append(key, v); + }); + }); + return fetch(request.toString(), { headers }).then(response => response.json()); } /** @@ -97,26 +76,3 @@ export function template(str: string, data: Record): string { return htmlEscape(value); }); } - -/** - * @internal - */ -export function getParamString( - obj: Record, - existingUrl?: string, - uppercase?: boolean -): string { - const params = []; - for (const i in obj) { - const key = encodeURIComponent(uppercase ? i.toUpperCase() : i); - const value = obj[i]; - if (!Array.isArray(value)) { - params.push(key + '=' + encodeURIComponent(String(value))); - } else { - for (let j = 0; j < value.length; j++) { - params.push(key + '=' + encodeURIComponent(value[j])); - } - } - } - return (!existingUrl || existingUrl.indexOf('?') === -1 ? '?' : '&') + params.join('&'); -} diff --git a/tsconfig.json b/tsconfig.json index 6fb01f0..c948db2 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -15,6 +15,7 @@ /* Linting */ "strict": false, + "strictNullChecks": true, "noUnusedLocals": true, "noUnusedParameters": false, "noFallthroughCasesInSwitch": true,