Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ The control comes with a number of default providers:
- [LocationIQ]
- [OpenCage]
- [OpenStreetMap]
- [PdokNl]
- [Pelias]
- [Mapbox](https://docs.mapbox.com/help/tutorials/local-search-geocoding-api/)
- [GeoApiFR](https://geo.api.gouv.fr/adresse)
Expand Down Expand Up @@ -352,6 +353,7 @@ refresh when you change source files.
[locationiq]: https://leaflet-geosearch.meijer.works/providers/locationiq
[opencage]: https://leaflet-geosearch.meijer.works/providers/opencage
[openstreetmap]: https://leaflet-geosearch.meijer.works/providers/openstreetmap
[pdoknl]: https://leaflet-geosearch.meijer.works/providers/pdoknl
[pelias]: https://leaflet-geosearch.meijer.works/providers/pelias

## Contributors ✨
Expand Down
3 changes: 3 additions & 0 deletions docs/lib/providers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
LocationIQProvider,
OpenCageProvider,
OpenStreetMapProvider,
PdokNlProvider,
PeliasProvider,
} from 'leaflet-geosearch';

Expand Down Expand Up @@ -54,5 +55,7 @@ export default {

OpenStreetMap: new OpenStreetMapProvider(),

PdokNl: new PdokNlProvider(),

Pelias: new PeliasProvider(),
};
51 changes: 51 additions & 0 deletions docs/providers/pdoknl.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
---
name: PdokNl
menu: Providers
route: /providers/pdoknl
---

import Playground from '../components/Playground';
import Map from '../components/Map';

# PdokNl Provider

For more options and configurations, see the [PDOK Locatieserver][1].

<Playground>
<Map provider="PdokNl" />
</Playground>

```js
import { PdokNlProvider } from 'leaflet-geosearch';

const provider = new PdokNlProvider();

// add to leaflet
import { GeoSearchControl } from 'leaflet-geosearch';

map.addControl(
new GeoSearchControl({
provider,
style: 'bar',
}),
);
```

## Optional parameters

PDOK Locatieserver supports a number of [optional parameters][2]. As the api requires those parameters to be added to the url, they can be added to the `params` key of the provider.

All options defined next to the `params` key, would have been added to the request body.

```js
const provider = new PdokNlProvider({
params: {
fq: 'gemeentenaam:Leiden', // limit search results to municipality
lat: 0, // Latitude in degree
lon: 0, // Longitude in degree
},
});
```

[1]: https://www.pdok.nl/pdok-locatieserver
[2]: https://api.pdok.nl/bzk/locatieserver/search/v3_1/ui/#/Locatieserver/suggest
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,6 @@ export { default as MapBoxProvider } from './providers/mapBoxProvider';
export { default as OpenCageProvider } from './providers/openCageProvider';
export { default as OpenStreetMapProvider } from './providers/openStreetMapProvider';
export { default as PeliasProvider } from './providers/peliasProvider';
export { default as PdokNlProvider } from './providers/pdokNlProvider';

export { default as JsonProvider } from './providers/provider';
25 changes: 25 additions & 0 deletions src/providers/__tests__/pdokNlProvider.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import Provider from '../pdokNlProvider';
import fixtures from './pdokNlResponse.json';

describe('PdokNlProvider', () => {
beforeAll(() => {
fetch.mockResponse(async () => ({ body: JSON.stringify(fixtures) }));
});

test('Can fetch results', async () => {
const provider = new Provider();

const results = await provider.search({ query: '3e Binnenvestgracht' });
const result = results[0];

const position = fixtures.response.docs[0].centroide_ll
.replace(/POINT\(|\)/g, '')
.trim()
.split(' ')
.map(Number);

expect(result.label).toBeTruthy();
expect(result.x).toEqual(position[0]);
expect(result.y).toEqual(position[1]);
});
});
64 changes: 64 additions & 0 deletions src/providers/__tests__/pdokNlResponse.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
{
"response": {
"numFound": 49,
"start": 0,
"maxScore": 10.934887,
"numFoundExact": true,
"docs": [
{
"weergavenaam": "3e Binnenvestgracht, 2312NR Leiden",
"id": "pcd-f14950155195f418398b6eda76d66adb",
"centroide_ll": "POINT(4.49093267 52.16412132)"
},
{
"weergavenaam": "3e Binnenvestgracht 23L-1, 2312NR Leiden",
"id": "adr-37dba6d7e92c0ccca8fc738005057bd9",
"centroide_ll": "POINT(4.49157312 52.16420333)"
},
{
"weergavenaam": "3e Binnenvestgracht 23L-2, 2312NR Leiden",
"id": "adr-fd7be59d3e4ea77dafba1e8fd69017e6",
"centroide_ll": "POINT(4.49157 52.16425474)"
},
{
"weergavenaam": "3e Binnenvestgracht 23T-1, 2312NR Leiden",
"id": "adr-763525b75ad60b4e13fdf3d00b195912",
"centroide_ll": "POINT(4.49202289 52.164203)"
},
{
"weergavenaam": "3e Binnenvestgracht 23A, 2312NR Leiden",
"id": "adr-6d98d9468e29425d0ad81dcfdbe00f7d",
"centroide_ll": "POINT(4.49098397 52.16416908)"
}
]
},
"highlighting": {
"pcd-f14950155195f418398b6eda76d66adb": {
"suggest": ["<b>3e</b> <b>Binnenvestgracht</b>, <b>2312</b> NR Leiden"]
},
"adr-37dba6d7e92c0ccca8fc738005057bd9": {
"suggest": [
"<b>3e</b> <b>Binnenvestgracht</b> <b>23L</b>-1, <b>2312NR</b> Leiden"
]
},
"adr-fd7be59d3e4ea77dafba1e8fd69017e6": {
"suggest": [
"<b>3e</b> <b>Binnenvestgracht</b> <b>23L</b>-2, <b>2312NR</b> Leiden"
]
},
"adr-763525b75ad60b4e13fdf3d00b195912": {
"suggest": [
"<b>3e</b> <b>Binnenvestgracht</b> <b>23T</b>-1, <b>2312NR</b> Leiden"
]
},
"adr-6d98d9468e29425d0ad81dcfdbe00f7d": {
"suggest": [
"<b>3e</b> <b>Binnenvestgracht</b> <b>23A</b>, <b>2312NR</b> Leiden"
]
}
},
"spellcheck": {
"suggestions": [],
"collations": []
}
}
1 change: 1 addition & 0 deletions src/providers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export { default as LocationIQProvider } from './locationIQProvider';
export { default as MapBoxProvider } from './mapBoxProvider';
export { default as OpenCageProvider } from './openCageProvider';
export { default as OpenStreetMapProvider } from './openStreetMapProvider';
export { default as PdokNlProvider } from './pdokNlProvider';
export { default as PeliasProvider } from './peliasProvider';

export { default as Provider } from './provider';
143 changes: 143 additions & 0 deletions src/providers/pdokNlProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import AbstractProvider, {
EndpointArgument,
ParseArgument,
ProviderOptions,
SearchResult,
RequestType,
SearchArgument,
} from './provider';

interface Doc {
weergavenaam: string;
id: string;
centroide_ll: string;
}

export interface RequestResult {
response: {
numFound: number;
start: number;
maxScore: number;
numFoundExact: boolean;
docs: Doc[];
};
highlighting: {
[key: string]: {
suggest: string[];
};
};
spellcheck: {
suggestions: [
string,
{
numFound: number;
startOffset: number;
endOffset: number;
suggestion: string[];
},
];
collations: [
'collation',
{
collationQuery: string;
hits: number;
misspellingsAndCorrections: string[];
},
];
};
}

export interface RawResult extends Doc {
highlight: string;
}

export type PdokNlProviderOptions = ProviderOptions;

export default class PdokNlProvider extends AbstractProvider<
RequestResult,
RawResult
> {
searchUrl: string;
reverseUrl: string;

constructor(options: PdokNlProviderOptions = {}) {
super({
...options,
params: {
bq: 'type:gemeente^0.5 type:woonplaats^0.5 type:weg^1.0 type:postcode^1.5 type:adres^2.0',
fl: 'id,weergavenaam,centroide_ll',
rows: 5,
...options.params,
},
});

const base = 'https://api.pdok.nl/bzk/locatieserver/search/v3_1';
this.searchUrl = `${base}/suggest`;
this.reverseUrl = `${base}/reverse`;
}

endpoint({ query, type }: EndpointArgument) {
const params = typeof query === 'string' ? { q: query } : query;

switch (type) {
case RequestType.REVERSE:
return this.getUrl(this.reverseUrl, params);

default:
return this.getUrl(this.searchUrl, params);
}
}

parse({ data }: ParseArgument<RequestResult>) {
return data.response.docs.map((doc) => {
const position = doc.centroide_ll
.replace(/POINT\(|\)/g, '')
.trim()
.split(' ')
.map(Number);

let highlight = doc.weergavenaam;
if (data.highlighting[doc.id].suggest) {
highlight = data.highlighting[doc.id].suggest[0];
}

return {
x: position[0],
y: position[1],
label: doc.weergavenaam,
bounds: null,
raw: {
...doc,
highlight,
},
};
});
}

async search(options: SearchArgument): Promise<SearchResult<RawResult>[]> {
const url = this.endpoint({
query: options.query,
});

const json = await fetch(url)
.then((response) => response.json())
.then((json: RequestResult) => {
// If there are no results found but there are spellcheck corrections, we search again using the corrected query.
if (json.response.numFound < 1) {
if (json.spellcheck.collations.length >= 2) {
const url = this.endpoint({
query: json.spellcheck.collations[1].collationQuery,
});

return fetch(url).then(
(response): Promise<RequestResult> => response.json(),
);
}
}

return json;
});

return this.parse({ data: json });
}
}
Loading