diff --git a/src/hooks/useDraw.ts b/src/hooks/useDraw.ts new file mode 100644 index 00000000..026c8db3 --- /dev/null +++ b/src/hooks/useDraw.ts @@ -0,0 +1,123 @@ +import * as OlEventConditions from 'ol/events/condition'; +import OlGeometry from 'ol/geom/Geometry'; +import OlInteractionDraw, { createBox, DrawEvent as OlDrawEvent, Options as OlDrawOptions } from 'ol/interaction/Draw'; +import OlVectorLayer from 'ol/layer/Vector'; +import OlVectorSource from 'ol/source/Vector'; +import { StyleLike as OlStyleLike } from 'ol/style/Style'; + +import { DigitizeUtil } from '../Util/DigitizeUtil'; +import useMap from './useMap'; +import {useOlInteraction} from './useOlInteraction'; +import {useOlListener} from './useOlListener'; +import {usePropOrDefault} from './usePropOrDefault'; + +export type UseDrawType = 'Point' | 'LineString' | 'Polygon' | 'Circle' | 'Rectangle'; + +export interface UseDrawProps { + /** + * Active state of interaction + */ + active: boolean; + /** + * Whether the line, point, polygon, circle, rectangle or text shape should + * be drawn. + */ + drawType: UseDrawType; + /** + * Style object / style function for drawn feature. + */ + drawStyle?: OlStyleLike; + /** + * Listener function for the 'drawend' event of an ol.interaction.Draw. + * See https://openlayers.org/en/latest/apidoc/module-ol_interaction_Draw-DrawEvent.html + * for more information. + */ + onDrawEnd?: (event: OlDrawEvent) => void; + /** + * Listener function for the 'drawstart' event of an ol.interaction.Draw. + * See https://openlayers.org/en/latest/apidoc/module-ol_interaction_Draw-DrawEvent.html + * for more information. + */ + onDrawStart?: (event: OlDrawEvent) => void; + /** + * The vector layer which will be used for digitize features. + * The standard digitizeLayer can be retrieved via `DigitizeUtil.getDigitizeLayer(map)`. + */ + digitizeLayer?: OlVectorLayer>; + /** + * Additional configuration object to apply to the ol.interaction.Draw. + * See https://openlayers.org/en/latest/apidoc/module-ol_interaction_Draw-Draw.html + * for more information + * + * Note: The keys source, type, geometryFunction, style and freehandCondition + * are handled internally and shouldn't be overwritten without any + * specific cause. + */ + drawInteractionConfig?: Omit; +} + +export const useDraw = ({ + active, + digitizeLayer, + drawInteractionConfig, + drawStyle, + drawType, + onDrawEnd, + onDrawStart +}: UseDrawProps) => { + const map = useMap(); + + const layer = usePropOrDefault( + digitizeLayer, + () => map ? DigitizeUtil.getDigitizeLayer(map) : undefined, + [map] + ); + + const drawInteraction = useOlInteraction( + () => { + if (!map || !layer) { + return undefined; + } + + let geometryFunction; + let type: 'Point' | 'Circle' | 'LineString' | 'Polygon'; + + if (drawType === 'Rectangle') { + geometryFunction = createBox(); + type = 'Circle'; + } else { + type = drawType; + } + + const newInteraction = new OlInteractionDraw({ + source: layer.getSource() || undefined, + type: type, + geometryFunction: geometryFunction, + style: drawStyle ?? DigitizeUtil.defaultDigitizeStyleFunction, + freehandCondition: OlEventConditions.never, + ...(drawInteractionConfig ?? {}) + }); + + newInteraction.set('name', `react-geo-draw-interaction-${drawType}`); + return newInteraction; + }, + [map, layer, drawType, drawStyle, drawInteractionConfig], + active + ); + + useOlListener( + drawInteraction, + i => i.on('drawend', (evt) => { + onDrawEnd?.(evt); + }), + [drawInteraction, onDrawEnd] + ); + + useOlListener( + drawInteraction, + i => i.on('drawstart', (evt) => { + onDrawStart?.(evt); + }), + [drawInteraction, onDrawStart] + ); +}; diff --git a/src/hooks/usePropOrDefault.ts b/src/hooks/usePropOrDefault.ts new file mode 100644 index 00000000..932f42ee --- /dev/null +++ b/src/hooks/usePropOrDefault.ts @@ -0,0 +1,18 @@ +import {DependencyList, useEffect, useState} from 'react'; + +export const usePropOrDefault = ( + prop: T|undefined, + defaultFunc: () => T, + dependencies: DependencyList +): T|undefined => { + const [value, setValue] = useState(undefined); + useEffect(() => { + if (prop) { + setValue(prop); + } else { + setValue(defaultFunc()); + } + }, [prop, ...dependencies]); + + return value; +}; diff --git a/src/hooks/useSelectFeatures.ts b/src/hooks/useSelectFeatures.ts new file mode 100644 index 00000000..eec85f71 --- /dev/null +++ b/src/hooks/useSelectFeatures.ts @@ -0,0 +1,113 @@ +import OlCollection from 'ol/Collection'; +import * as OlEventConditions from 'ol/events/condition'; +import OlFeature from 'ol/Feature'; +import OlGeometry from 'ol/geom/Geometry'; +import OlInteractionSelect, {Options as OlSelectOptions, SelectEvent as OlSelectEvent} from 'ol/interaction/Select'; +import OlVectorLayer from 'ol/layer/Vector'; +import OlVectorSource from 'ol/source/Vector'; +import {StyleLike as OlStyleLike} from 'ol/style/Style'; +import {useEffect} from 'react'; + +import {DigitizeUtil} from '../Util/DigitizeUtil'; +import {useOlInteraction} from './useOlInteraction'; +import {useOlListener} from './useOlListener'; +import {usePropOrDefault} from './usePropOrDefault'; + +export interface UseSelectFeaturesProps { + /** + * Active state of interaction + */ + active: boolean; + /** + * Select style of the selected features. + */ + selectStyle?: OlStyleLike; + /** + * Additional configuration object to apply to the ol.interaction.Select. + * See https://openlayers.org/en/latest/apidoc/module-ol_interaction_Select-Select.html + * for more information + * + * Note: The keys condition, hitTolerance and style are handled internally + * and shouldn't be overwritten without any specific cause. + */ + selectInteractionConfig?: Omit; + /** + * Listener function for the 'select' event of the ol.interaction.Select + * See https://openlayers.org/en/latest/apidoc/module-ol_interaction_Select.html + * for more information. + */ + onFeatureSelect?: (event: OlSelectEvent) => void; + /** + * Array of layers the SelectFeaturesButton should operate on. + */ + layers: OlVectorLayer>[]; + /** + * Hit tolerance of the select action. Default: 5 + */ + hitTolerance?: number; + /** + * Clear the feature collection of the interaction after select. Default: false + */ + clearAfterSelect?: boolean; + /** + * A feature collection to use. + */ + featuresCollection?: OlCollection>; +} + +export const useSelectFeatures = ({ + active, + selectStyle, + selectInteractionConfig, + onFeatureSelect, + hitTolerance = 5, + layers, + clearAfterSelect = false, + featuresCollection, +}: UseSelectFeaturesProps) => { + const features = usePropOrDefault( + featuresCollection, + () => new OlCollection(), + [] + ); + + const selectInteraction = useOlInteraction( + () => { + if (!features) { + return undefined; + } + + const newInteraction = new OlInteractionSelect({ + condition: OlEventConditions.singleClick, + features, + hitTolerance: hitTolerance, + style: selectStyle ?? DigitizeUtil.DEFAULT_SELECT_STYLE, + layers: layers, + ...(selectInteractionConfig ?? {}) + }); + + newInteraction.set('name', 'react-geo-select-interaction'); + + return newInteraction; + }, + [features, hitTolerance, selectStyle, layers, selectInteractionConfig], + active + ); + + useOlListener( + selectInteraction, + i => i.on('select', e => { + if (features && clearAfterSelect) { + features.clear(); + } + onFeatureSelect?.(e); + }), + [features, clearAfterSelect, onFeatureSelect] + ); + + useEffect(() => { + if (!active && features) { + features.clear(); + } + }, []); +};