diff --git a/src/CoordinateInfo/CoordinateInfo.example.md b/src/CoordinateInfo/CoordinateInfo.example.md index efa1ed25bb..cb2e3e60ff 100644 --- a/src/CoordinateInfo/CoordinateInfo.example.md +++ b/src/CoordinateInfo/CoordinateInfo.example.md @@ -9,13 +9,15 @@ import { Tooltip } from 'antd'; import * as copy from 'copy-to-clipboard'; +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'; import OlSourceOSM from 'ol/source/OSM'; import OlSourceTileWMS from 'ol/source/TileWMS'; import OlView from 'ol/View'; -import * as React from 'react'; +import React, { useEffect,useState } from 'react'; const queryLayer = new OlLayerTile({ name: 'States (USA)', @@ -30,14 +32,12 @@ const queryLayer = new OlLayerTile({ }) }); -class CoordinateInfoExample extends React.Component { +const CoordinateInfoExample = () => { - constructor(props) { - super(props); + const [map, setMap] = useState(); - this.mapDivId = `map-${Math.random()}`; - - this.map = new OlMap({ + useEffect(() => { + setMap(new OlMap({ layers: [ new OlLayerTile({ name: 'OSM', @@ -49,22 +49,21 @@ class CoordinateInfoExample extends React.Component { center: fromLonLat([-99.4031637, 38.3025695]), zoom: 4 }) - }); - } + })); + }, []); - componentDidMount() { - this.map.setTarget(this.mapDivId); + if (!map) { + return null; } - render() { - return ( - <> -
+ return ( + + - + ); - } } diff --git a/src/CoordinateInfo/CoordinateInfo.spec.tsx b/src/CoordinateInfo/CoordinateInfo.spec.tsx index e04e5f8edf..5a27c61ac0 100644 --- a/src/CoordinateInfo/CoordinateInfo.spec.tsx +++ b/src/CoordinateInfo/CoordinateInfo.spec.tsx @@ -17,7 +17,7 @@ describe('', () => { }); it('can be rendered', () => { - const { container } = render(); + const { container } = render(); expect(container).toBeVisible(); }); }); diff --git a/src/CoordinateInfo/CoordinateInfo.tsx b/src/CoordinateInfo/CoordinateInfo.tsx index 068b697c28..645e57de2f 100644 --- a/src/CoordinateInfo/CoordinateInfo.tsx +++ b/src/CoordinateInfo/CoordinateInfo.tsx @@ -1,269 +1,51 @@ -import Logger from '@terrestris/base-util/dist/Logger'; -import { isWmsLayer, WmsLayer } from '@terrestris/react-util/dist/Util/typeUtils'; -import _cloneDeep from 'lodash/cloneDeep'; -import _isString from 'lodash/isString'; -import { getUid } from 'ol'; -import { Coordinate as OlCoordinate } from 'ol/coordinate'; -import OlFeature from 'ol/Feature'; -import OlFormatGML2 from 'ol/format/GML2'; -import OlGeometry from 'ol/geom/Geometry'; -import OlBaseLayer from 'ol/layer/Base'; -import OlMap from 'ol/Map'; -import OlMapBrowserEvent from 'ol/MapBrowserEvent'; -import * as React from 'react'; - -const format = new OlFormatGML2(); - -export interface CoordinateInfoProps { - /** - * List of (WMS) layers that should be queried. - */ - queryLayers: Array; - - /** - * The number of max. features that should be returned by the GFI request. - */ - featureCount: number; - - /** - * Whether the GFI control should requests all layers at a given coordinate - * or just the uppermost one. - */ - drillDown: boolean; +import useCoordinateInfo, { + CoordinateInfoResult, + UseCoordinateInfoArgs +} from '@terrestris/react-util/dist/Hooks/useCoordinateInfo/useCoordinateInfo'; +import _isNil from 'lodash/isNil'; +import React, { FC } from 'react'; +export type CoordinateInfoProps = { /** * The children component that should be rendered. The render prop function * receives the state of the component (this is the clicked coordinate, the * list of GFI features if any and the loading state). */ - resultRenderer: (childrenProps: CoordinateInfoState) => React.ReactNode; - /** - * The ol map. - */ - map: OlMap; - /** - * Optional request options to apply (separated by each query layer, identified - * by its internal ol id or a callback function). - */ - fetchOpts: - | { - [uid: string]: RequestInit; - } - | ((layer: WmsLayer) => RequestInit); - /** - * Callback function that gets called if all features are fetched successfully - * via GetFeatureInfo. - */ - onSuccess: (features: CoordinateInfoState) => void; - /** - * Callback function that gets called if an error occured while fetching the - * features via GetFeatureInfo. - */ - onError: (error: any) => void; -} - -export interface CoordinateInfoState { - clickCoordinate: OlCoordinate | null; - features: { - [layerName: string]: OlFeature[]; - }; - loading: boolean; -} + resultRenderer?: (childrenProps: CoordinateInfoResult) => React.ReactNode; +} & UseCoordinateInfoArgs; /** * Constructs a wrapper component for querying features from the clicked * coordinate. The returned features can be passed to a child component * to be visualized. * - * @class The CoordinateInfo - * @extends React.Component */ -export class CoordinateInfo extends React.Component { - /** - * The defaultProps. - */ - static defaultProps = { - queryLayers: [], - featureCount: 1, - drillDown: true, - resultRenderer: () => { - return ( -
- ); - }, - fetchOpts: {}, - onSuccess: () => { }, - onError: () => { } - }; - - /** - * Creates the CoordinateInfo component. - * @constructs CoordinateInfo - */ - constructor(props: CoordinateInfoProps) { - super(props); - - this.state = { - clickCoordinate: null, - features: {}, - loading: false - }; - - this.onMapClick = this.onMapClick.bind(this); - this.layerFilter = this.layerFilter.bind(this); - } - - componentDidMount() { - const { - map - } = this.props; - - map.on('click', this.onMapClick); +export const CoordinateInfo: FC = ({ + drillDown = true, + featureCount = 1, + fetchOpts = {}, + onError = () => undefined, + queryLayers = [], + resultRenderer = () => <> +}) => { + + const result = useCoordinateInfo({ + drillDown, + featureCount, + fetchOpts, + onError, + queryLayers, + }); + + if (_isNil(result)) { + return null; } - componentWillUnmount() { - const { - map - } = this.props; - - map.un('click', this.onMapClick); - } - - onMapClick(olEvt: OlMapBrowserEvent) { - const { - map, - featureCount, - drillDown, - fetchOpts, - onSuccess, - onError - } = this.props; - - const mapView = map.getView(); - const viewResolution = mapView.getResolution(); - const viewProjection = mapView.getProjection(); - const pixel = map.getEventPixel(olEvt.originalEvent); - const coordinate = olEvt.coordinate; - - const promises: Promise[] = []; - - const mapLayers = - map.getAllLayers() - .filter(this.layerFilter) - .filter(l => l.getData && l.getData(pixel) && isWmsLayer(l)); - mapLayers.forEach(l => { - const layerSource = (l as WmsLayer).getSource(); - if (!layerSource) { - return; - } - const featureInfoUrl = layerSource.getFeatureInfoUrl( - coordinate, - viewResolution!, - viewProjection, - { - INFO_FORMAT: 'application/vnd.ogc.gml', - FEATURE_COUNT: featureCount - } - ); - if (featureInfoUrl) { - let opts; - if (fetchOpts instanceof Function) { - opts = fetchOpts(l as WmsLayer); - } else { - opts = fetchOpts[getUid(l)]; - } - promises.push(fetch(featureInfoUrl, opts)); - } - - return !drillDown; - }); - - map.getTargetElement().style.cursor = 'wait'; - - this.setState({ - loading: true - }); - - Promise.all(promises) - .then((responses: Response[]) => { - this.setState({ - clickCoordinate: coordinate - }); - const textResponses = responses.map(response => response.text()); - return Promise.all(textResponses); - }) - .then((textResponses: string[]) => { - const features: {[index: string]: OlFeature[]} = {}; - - textResponses.forEach((featureCollection: string, idx: number) => { - const fc = format.readFeatures(featureCollection); - fc.forEach((feature: OlFeature) => { - const id = feature.getId(); - const featureTypeName = _isString(id) ? id.split('.')[0] : id?.toString() ?? `UNKNOWN-${idx}`; - - if (!features[featureTypeName]) { - features[featureTypeName] = []; - } - - features[featureTypeName].push(feature); - }); - }); - - this.setState({ - features: features - }, () => { - onSuccess(this.getCoordinateInfoState()); - }); - }) - .catch((error: any) => { - Logger.error(error); - - onError(error); - }) - .finally(() => { - map.getTargetElement().style.cursor = ''; - - this.setState({ - loading: false - }); - }); - } - - layerFilter(layerCandidate: OlBaseLayer) { - const { - queryLayers - } = this.props; - - return (queryLayers as OlBaseLayer[]).includes(layerCandidate); - } - - getCoordinateInfoState(): CoordinateInfoState { - // We're cloning the click coordinate and features to - // not pass the internal state reference to the parent component. - // Also note that we explicitly don't use feature.clone() to - // keep all feature properties (in particular the id) intact. - const coordinateInfoState: CoordinateInfoState = { - clickCoordinate: this.state.clickCoordinate ? - _cloneDeep(this.state.clickCoordinate) : - null, - loading: this.state.loading, - features: _cloneDeep(this.state.features) - }; - - return coordinateInfoState; - }; - - render() { - const { - resultRenderer - } = this.props; - - return ( - <> - {resultRenderer(this.getCoordinateInfoState())} - - ); - } -} + return ( + <> + {resultRenderer(result)} + + ); +}; export default CoordinateInfo;