Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: introduce useNominatim hook #600

Merged
merged 1 commit into from
Feb 22, 2024
Merged
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
7 changes: 7 additions & 0 deletions src/Hooks/useNominatim/useNominatim.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { useNominatim } from './useNominatim';

describe('basic test', () => {
it('is defined', () => {
expect(useNominatim).toBeDefined();
});
});
172 changes: 172 additions & 0 deletions src/Hooks/useNominatim/useNominatim.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@


// See https://nominatim.org/release-docs/develop/api/Output/ for some more information
import { UrlUtil } from '@terrestris/base-util';
import Logger from '@terrestris/base-util/dist/Logger';
import { GeoJSON } from 'geojson';
import { useEffect, useState } from 'react';

export type NominatimPlace = {
// eslint-disable-next-line camelcase
place_id: number;
// eslint-disable-next-line camelcase
osm_type: string;
// eslint-disable-next-line camelcase
osm_id: number;
boundingbox: string[];
// eslint-disable-next-line camelcase
display_name: string;
category: string;
type: string;
importance: number;
icon?: string;
address?: any;
extratags?: any;
namedetails?: any;
geojson: GeoJSON;
licence: string;
};

export type UseNominatimArgs = {
searchTerm: string;
/**
* Time in miliseconds that the search waits before doing a request.
*/
debounceTime?: number;
/**
* The Nominatim Base URL. See https://wiki.openstreetmap.org/wiki/Nominatim
*/
nominatimBaseUrl?: string;
/**
* Output format.
*/
format?: string;
/**
* The preferred area to find search results in [left],[top],[right],[bottom].
*/
viewBox?: string;
/**
* Restrict the results to only items contained with the bounding box.
* Restricting the results to the bounding box also enables searching by
* amenity only. For example a search query of just "[pub]" would normally be
* rejected but with bounded=1 will result in a list of items matching within
* the bounding box.
*/
bounded?: number;
/**
* Output geometry of results in geojson format.
*/
polygonGeoJSON?: number;
/**
* Include a breakdown of the address into elements.
*/
addressDetails?: number;
/**
* Limit the number of returned results.
*/
limit?: number;
/**
* Limit search results to a specific country (or a list of countries).
* [countrycode] should be the ISO 3166-1alpha2 code, e.g. gb for the United
* Kingdom, de for Germany, etc.
*/
countryCodes?: string;
/**
* Preferred language order for showing search results, overrides the value
* specified in the "Accept-Language" HTTP header. Either use a standard RFC2616
* accept-language string or a simple comma-separated list of language codes.
*/
searchResultLanguage?: string;
/**
* A callback function which gets called if data fetching has failed.
*/
onFetchError?: (error: any) => void;
/**
* The minimal amount of characters in the input to start a search.
*/
minChars?: number;
};

export const useNominatim = ({
addressDetails = 1,
bounded = 1,
countryCodes = 'de',
debounceTime = 300,
format = 'json',
limit = 10,
minChars = 3,
nominatimBaseUrl = 'https://nominatim.openstreetmap.org/search?',
onFetchError,
polygonGeoJSON = 1,
searchResultLanguage,
searchTerm,
viewBox = '-180,90,180,-90'
}: UseNominatimArgs): NominatimPlace[] | undefined => {

const [nominatimResults, setNominatimResults] = useState<NominatimPlace[]>([]);

const onError = (error: any) => {
Logger.error(`Error while requesting Nominatim: ${error}`);
onFetchError?.(error);
};

// TODO abort controller
const fetchResults = async (baseParams: any) => {
const getRequestParams = UrlUtil.objectToRequestString(baseParams);
try {
let fetchOpts: RequestInit = {};
if (searchResultLanguage) {
fetchOpts = {
headers: {
'accept-language': searchResultLanguage
}
};
}
const response = await fetch(`${nominatimBaseUrl}${getRequestParams}`, fetchOpts);
if (!response.ok) {
onError(new Error(`Return code: ${response.status}`));
}
const responseJson = await response.json();
setNominatimResults(responseJson);
} catch (error) {
onError(error);
}
};

/**
* Trigger search when searchTerm has changed
*/
useEffect(() => {
setNominatimResults([]);

if (!searchTerm) {
setNominatimResults([]);
}

if (!searchTerm || searchTerm.length < minChars) {
return () => undefined;
}

const timeout = setTimeout(async () => {
await fetchResults({
format: format,
viewbox: viewBox,
bounded: bounded,
// eslint-disable-next-line camelcase
polygon_geojson: polygonGeoJSON,
addressdetails: addressDetails,
limit: limit,
countrycodes: countryCodes,
q: searchTerm
});
}, debounceTime);

return () => {
clearTimeout(timeout);
};
}, [searchTerm]);

return nominatimResults;
};

export default useNominatim;
Loading