Skip to content

Commit

Permalink
feat(modifybutton): add modify button
Browse files Browse the repository at this point in the history
  • Loading branch information
simonseyock committed Jan 28, 2022
1 parent 1cb354f commit 706f54b
Show file tree
Hide file tree
Showing 2 changed files with 363 additions and 0 deletions.
77 changes: 77 additions & 0 deletions src/Button/ModifyButton/ModifyButton.example.md
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/>
```
286 changes: 286 additions & 0 deletions src/Button/ModifyButton/ModifyButton.tsx
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}
/>
</>;
}
};

0 comments on commit 706f54b

Please sign in to comment.