Skip to content

Commit dc9c195

Browse files
committed
feat: introduce useNominatim hook
1 parent eecaa0d commit dc9c195

File tree

2 files changed

+179
-0
lines changed

2 files changed

+179
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { useNominatim } from './useNominatim';
2+
3+
describe('basic test', () => {
4+
it('is defined', () => {
5+
expect(useNominatim).toBeDefined();
6+
});
7+
});
+172
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
2+
3+
// See https://nominatim.org/release-docs/develop/api/Output/ for some more information
4+
import { UrlUtil } from '@terrestris/base-util';
5+
import Logger from '@terrestris/base-util/dist/Logger';
6+
import { GeoJSON } from 'geojson';
7+
import { useEffect, useState } from 'react';
8+
9+
export type NominatimPlace = {
10+
// eslint-disable-next-line camelcase
11+
place_id: number;
12+
// eslint-disable-next-line camelcase
13+
osm_type: string;
14+
// eslint-disable-next-line camelcase
15+
osm_id: number;
16+
boundingbox: string[];
17+
// eslint-disable-next-line camelcase
18+
display_name: string;
19+
category: string;
20+
type: string;
21+
importance: number;
22+
icon?: string;
23+
address?: any;
24+
extratags?: any;
25+
namedetails?: any;
26+
geojson: GeoJSON;
27+
licence: string;
28+
};
29+
30+
export type UseNominatimArgs = {
31+
searchTerm: string;
32+
/**
33+
* Time in miliseconds that the search waits before doing a request.
34+
*/
35+
debounceTime?: number;
36+
/**
37+
* The Nominatim Base URL. See https://wiki.openstreetmap.org/wiki/Nominatim
38+
*/
39+
nominatimBaseUrl?: string;
40+
/**
41+
* Output format.
42+
*/
43+
format?: string;
44+
/**
45+
* The preferred area to find search results in [left],[top],[right],[bottom].
46+
*/
47+
viewBox?: string;
48+
/**
49+
* Restrict the results to only items contained with the bounding box.
50+
* Restricting the results to the bounding box also enables searching by
51+
* amenity only. For example a search query of just "[pub]" would normally be
52+
* rejected but with bounded=1 will result in a list of items matching within
53+
* the bounding box.
54+
*/
55+
bounded?: number;
56+
/**
57+
* Output geometry of results in geojson format.
58+
*/
59+
polygonGeoJSON?: number;
60+
/**
61+
* Include a breakdown of the address into elements.
62+
*/
63+
addressDetails?: number;
64+
/**
65+
* Limit the number of returned results.
66+
*/
67+
limit?: number;
68+
/**
69+
* Limit search results to a specific country (or a list of countries).
70+
* [countrycode] should be the ISO 3166-1alpha2 code, e.g. gb for the United
71+
* Kingdom, de for Germany, etc.
72+
*/
73+
countryCodes?: string;
74+
/**
75+
* Preferred language order for showing search results, overrides the value
76+
* specified in the "Accept-Language" HTTP header. Either use a standard RFC2616
77+
* accept-language string or a simple comma-separated list of language codes.
78+
*/
79+
searchResultLanguage?: string;
80+
/**
81+
* A callback function which gets called if data fetching has failed.
82+
*/
83+
onFetchError?: (error: any) => void;
84+
/**
85+
* The minimal amount of characters in the input to start a search.
86+
*/
87+
minChars?: number;
88+
};
89+
90+
export const useNominatim = ({
91+
addressDetails = 1,
92+
bounded = 1,
93+
countryCodes = 'de',
94+
debounceTime = 300,
95+
format = 'json',
96+
limit = 10,
97+
minChars = 3,
98+
nominatimBaseUrl = 'https://nominatim.openstreetmap.org/search?',
99+
onFetchError,
100+
polygonGeoJSON = 1,
101+
searchResultLanguage,
102+
searchTerm,
103+
viewBox = '-180,90,180,-90'
104+
}: UseNominatimArgs): NominatimPlace[] | undefined => {
105+
106+
const [nominatimResults, setNominatimResults] = useState<NominatimPlace[]>([]);
107+
108+
const onError = (error: any) => {
109+
Logger.error(`Error while requesting Nominatim: ${error}`);
110+
onFetchError?.(error);
111+
};
112+
113+
// TODO abortcontroller
114+
const fetchResults = async (baseParams: any) => {
115+
const getRequestParams = UrlUtil.objectToRequestString(baseParams);
116+
try {
117+
let fetchOpts: RequestInit = {};
118+
if (searchResultLanguage) {
119+
fetchOpts = {
120+
headers: {
121+
'accept-language': searchResultLanguage
122+
}
123+
};
124+
}
125+
const response = await fetch(`${nominatimBaseUrl}${getRequestParams}`, fetchOpts);
126+
if (!response.ok) {
127+
onError(new Error(`Return code: ${response.status}`));
128+
}
129+
const responseJson = await response.json();
130+
setNominatimResults(responseJson);
131+
} catch (error) {
132+
onError(error);
133+
}
134+
};
135+
136+
/**
137+
* Trigger search when searchTerm has changed
138+
*/
139+
useEffect(() => {
140+
setNominatimResults([]);
141+
142+
if (!searchTerm) {
143+
setNominatimResults([]);
144+
}
145+
146+
if (!searchTerm || searchTerm.length < minChars) {
147+
return () => undefined;
148+
}
149+
150+
const timeout = setTimeout(async () => {
151+
await fetchResults({
152+
format: format,
153+
viewbox: viewBox,
154+
bounded: bounded,
155+
// eslint-disable-next-line camelcase
156+
polygon_geojson: polygonGeoJSON,
157+
addressdetails: addressDetails,
158+
limit: limit,
159+
countrycodes: countryCodes,
160+
q: searchTerm
161+
});
162+
}, debounceTime);
163+
164+
return () => {
165+
clearTimeout(timeout);
166+
};
167+
}, []);
168+
169+
return nominatimResults;
170+
};
171+
172+
export default useNominatim;

0 commit comments

Comments
 (0)