-
Notifications
You must be signed in to change notification settings - Fork 59
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(modifybutton): add modify button
- Loading branch information
1 parent
1cb354f
commit 706f54b
Showing
2 changed files
with
363 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
This demonstrates the use of the ModifyButton. | ||
|
||
```jsx | ||
import {useEffect, useState} from 'react'; | ||
|
||
import OlMap from 'ol/Map'; | ||
import OlView from 'ol/View'; | ||
import OlLayerTile from 'ol/layer/Tile'; | ||
import OlSourceOsm from 'ol/source/OSM'; | ||
import OlVectorLayer from 'ol/layer/Vector'; | ||
import OlVectorSource from 'ol/source/Vector'; | ||
import OlFormatGeoJSON from 'ol/format/GeoJSON'; | ||
import {fromLonLat} from 'ol/proj'; | ||
|
||
import MapContext from '@terrestris/react-geo/Context/MapContext/MapContext' | ||
import MapComponent from '@terrestris/react-geo/Map/MapComponent/MapComponent'; | ||
import SelectFeaturesButton from '@terrestris/react-geo/Button/SelectFeaturesButton/SelectFeaturesButton'; | ||
import {ModifyButton} from '@terrestris/react-geo/Button/ModifyButton/ModifyButton'; | ||
import {DigitizeUtil} from '@terrestris/react-geo/Util/DigitizeUtil'; | ||
|
||
import featuresJson from '../../../assets/simple-geometries.json'; | ||
|
||
const format = new OlFormatGeoJSON({ | ||
featureProjection: 'EPSG:3857' | ||
}); | ||
const features = format.readFeatures(featuresJson); | ||
|
||
const ModifyButtonExample = () => { | ||
|
||
const [map, setMap] = useState(); | ||
const [layer, setLayer] = useState(); | ||
const [feature, setFeature] = useState(); | ||
|
||
useEffect(() => { | ||
const newMap = new OlMap({ | ||
layers: [ | ||
new OlLayerTile({ | ||
name: 'OSM', | ||
source: new OlSourceOsm() | ||
}) | ||
], | ||
view: new OlView({ | ||
center: fromLonLat([8, 50]), | ||
zoom: 4 | ||
}) | ||
}); | ||
|
||
const digitizeLayer = DigitizeUtil.getDigitizeLayer(newMap); | ||
digitizeLayer.getSource().addFeatures(features); | ||
|
||
setMap(newMap); | ||
}, []); | ||
|
||
if (!map) { | ||
return null; | ||
} | ||
|
||
return ( | ||
<div> | ||
<MapContext.Provider value={map}> | ||
<MapComponent | ||
map={map} | ||
style={{ | ||
height: '400px' | ||
}} | ||
/> | ||
|
||
<ModifyButton> | ||
Select feature | ||
</ModifyButton> | ||
</MapContext.Provider> | ||
</div> | ||
); | ||
} | ||
|
||
<ModifyButtonExample/> | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,286 @@ | ||
import SelectFeaturesButton, { SelectFeaturesButtonProps } from '../SelectFeaturesButton/SelectFeaturesButton'; | ||
import OlVectorLayer from 'ol/layer/Vector'; | ||
import OlVectorSource from 'ol/source/Vector'; | ||
import OlGeometry from 'ol/geom/Geometry'; | ||
import { CSS_PREFIX } from '../../constants'; | ||
import { useEffect, useState } from 'react'; | ||
import { useMap } from '../..'; | ||
import { DigitizeUtil } from '../../Util/DigitizeUtil'; | ||
// import { SelectEvent } from 'ol/interaction/Select'; | ||
import * as React from 'react'; | ||
import Modify, { ModifyEvent, Options as ModifyOptions } from 'ol/interaction/Modify'; | ||
import Translate, { Options as TranslateOptions, TranslateEvent } from 'ol/interaction/Translate'; | ||
import OlFeature from 'ol/Feature'; | ||
import { singleClick } from 'ol/events/condition'; | ||
import { unByKey } from 'ol/Observable'; | ||
import OlCollection from 'ol/Collection'; | ||
import { FeatureLabelModal } from '../../FeatureLabelModal/FeatureLabelModal'; | ||
import { SelectEvent as OlSelectEvent } from 'ol/interaction/Select'; | ||
|
||
interface OwnProps { | ||
/** | ||
* Additional configuration object to apply to the ol.interaction.Modify. | ||
* See https://openlayers.org/en/latest/apidoc/module-ol_interaction_Modify-Modify.html | ||
* for more information | ||
* | ||
* Note: The keys features, deleteCondition and style are handled internally | ||
* and shouldn't be overwritten without any specific cause. | ||
*/ | ||
modifyInteractionConfig: Omit<ModifyOptions, 'features'|'source'|'deleteCondition'|'style'>; | ||
/** | ||
* Additional configuration object to apply to the ol.interaction.Translate. | ||
* See https://openlayers.org/en/latest/apidoc/module-ol_interaction_Translate-Translate.html | ||
* for more information | ||
* | ||
* Note: The key feature is handled internally and shouldn't be overwritten | ||
* without any specific cause. | ||
*/ | ||
translateInteractionConfig: Omit<TranslateOptions, 'features'|'layers'>; | ||
/** | ||
* The vector layer which will be used for digitize features. | ||
* The standard digitizeLayer can be retrieved via `DigitizeUtil.getDigitizeLayer(map)`. | ||
*/ | ||
digitizeLayer?: OlVectorLayer<OlVectorSource<OlGeometry>>; | ||
/** | ||
* Listener function for the 'modifystart' event of an ol.interaction.Modify. | ||
* See https://openlayers.org/en/latest/apidoc/module-ol_interaction_Modify-ModifyEvent.html | ||
* for more information. | ||
*/ | ||
onModifyStart?: (event: ModifyEvent) => void; | ||
/** | ||
* Listener function for the 'modifyend' event of an ol.interaction.Modify. | ||
* See https://openlayers.org/en/latest/apidoc/module-ol_interaction_Modify-ModifyEvent.html | ||
* for more information. | ||
*/ | ||
onModifyEnd?: (event: ModifyEvent) => void; | ||
/** | ||
* Listener function for the 'translatestart' event of an ol.interaction.Translate. | ||
* See https://openlayers.org/en/latest/apidoc/module-ol_interaction_Translate-TranslateEvent.html | ||
* for more information. | ||
*/ | ||
onTranslateStart?: (event: TranslateEvent) => void; | ||
/** | ||
* Listener function for the 'translateend' event of an ol.interaction.Translate. | ||
* See https://openlayers.org/en/latest/apidoc/module-ol_interaction_Translate-TranslateEvent.html | ||
* for more information. | ||
*/ | ||
onTranslateEnd?: (event: TranslateEvent) => void; | ||
/** | ||
* Listener function for the 'translating' event of an ol.interaction.Translate. | ||
* See https://openlayers.org/en/latest/apidoc/module-ol_interaction_Translate-TranslateEvent.html | ||
* for more information. | ||
*/ | ||
onTranslating?: (event: TranslateEvent) => void; | ||
/** | ||
* Callback function that will be called when the ok-button of the modal was clicked | ||
*/ | ||
onModalLabelOk?: (feature: OlFeature<OlGeometry>) => void; | ||
/** | ||
* Callback function that will be called | ||
* when the cancel-button of the modal was clicked | ||
*/ | ||
onModalLabelCancel?: () => void; | ||
/** | ||
* Maximal length of feature label. | ||
* If exceeded label will be divided into multiple lines. Optional. | ||
*/ | ||
maxLabelLineLength?: number; | ||
/** | ||
* Title for modal used for input of labels for digitize features. | ||
*/ | ||
modalPromptTitle?: string; | ||
/** | ||
* Text string for `OK` button of the modal. | ||
*/ | ||
modalPromptOkButtonText?: string; | ||
/** | ||
* Text string for `Cancel` button of the modal. | ||
*/ | ||
modalPromptCancelButtonText?: string; | ||
/** | ||
* Enable label editing via modal. Will open before being able to modify/translate feature. Default: `true`. | ||
*/ | ||
editLabel?: boolean; | ||
} | ||
|
||
export type ModifyButtonProps = OwnProps & Omit<SelectFeaturesButtonProps, 'layers'|'onFeatureSelect'|'featuresCollection'>; | ||
|
||
/** | ||
* The className added to this component. | ||
*/ | ||
const defaultClassName = `${CSS_PREFIX}modifybutton`; | ||
|
||
export const ModifyButton: React.FC<ModifyButtonProps> = ({ | ||
className, | ||
onModifyStart, | ||
onModifyEnd, | ||
onTranslateStart, | ||
onTranslateEnd, | ||
onTranslating, | ||
digitizeLayer, | ||
selectStyle, | ||
modifyInteractionConfig, | ||
translateInteractionConfig, | ||
onModalLabelOk, | ||
onModalLabelCancel, | ||
onToggle, | ||
maxLabelLineLength, | ||
modalPromptTitle, | ||
modalPromptOkButtonText, | ||
modalPromptCancelButtonText, | ||
editLabel = true, | ||
...passThroughProps | ||
}) => { | ||
const [layers, setLayers] = useState<[OlVectorLayer<OlVectorSource<OlGeometry>>]>(null); | ||
const [modifyInteraction, setModifyInteraction] = useState<Modify>(null); | ||
const [translateInteraction, setTranslateInteraction] = useState<Translate>(null); | ||
const [features, setFeatures] = useState<OlCollection<OlFeature<OlGeometry>>|null>(null); | ||
|
||
const map = useMap(); | ||
|
||
const [editLabelFeature, setEditLabelFeature] = useState<OlFeature<OlGeometry>>(null); | ||
|
||
useEffect(() => { | ||
if (!map) { | ||
return; | ||
} | ||
|
||
setLayers([digitizeLayer ?? DigitizeUtil.getDigitizeLayer(map)]); | ||
setFeatures(new OlCollection()); | ||
}, [map, digitizeLayer]); | ||
|
||
useEffect(() => { | ||
if (!map || !features) { | ||
return undefined; | ||
} | ||
|
||
const newTranslateInteraction = new Translate({ | ||
features, | ||
...translateInteractionConfig | ||
}); | ||
newTranslateInteraction.set('name', 'react-geo-translate-interaction') | ||
newTranslateInteraction.setActive(false); | ||
|
||
map.addInteraction(newTranslateInteraction); | ||
setTranslateInteraction(newTranslateInteraction); | ||
|
||
const newModifyInteraction = new Modify({ | ||
features, | ||
deleteCondition: singleClick, | ||
style: selectStyle ?? DigitizeUtil.DEFAULT_SELECT_STYLE, | ||
...modifyInteractionConfig | ||
}); | ||
newModifyInteraction.set('name', 'react-geo-modify-interaction'); | ||
newModifyInteraction.setActive(false); | ||
|
||
map.addInteraction(newModifyInteraction); | ||
setModifyInteraction(newModifyInteraction); | ||
|
||
return () => { | ||
map.removeInteraction(newModifyInteraction); | ||
map.removeInteraction(translateInteraction); | ||
}; | ||
}, [selectStyle, modifyInteractionConfig, translateInteractionConfig, features, map]); | ||
|
||
useEffect(() => { | ||
if (!modifyInteraction) { | ||
return undefined; | ||
} | ||
|
||
const startKey = modifyInteraction.on('modifystart', e => { | ||
onModifyStart?.(e); | ||
}); | ||
|
||
const endKey = modifyInteraction.on('modifyend', e => { | ||
onModifyEnd?.(e); | ||
}); | ||
|
||
return () => { | ||
unByKey(startKey); | ||
unByKey(endKey); | ||
}; | ||
}, [modifyInteraction, onModifyStart, onModifyEnd]); | ||
|
||
useEffect(() => { | ||
if (!translateInteraction) { | ||
return undefined; | ||
} | ||
|
||
const startKey = translateInteraction.on('translatestart', e => { | ||
onTranslateStart?.(e); | ||
}); | ||
|
||
const endKey = translateInteraction.on('translatestart', e => { | ||
onTranslateEnd?.(e); | ||
}); | ||
|
||
const translatingKey = translateInteraction.on('translating', e => { | ||
onTranslating?.(e); | ||
}) | ||
|
||
return () => { | ||
unByKey(startKey); | ||
unByKey(endKey); | ||
unByKey(translatingKey); | ||
}; | ||
}, [translateInteraction, onTranslateStart, onTranslateEnd, onTranslating]); | ||
|
||
const onToggleInternal = (pressed: boolean, lastClickEvent: any) => { | ||
modifyInteraction.setActive(pressed); | ||
translateInteraction.setActive(pressed); | ||
onToggle?.(pressed, lastClickEvent); | ||
} | ||
|
||
const onFeatureSelect = (event: OlSelectEvent) => { | ||
if (editLabel) { | ||
const labeled = event.selected.find(f => f.get('isLabel')); | ||
setEditLabelFeature(labeled); | ||
} | ||
}; | ||
|
||
const onModalLabelOkInternal = () => { | ||
onModalLabelOk?.(editLabelFeature); | ||
setEditLabelFeature(null); | ||
}; | ||
|
||
const onModalLabelCancelInternal = () => { | ||
onModalLabelCancel?.(); | ||
setEditLabelFeature(null); | ||
}; | ||
|
||
const finalClassName = className | ||
? `${defaultClassName} ${className}` | ||
: defaultClassName; | ||
|
||
if (!layers) { | ||
return null; | ||
} | ||
|
||
const button = <SelectFeaturesButton | ||
layers={layers} | ||
selectStyle={selectStyle} | ||
className={finalClassName} | ||
onToggle={onToggleInternal} | ||
featuresCollection={features} | ||
clearAfterSelect={false} | ||
onFeatureSelect={onFeatureSelect} | ||
{...passThroughProps} | ||
/>; | ||
|
||
if (!editLabel) { | ||
return button; | ||
} else { | ||
return <> | ||
{button} | ||
<FeatureLabelModal | ||
onOk={onModalLabelOkInternal} | ||
onCancel={onModalLabelCancelInternal} | ||
title={modalPromptTitle} | ||
okText={modalPromptOkButtonText} | ||
cancelText={modalPromptCancelButtonText} | ||
maxLabelLineLength={maxLabelLineLength} | ||
feature={editLabelFeature} | ||
/> | ||
</>; | ||
} | ||
}; |