diff --git a/src/Field/NominatimSearch/NominatimSearch.example.md b/src/Field/NominatimSearch/NominatimSearch.example.md
index a43911963b..8fea82e420 100644
--- a/src/Field/NominatimSearch/NominatimSearch.example.md
+++ b/src/Field/NominatimSearch/NominatimSearch.example.md
@@ -3,6 +3,7 @@ This demonstrates the usage of the NominatimSearch.
```jsx
import NominatimSearch from '@terrestris/react-geo/dist/Field/NominatimSearch/NominatimSearch';
import MapComponent from '@terrestris/react-util/dist/Components/MapComponent/MapComponent';
+import MapContext from '@terrestris/react-util/dist/Context/MapContext/MapContext';
import OlLayerTile from 'ol/layer/Tile';
import OlMap from 'ol/Map';
import {fromLonLat} from 'ol/proj';
@@ -35,10 +36,11 @@ const NominatimSearchExample = () => {
}
return (
-
+
+
@@ -48,7 +50,7 @@ const NominatimSearchExample = () => {
height: '400px'
}}
/>
-
+
);
};
diff --git a/src/Field/NominatimSearch/NominatimSearch.tsx b/src/Field/NominatimSearch/NominatimSearch.tsx
index ba08ff6abb..55d9b44d6b 100644
--- a/src/Field/NominatimSearch/NominatimSearch.tsx
+++ b/src/Field/NominatimSearch/NominatimSearch.tsx
@@ -1,92 +1,24 @@
-import { AutoComplete } from 'antd';
-import { AutoCompleteProps } from 'antd/lib/auto-complete';
-import * as React from 'react';
-const Option = AutoComplete.Option;
import './NominatimSearch.less';
-import Logger from '@terrestris/base-util/dist/Logger';
-import UrlUtil from '@terrestris/base-util/dist/UrlUtil/UrlUtil';
import { useMap } from '@terrestris/react-util/dist/Hooks/useMap/useMap';
-import { DefaultOptionType, OptionProps } from 'antd/lib/select';
-import { GeoJSON } from 'geojson';
+import useNominatim, {
+ NominatimPlace,
+ UseNominatimArgs
+} from '@terrestris/react-util/dist/Hooks/useNominatim/useNominatim';
+import { AutoComplete } from 'antd';
+import { AutoCompleteProps } from 'antd/lib/auto-complete';
+import { OptionProps } from 'antd/lib/select';
+import _isNil from 'lodash/isNil';
import { Extent as OlExtent } from 'ol/extent';
-import OlMap from 'ol/Map';
import { transformExtent } from 'ol/proj';
-import { FC, useCallback, useEffect, useState } from 'react';
+import { DefaultOptionType } from 'rc-select/lib/Select';
+import React, { FC, useCallback, useState } from 'react';
import { CSS_PREFIX } from '../../constants';
-// See https://nominatim.org/release-docs/develop/api/Output/ for some more information
-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;
-} & DefaultOptionType;
+const Option = AutoComplete.Option;
interface OwnProps {
- /**
- * 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;
- /**
- * The minimal amount of characters entered in the input to start a search.
- */
- minChars?: number;
/**
* A render function which gets called with the selected item as it is
* returned by nominatim. It must return an `AutoComplete.Option`.
@@ -96,7 +28,7 @@ interface OwnProps {
* An onSelect function which gets called with the selected item as it is
* returned by nominatim.
*/
- onSelect?: (item: NominatimPlace, olMap: OlMap) => void;
+ onSelect?: (item: NominatimPlace) => void;
/**
* Indicate if we should render the input and results. When setting to false,
* you need to handle user input and result yourself
@@ -107,14 +39,6 @@ interface OwnProps {
* `visible` to `false`.
*/
searchTerm?: string;
- /**
- * A callback function which gets called with the successfully fetched data.
- */
- onFetchSuccess?: (data: NominatimPlace[]) => void;
- /**
- * A callback function which gets called if data fetching has failed.
- */
- onFetchError?: (error: any) => void;
/**
* An optional CSS class which should be added.
*/
@@ -124,50 +48,60 @@ interface OwnProps {
* value is empty.
*/
onClear?: () => void;
- /**
- * Time in miliseconds that the search waits before doing a request.
- */
- debounceTime?: number;
}
-export type NominatimSearchProps = OwnProps & Omit;
+export type NominatimSearchProps = OwnProps &
+ Omit & Omit;
/**
* The NominatimSearch.
*/
export const NominatimSearch: FC = ({
- addressDetails = 1,
- bounded = 1,
+ addressDetails,
+ bounded,
className = `${CSS_PREFIX}nominatimsearch`,
- countryCodes = 'de',
- debounceTime = 300,
- format = 'json',
- limit = 10,
- minChars = 3,
- nominatimBaseUrl = 'https://nominatim.openstreetmap.org/search?',
+ countryCodes,
+ debounceTime,
+ format,
+ limit,
+ minChars,
+ nominatimBaseUrl,
+ onChange = () => undefined,
onClear,
onFetchError,
- onFetchSuccess,
onSelect,
- polygonGeoJSON = 1,
+ polygonGeoJSON,
renderOption,
- onChange = () => undefined,
searchResultLanguage,
- viewBox = '-180,90,180,-90',
+ viewBox,
visible = true,
...passThroughProps
}) => {
- const map = useMap();
-
const [searchTerm, setSearchTerm] = useState('');
- const [dataSource, setDataSource] = useState([]);
+ const map = useMap();
- const finalOnSelect = useCallback((selected: NominatimPlace, olMap: OlMap) => {
+ const nominatimResults = useNominatim({
+ addressDetails,
+ bounded,
+ countryCodes,
+ debounceTime,
+ format,
+ limit,
+ minChars,
+ nominatimBaseUrl,
+ onFetchError,
+ polygonGeoJSON,
+ searchResultLanguage,
+ searchTerm,
+ viewBox
+ });
+
+ const finalOnSelect = useCallback((selected: NominatimPlace) => {
if (onSelect) {
- onSelect(selected, olMap);
+ onSelect(selected);
} else if (selected && selected.boundingbox) {
- const olView = olMap.getView();
+ const olView = map?.getView();
const bbox: number[] = selected.boundingbox.map(parseFloat);
let extent = [
bbox[2],
@@ -177,116 +111,59 @@ export const NominatimSearch: FC = ({
] as OlExtent;
extent = transformExtent(extent, 'EPSG:4326',
- olView.getProjection().getCode());
+ olView?.getProjection().getCode());
- olView.fit(extent, {
+ olView?.fit(extent, {
duration: 500
});
}
- }, [onSelect]);
+ }, [map, onSelect]);
const finalRenderOption = useCallback((item: NominatimPlace): React.ReactElement => {
+ if (_isNil(item)) {
+ return <>>;
+ }
if (renderOption) {
return renderOption(item);
} else {
return (
);
}
}, [renderOption]);
- const fetchResults = useCallback(async (baseParams: any) => {
- const getRequestParams = UrlUtil.objectToRequestString(baseParams);
-
- const onError = (error: any) => {
- Logger.error(`Error while requesting Nominatim: ${error}`);
- onFetchError?.(error);
- };
-
- 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();
-
- setDataSource(responseJson);
- onFetchSuccess?.(responseJson);
- } catch (error) {
- onError(error);
- }
- }, [nominatimBaseUrl, onFetchError, onFetchSuccess, searchResultLanguage]);
-
- /**
- * Trigger search when searchTerm has changed
- */
- useEffect(() => {
- setDataSource([]);
-
- if (!searchTerm && onClear) {
- onClear();
- }
-
- if (!searchTerm || searchTerm.length < minChars) {
- return;
- }
-
- const timeout = setTimeout(() => {
- 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, minChars, debounceTime, addressDetails, bounded, countryCodes,
- fetchResults, format, limit, onClear, polygonGeoJSON, viewBox]);
-
/**
* The function describes what to do when an item is selected.
*
* @param option The selected OptionData
*/
- const onMenuItemSelected = useCallback((_: any, option: NominatimPlace) => {
+ const onMenuItemSelected = useCallback((_: any, option: DefaultOptionType) => {
if (!map) {
return;
}
- const selected = dataSource.find(
- i => i.place_id.toString() === option.key
+ const selected = nominatimResults?.find(
+ i => `${i.place_id}` === option.key
);
- if (selected) {
- finalOnSelect(selected, map);
+ if (!_isNil(selected)) {
+ finalOnSelect(selected);
}
- }, [finalOnSelect, dataSource, map]);
+ }, [finalOnSelect, nominatimResults, map]);
- const onValueChange = (value: string, option: NominatimPlace | NominatimPlace[]) => {
+ const onValueChange = (value: string, place: NominatimPlace) => {
setSearchTerm(value);
- onChange(value, option);
+ if (!_isNil(place)) {
+ onChange(value, {
+ ...place,
+ label: place.display_name
+ });
+ } else {
+ onChange(value, place);
+ }
};
if (!visible) {
@@ -303,7 +180,7 @@ export const NominatimSearch: FC = ({
{...passThroughProps}
>
{
- dataSource.map(finalRenderOption)
+ nominatimResults?.map(finalRenderOption)
}
);