From b13aa168c3d2bdb836195b46b4bbe64fa2bb47de Mon Sep 17 00:00:00 2001 From: Patrick Moulden Date: Wed, 18 Aug 2021 11:35:46 -0400 Subject: [PATCH 01/13] initial commit --- package-lock.json | 111 ++- package.json | 1 + src/FeatureEditor/FeatureEditor.js | 279 +++++++ src/FeatureEditor/index.js | 1 + src/FeatureEditor/styles.js | 729 ++++++++++++++++++ src/FeatureEditor/utils.js | 333 ++++++++ src/Popup/Popup.js | 40 +- .../PopupActionEdit/PopupActionEdit.js | 135 ++++ .../PopupActions/PopupActionEdit/index.js | 1 + src/Popup/PopupActions/index.js | 1 + src/Popup/PopupInsert/PopupDefaultInsert.js | 15 +- .../PopupInsert/PopupDefaultPage/index.js | 3 + src/classes/Translate.js | 35 + src/classes/index.js | 1 + src/index.js | 1 + 15 files changed, 1648 insertions(+), 38 deletions(-) create mode 100644 src/FeatureEditor/FeatureEditor.js create mode 100644 src/FeatureEditor/index.js create mode 100644 src/FeatureEditor/styles.js create mode 100644 src/FeatureEditor/utils.js create mode 100644 src/Popup/PopupActions/PopupActionEdit/PopupActionEdit.js create mode 100644 src/Popup/PopupActions/PopupActionEdit/index.js create mode 100644 src/classes/Translate.js diff --git a/package-lock.json b/package-lock.json index ba148378..689443a9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1309,7 +1309,6 @@ "version": "7.7.7", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.7.7.tgz", "integrity": "sha512-uCnC2JEVAu8AKB5do1WRIsvrdJ0flYx/A/9f/6chdacnEZ7LmavjdsDXr5ksYBegxtuTPR5Va9/+13QF/kFkCA==", - "dev": true, "requires": { "regenerator-runtime": "^0.13.2" } @@ -11049,20 +11048,36 @@ "d3-collection": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/d3-collection/-/d3-collection-1.0.7.tgz", - "integrity": "sha512-ii0/r5f4sjKNTfh84Di+DpztYwqKhEyUlKoPrzUFfeSkWxjW49xU2QzO9qrPrNkpdI0XJkfzvmTu8V2Zylln6A==", - "dev": true + "integrity": "sha512-ii0/r5f4sjKNTfh84Di+DpztYwqKhEyUlKoPrzUFfeSkWxjW49xU2QzO9qrPrNkpdI0XJkfzvmTu8V2Zylln6A==" }, "d3-color": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-1.4.1.tgz", - "integrity": "sha512-p2sTHSLCJI2QKunbGb7ocOh7DgTAn8IrLx21QRc/BSnodXM4sv6aLQlnfpvehFMLZEfBc6g9pH9SWQccFYfJ9Q==", - "dev": true + "integrity": "sha512-p2sTHSLCJI2QKunbGb7ocOh7DgTAn8IrLx21QRc/BSnodXM4sv6aLQlnfpvehFMLZEfBc6g9pH9SWQccFYfJ9Q==" + }, + "d3-dispatch": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-1.0.6.tgz", + "integrity": "sha512-fVjoElzjhCEy+Hbn8KygnmMS7Or0a9sI2UzGwoB7cCtvI1XpVN9GpoYlnb3xt2YV66oXYb1fLJ8GMvP4hdU1RA==" + }, + "d3-drag": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-1.2.5.tgz", + "integrity": "sha512-rD1ohlkKQwMZYkQlYVCrSFxsWPzI97+W+PaEIBNTMxRuxz9RF0Hi5nJWHGVJ3Om9d2fRTe1yOBINJyy/ahV95w==", + "requires": { + "d3-dispatch": "1", + "d3-selection": "1" + } + }, + "d3-ease": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-1.0.7.tgz", + "integrity": "sha512-lx14ZPYkhNx0s/2HX5sLFUI3mbasHjSSpwO/KaaNACweVwxUruKyWVcb293wMv1RqTPZyZ8kSZ2NogUZNcLOFQ==" }, "d3-format": { "version": "1.4.5", "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-1.4.5.tgz", - "integrity": "sha512-J0piedu6Z8iB6TbIGfZgDzfXxUFN3qQRMofy2oPdXzQibYGqPB/9iMcxr/TGalU+2RsyDO+U4f33id8tbnSRMQ==", - "dev": true + "integrity": "sha512-J0piedu6Z8iB6TbIGfZgDzfXxUFN3qQRMofy2oPdXzQibYGqPB/9iMcxr/TGalU+2RsyDO+U4f33id8tbnSRMQ==" }, "d3-geo": { "version": "1.7.1", @@ -11076,7 +11091,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-1.4.0.tgz", "integrity": "sha512-V9znK0zc3jOPV4VD2zZn0sDhZU3WAE2bmlxdIwwQPPzPjvyLkd8B3JUVdS1IDUFDkWZ72c9qnv1GK2ZagTZ8EA==", - "dev": true, "requires": { "d3-color": "1" } @@ -11091,7 +11105,6 @@ "version": "2.2.2", "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-2.2.2.tgz", "integrity": "sha512-LbeEvGgIb8UMcAa0EATLNX0lelKWGYDQiPdHj+gLblGVhGLyNbaCn3EvrJf0A3Y/uOOU5aD6MTh5ZFCdEwGiCw==", - "dev": true, "requires": { "d3-array": "^1.2.0", "d3-collection": "1", @@ -11101,6 +11114,11 @@ "d3-time-format": "2" } }, + "d3-selection": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-1.4.2.tgz", + "integrity": "sha512-SJ0BqYihzOjDnnlfyeHT0e30k0K1+5sR3d5fNueCNeuhZTnGw4M4o8mqJchSwgKMXCNFo+e2VTChiSJ0vYtXkg==" + }, "d3-shape": { "version": "1.3.7", "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz", @@ -11113,18 +11131,21 @@ "d3-time": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-1.1.0.tgz", - "integrity": "sha512-Xh0isrZ5rPYYdqhAVk8VLnMEidhz5aP7htAADH6MfzgmmicPkTo8LhkLxci61/lCB7n7UmE3bN0leRt+qvkLxA==", - "dev": true + "integrity": "sha512-Xh0isrZ5rPYYdqhAVk8VLnMEidhz5aP7htAADH6MfzgmmicPkTo8LhkLxci61/lCB7n7UmE3bN0leRt+qvkLxA==" }, "d3-time-format": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-2.3.0.tgz", "integrity": "sha512-guv6b2H37s2Uq/GefleCDtbe0XZAuy7Wa49VGkPVPMfLL9qObgBST3lEHJBMUp8S7NdLQAGIvr2KXk8Hc98iKQ==", - "dev": true, "requires": { "d3-time": "1" } }, + "d3-timer": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-1.0.10.tgz", + "integrity": "sha512-B1JDm0XDaQC+uvo4DT79H0XmBskgS3l6Ve+1SBCfxgmtIb1AVrPIoqd+nPSv+loMX8szQ0sVUhGngL7D5QPiXw==" + }, "d3-voronoi": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/d3-voronoi/-/d3-voronoi-1.1.2.tgz", @@ -21811,8 +21832,7 @@ "performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", - "dev": true + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" }, "picomatch": { "version": "2.2.2", @@ -23233,7 +23253,6 @@ "version": "3.4.1", "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==", - "dev": true, "requires": { "performance-now": "^2.1.0" } @@ -23520,8 +23539,36 @@ "react-lifecycles-compat": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", - "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==", - "dev": true + "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" + }, + "react-motion": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/react-motion/-/react-motion-0.5.2.tgz", + "integrity": "sha512-9q3YAvHoUiWlP3cK0v+w1N5Z23HXMj4IF4YuvjvWegWqNPfLXsOBE/V7UvQGpXxHFKRQQcNcVQE31g9SB/6qgQ==", + "requires": { + "performance-now": "^0.2.0", + "prop-types": "^15.5.8", + "raf": "^3.1.0" + }, + "dependencies": { + "performance-now": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-0.2.0.tgz", + "integrity": "sha1-M+8wxcd9TqIcWlOGnZG1bY8lVeU=" + } + } + }, + "react-move": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/react-move/-/react-move-2.9.1.tgz", + "integrity": "sha512-5qKYsJrKKpSypEaaYyR2HBbBgX65htRqKDa8o5OGDkq2VfklmTCbLawtYFpdmcJRqbz4jCYpzo2Rrsazq9HA8Q==", + "requires": { + "@babel/runtime": "^7.2.0", + "d3-interpolate": "^1.3.2", + "d3-timer": "^1.0.9", + "prop-types": "^15.6.2", + "react-lifecycles-compat": "^3.0.4" + } }, "react-overlays": { "version": "4.1.0", @@ -23557,6 +23604,26 @@ "resize-observer-polyfill": "^1.5.0" } }, + "react-rotary-knob": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/react-rotary-knob/-/react-rotary-knob-1.1.8.tgz", + "integrity": "sha512-K/RfMCM1OcDUMPdP7uR/uDBcg84rFgvC1HkgS89EGvZzaHwURCV/9JQTmJNxWeaCZhd2piFKFPoej3QAu6DAWQ==", + "requires": { + "d3-drag": "^1.2.1", + "d3-scale": "^2.0.0", + "d3-selection": "^1.3.0", + "prop-types": "^15.6.0", + "react-svgmt": "^1.1.7", + "uuid": "^3.2.1" + }, + "dependencies": { + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" + } + } + }, "react-router": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.2.0.tgz", @@ -23682,6 +23749,16 @@ "react-virtualized": "^9.21.2" } }, + "react-svgmt": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/react-svgmt/-/react-svgmt-1.1.11.tgz", + "integrity": "sha512-y85s2dy37AJ0z+Sjsnwpkt/5md9nUFh2gkcTZL68uG5ZqIIXf7Na2CrinXWm6anY5JmnMQAXsoMlozcro1H8DA==", + "requires": { + "d3-ease": "^1.0.3", + "react-motion": "^0.5.2", + "react-move": "^2.7.0" + } + }, "react-test-renderer": { "version": "16.14.0", "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-16.14.0.tgz", diff --git a/package.json b/package.json index bf611b22..7bef3372 100644 --- a/package.json +++ b/package.json @@ -96,6 +96,7 @@ "react-draggable": "~4.4.3", "react-dropzone": "^11.3.1", "react-hook-form": "~5.7.2", + "react-rotary-knob": "^1.1.8", "react-select": "~3.1.0", "react-viewer": "^3.2.2", "shp-write": "~0.3.2", diff --git a/src/FeatureEditor/FeatureEditor.js b/src/FeatureEditor/FeatureEditor.js new file mode 100644 index 00000000..95fe5bcc --- /dev/null +++ b/src/FeatureEditor/FeatureEditor.js @@ -0,0 +1,279 @@ +import React, { Component } from 'react' +import PropTypes from 'prop-types' +import Translate from 'classes/Translate' +import { immediateEditStyle } from './styles' +import { getVectorContext } from 'ol/render' +import { Toolbar } from 'Toolbar' +import { Knob } from 'react-rotary-knob' +import { connectToContext } from 'Provider' +import { Card, Grid, CardActions, Button, FormControlLabel } from '@material-ui/core' +import { withStyles } from '@material-ui/core/styles' + +import olInteractionModify from 'ol/interaction/Modify' +import olCollection from 'ol/Collection' + +const ButtonCardActions = withStyles(() => ({ + root: { + padding: '4px 4px 3px 4px' + } +}))(CardActions) + +const LeftCard = withStyles(() => ({ + root: { + borderTopLeftRadius: '4px', + borderBottomLeftRadius: '4px', + borderBottomRightRadius: '0px', + borderTopRightRadius: '0px', + height: '38px' + } +}))(Card) + +const CenterCard = withStyles(() => ({ + root: { + borderRadius: '0px', + paddingLeft: '20px', + marginLeft: '0px', + height: '38px' + } +}))(Card) + +const RightCard = withStyles(() => ({ + root: { + borderTopRightRadius: '4px', + borderBottomRightRadius: '4px', + borderTopLeftRadius: '0px', + borderBottomLeftRadius: '0px', + marginLeft: '0px !important', + height: '38px' + } +}))(Card) + +class FeatureEditor extends Component { + constructor (props) { + super(props) + this.state = { + interactions: [], + editFeatures: undefined, + showMeasurements: false, + canceled: false, + finished: false + } + } + + /** In the past we've used temporary layers added to the map to avoid modifying the original features directly. + However, this leads to a lot of cleanup since those layers need to be added/removed from both the map and state. + That is fine as long as everything goes smoothly but if there is additional logic listening to the map for changes to it's features + or layers than we can get left in a broken state that requires a reload. Using vectorContext.drawFeature instead of + a layer added to the map alleviates at least some of this risk.*/ + _renderFeature = (vectorContext, feature, editStyle = this.props.editStyle) => { // vectorContext.drawFeature only respects a style object and since it is common to have style functions and arrays in Openlayers we need to break the other formats down into objects + const { map, areaUOM, distanceUOM } = this.props + const { showMeasurements } = this.state + const measurementStyles = showMeasurements ? editStyle(feature, map, showMeasurements, { areaUOM, distanceUOM }) : editStyle // eslint-disable-line + const styleType = Array.isArray(measurementStyles) ? 'array' : typeof editStyle + + try { + switch (styleType) { + case 'array': + measurementStyles.map(style => { + const geom = style.getGeometry() ? style.getGeometry() : feature.getGeometry() + + vectorContext.setStyle(style) + vectorContext.drawGeometry(geom) + }) // Arrays of style objects require a feature to be drawn for each style object in the array. This could also be recursively called but that would add extra complexity. + break + case 'function': + this._renderFeature( + vectorContext, + feature, + editStyle(feature, map, showMeasurements, { areaUOM, distanceUOM }) + ) // eslint-disable-line Openlayers style functions return style objects or arrays of style objects so we can call functions recursively. + break + default: // style object + vectorContext.drawFeature(feature, editStyle) + } + } catch (e) { + console.warn(`Geokit was unable to draw the features to the map, this is most likely due to an invalid style object: ${e.message}`, e) // eslint-disable-line + } + } + + _renderEditOverlay = (e) => { + const { map, editStyle } = this.props + const { editFeatures } = this.state + + const vectorContext = getVectorContext(e) + + if (!editFeatures) return // to avoid using a setStateCallback we just check for editFeatures first + + editFeatures.forEach(feature => this._renderFeature(vectorContext, feature, editStyle)) + + return map.render() // render the results asynchronously + } + + _addPostComposeListener = () => { + const { editFeatures } = this.state + + editFeatures.getArray()[0]?.get('_ol_kit_parent')?.on('postrender', this._renderEditOverlay) + } + + _removePostComposeListener = () => { + const { editFeatures } = this.state + + editFeatures.getArray()[0]?.get('_ol_kit_parent')?.un('postrender', this._renderEditOverlay) + } + + _end = () => { // this function cleans up our state and map. If this does not execute correctly we could get stuck in a corrupted map state. + try { + const { map } = this.props + const { interactions } = this.state + + this._removePostComposeListener() + + interactions.forEach(i => map.removeInteraction(i)) + } catch (err) { + console.warn(`Geokit encountered a problem while editing a feature: ${err.message}. \n`, err) // eslint-disable-line no-console + } + } + + showMeasurements = () => { + this.setState({ showMeasurements: !this.state.showMeasurements }) + } + + cancelEdit = () => { + const { onEditCancel, editOpts: { features } } = this.props + + this.setState({ canceled: true }, () => onEditCancel(features)) + } + + finishEdit = () => { + const { onEditFinish } = this.props + const { editFeatures } = this.state + + this.setState({ finished: true }, () => onEditFinish(editFeatures.getArray())) + } + + componentDidMount () { + const { editOpts, map, onEditBegin } = this.props + const { features } = editOpts + const { interactions } = this.state + + if (interactions.length === 2) return + + const editFeatures = new olCollection(features.map(f => f.clone())) // create a collection of clones of the features in props, this avoids modifying the existing features + + const opts = Object.assign({}, editOpts, { + pixelTolerance: 10, + features: editFeatures, + deleteCondition: ({ originalEvent, type }) => { + const { altKey, ctrlKey, shiftKey, metaKey } = originalEvent + const modifierKeyActive = altKey || ctrlKey || shiftKey || metaKey + const altClick = type === 'click' && modifierKeyActive + const rightClick = (type === 'pointerdown' || type === 'click') && originalEvent.button === 2 + + return rightClick || altClick + } + }) + + const translateInteraction = new Translate({ features: editFeatures }) // ol/interaction/translate only checks for features on the map and since we are not adding these to the map (see additional comments) we use our own that knows to look for the features we pass to it whether or not they're on the map. + const modifyInteraction = new olInteractionModify(opts) // ol/interaction/modify doesn't care about the features being on the map or not so it's good to go + // const rotateInteraction = new Rotate(map, editFeatures.item(0), icons.rotate) + + this.setState({ + interactions: [modifyInteraction, translateInteraction], + editFeatures, + canceled: false, + finished: false + }, () => { + this._addPostComposeListener() + }) + // map.addInteraction(rotateInteraction) + map.addInteraction(translateInteraction) + map.addInteraction(modifyInteraction) + onEditBegin({ oldFeatures: features, newFeatures: editFeatures.getArray(), newFeaturesCollection: editFeatures }) // callback function for IAs. FeatureEditor doesn't do anything to the original features so we tell the IA which features they passed in as props and what features we are editing. This should help if they want to add custom logic around these features. + } + + componentWillUnmount () { + const { canceled, finished } = this.state + + if (!canceled && !finished) console.warn(`Geokit FeatureEditor has been unmounted unexpectedly. This may lead undesirable behaviour in your application.`) // eslint-disable-line no-console + + this._end() + } + + render () { + const { translations } = this.props + + return ( + + + + + + + + console.log(val)} /> + } + label={'Rotate'} + /> + + + + + + + + ) + } +} + +FeatureEditor.propTypes = { + editOpts: PropTypes.exact({ + condition: PropTypes.string, + deleteCondition: PropTypes.string, + insertVertexCondition: PropTypes.string, + pixelTolerance: PropTypes.number, + style: PropTypes.object, + source: PropTypes.object, + features: PropTypes.array, + wrapX: PropTypes.bool + }).isRequired, + map: PropTypes.object, + onEditBegin: PropTypes.func, + onEditFinish: PropTypes.func, + onEditCancel: PropTypes.func, + editStyle: PropTypes.oneOfType([ + PropTypes.func, + PropTypes.object, + PropTypes.array + ]), + areaUOM: PropTypes.string, + distanceUOM: PropTypes.string +} + +FeatureEditor.defaultProps = { + editOpts: {}, + onEditFinish: () => {}, + onEditBegin: () => {}, + onEditCancel: () => {}, + editStyle: (feature, map, showMeasurements = false, { areaUOM, distanceUOM }) => { // eslint-disable-line + const translations = { + '_ol_kit.DrawToolbar.cancel': 'Cancel [ESC]', + '_ol_kit.DrawToolbar.finish': 'Finish', + '_ol_kit.DrawToolbar.showMeasurements': 'Show measurements' + } + + return immediateEditStyle( + { areaUOM, distanceUOM, showMeasurements, map, translations, language: navigator.language }, + feature, + map.getView().getResolution() + ) + } +} + +export default connectToContext(FeatureEditor) diff --git a/src/FeatureEditor/index.js b/src/FeatureEditor/index.js new file mode 100644 index 00000000..d1ab7a10 --- /dev/null +++ b/src/FeatureEditor/index.js @@ -0,0 +1 @@ +export { default as FeatureEditor } from './FeatureEditor' diff --git a/src/FeatureEditor/styles.js b/src/FeatureEditor/styles.js new file mode 100644 index 00000000..8624da5c --- /dev/null +++ b/src/FeatureEditor/styles.js @@ -0,0 +1,729 @@ +import olFeature from 'ol/Feature' +import olGeomLineString from 'ol/geom/LineString' +import olGeomMultiPoint from 'ol/geom/MultiPoint' +import olStyleFill from 'ol/style/Fill' +import olStyleStroke from 'ol/style/Stroke' +import olStyleStyle from 'ol/style/Style' +import olStyleCircle from 'ol/style/Circle' +import olStyleIcon from 'ol/style/Icon' +import olStyleText from 'ol/style/Text' +import olGeomCircle from 'ol/geom/Circle' +import olPoint from 'ol/geom/Point' +import centroid from '@turf/centroid' + +// import { icons } from '../../svgs' +// import { flatten, scaleDistanceToMap } from 'utils/general' +// import vmfReserved from 'constants/vmfReserved' +// import { getSVGUri } from 'utils/mapMarkers' +// import { pairCoords, getCoordinates } from 'utils/coords' +// import { getMeasurementText } from 'utils/measure' +// import { getText, calculateScale, getTextWidth } from 'utils/text' +import { pointsFromVertices, translatePoint } from './utils' +// import turf from 'utils/astro' + +export function markerStyler (feature, icons) { + return function () { + const { iconName, iconColor, iconSize } = feature.get('_vmf_icon') + + return new olStyleStyle({ + image: new olStyleIcon({ + anchor: [0.5, 0.5], + opacity: 1, + src: `data:image/svg+xml;utf8,${getSVGUri(icons[iconName], { fill: iconColor, size: iconSize })}`, + scale: 4, + rotation: (feature.get('_vmf_rotation') * -1) || 0 + }) + }) + } +} + +export function resolveStyleFunctionArgs (args) { + // Using function.prototype.bind with additional arguments injects those arguments at the zeroth index of the arguements object and since opts is optional we need to handle a variable arguement object + const argLength = args.length + const feature = args[argLength - 2] || args + const resolution = args[argLength - 1] + const opts = argLength >= 3 ? args[0] : {} + + return { feature, resolution, opts } +} + +export function styleDefault (map, feature, resolution, optsArg = {}) { + const opts = { ...optsArg, map, store: { map } } // backwards compatible with redux store + /** FUNCTION TO DYNAMICALLY DETERMINE STYLE OF FEATURE */ + const image = new olStyleCircle({ + radius: 5, + fill: new olStyleFill({ + color: 'rgba(255, 255, 255, 0.75)' + }), + stroke: new olStyleStroke({ + color: 'rgb(66, 188, 244)', + width: 1 + }) + }) + const styles = { + 'Point': new olStyleStyle({ + image: image + }), + 'LineString': new olStyleStyle({ + stroke: new olStyleStroke({ + color: 'rgb(66, 188, 244)', + width: 5 + }) + }), + 'MultiLineString': new olStyleStyle({ + stroke: new olStyleStroke({ + color: 'rgb(66, 188, 244)', + lineDash: [15, 0, 10], + width: 5 + }) + }), + 'MultiPoint': new olStyleStyle({ + image: image + }), + 'MultiPolygon': new olStyleStyle({ + stroke: new olStyleStroke({ + color: 'rgb(66, 188, 244)', + width: 2 + }), + fill: new olStyleFill({ + color: 'rgba(66, 188, 244, 0.2)' + }) + }), + 'Polygon': new olStyleStyle({ + stroke: new olStyleStroke({ + color: 'rgba(41,128,185,1)', + width: 3 + }), + fill: new olStyleFill({ + color: 'rgba(255, 255, 255, 0.75)' + }) + }), + 'GeometryCollection': new olStyleStyle({ + stroke: new olStyleStroke({ + color: 'magenta', + width: 3 + }), + fill: new olStyleFill({ + color: 'magenta' + }), + image: new olStyleCircle({ + radius: 10, + fill: null, + stroke: new olStyleStroke({ + color: 'magenta' + }) + }) + }), + 'Circle': new olStyleStyle({ + stroke: new olStyleStroke({ + color: 'rgba(41,128,185,1)', + width: 3 + }), + fill: new olStyleFill({ + color: 'rgba(255, 255, 255, 0.75)' + }) + }) + } + const needsVertexLabels = feature.get('_ol_kit_coordinate_labels') + const style = styles[feature.getGeometry().getType()] + const label = () => { + const rotation = -feature.get(vmfReserved.rotation) || 0 // we need the negative of the rotation due to the way ol works with rotation + + return new olStyleStyle({ + geometry: function (olFeature) { + return olFeature.getGeometry() + }, + text: styleText({ + store: opts.store, + placement: 'point', + textAlign: 'left', + textBaseLine: 'bottom', + rotation + }, feature, resolution) + }) + } + + const marker = (iconName, opts) => { + return new olStyleStyle({ + image: new olStyleIcon({ + anchor: [0.5, 0.5], + opacity: 1, + src: `data:image/svg+xml;utf8,${getSVGUri(icons[iconName], { fill: opts.iconColor, size: opts.iconSize })}`, + scale: 4, + rotation: (feature.get('_vmf_rotation') * -1) || 0 + }) + }) + } + const featureProps = feature.getProperties() + const iconProps = featureProps[vmfReserved.marker] + + switch (feature.get(vmfReserved.type)) { + case vmfReserved.annotation: + return label() + case vmfReserved.marker: + return marker(iconProps.iconName, { iconColor: iconProps.iconColor, iconSize: iconProps.iconSize }) + case vmfReserved.drawing: + return [style, ...(needsVertexLabels ? coordinateLabels(getVertices(feature), resolution, opts) : [])] + default: + return [style, ...(needsVertexLabels ? coordinateLabels(getVertices(feature), resolution, opts) : [])] + } +} + +/** + * Style ol/Features + * @function + * @param {object} opts - The config object + * @param {ol/Feature} feature - The feature you want to style + * @param {number} resolution - the resolution of the map + * @returns {object} The style object for the passed feature + */ +export function styleText (...args) { + const { feature, resolution, opts } = resolveStyleFunctionArgs(args) + const label = feature.get('_vmf_label') + const isMeasurement = feature.get('_vmf_type') === '_vmf_measurement' + const isCentroidLabel = feature.get('_ol_kit_needs_centroid_label') + const offsetY = isCentroidLabel ? 20 : isMeasurement ? 0 : (label.fontSize || 16) / 2 // eslint-disable-line + const styleOpts = { + placement: opts.placement || 'point', + textAlign: opts.textAlign, + textBaseline: opts.textBaseLine || 'top', + maxAngle: opts.maxAngle || Infinity, + // These weird calculations provide the most accurate placement relative to the textarea where people edit their text + offsetX: isMeasurement ? 0 : (label.fontSize || 16) / 2, + offsetY: offsetY, + rotation: -feature.get('_vmf_rotation') || 0, + font: `bold ${label.fontSize || 16}px sans-serif`, + stroke: new olStyleStroke({ + // show a black outline unless the text color is black; then show a white outline + color: label.color === '#000000' ? '#ffffff' : '#000000', + width: 3 + }), + text: isMeasurement + ? getMeasurementText(opts.store, feature.getGeometry()) + : getText(label, resolution, opts), + scale: calculateScale(opts.store.map, feature), + fill: new olStyleFill({ + color: label.color || '#ffffff' + }), + image: new olStyleCircle({ + radius: 7, + fill: new olStyleFill({ + color: '#ffcc33' + }) + }), + rotateWithView: false, + overflow: true + } + + return new olStyleText(styleOpts) +} + +export function styleMeasure (map, feature, resolution, optsArg = {}) { + const opts = { ...optsArg, map, store: { map } } // backwards compatible with redux store + const fill = new olStyleFill({ + color: 'rgba(255, 255, 255, 0.5)', + opacity: 1 + }) + const stroke = new olStyleStroke({ + color: '#000000', + width: 3, + lineDash: [10, 0, 10], + opacity: 1 + }) + + // checking to see if .geometry is defined allows us to call this function recursively for geometry collections + const geometry = feature.getGeometry() + const areaLabelsFlag = feature.get('_ol_kit_area_labels') + const distanceLabelsFlag = feature.get('_ol_kit_distance_labels') + const isLegacyMeasure = feature.get('_vmf_type') === '_vmf_measurement' && !(distanceLabelsFlag || areaLabelsFlag) // ignore legacy measure flag if either of the new flags are used. Legacy features won't have the new flags and new features will only have the legacy flag if it also has one of the new flags (we still add the legacy flag to avoid a breaking change). + const needsVertexLabels = feature.get('_ol_kit_coordinate_labels') !== undefined + const needsCentroidLabels = feature.get('_ol_kit_needs_centroid_label') !== undefined + const needsAreaLabels = areaLabelsFlag || isLegacyMeasure + const needsDistanceLabels = distanceLabelsFlag || isLegacyMeasure + const isNotCircle = feature.get('_ol_kit_draw_mode') !== 'circle' + const vertexLabels = needsVertexLabels && isNotCircle ? coordinateLabels(getVertices(feature), resolution, opts) : [] + + switch (geometry.getType()) { + case 'Point': + return [new olStyleStyle({ + image: new olStyleCircle({ + radius: 5, + fill: new olStyleFill({ + color: 'rgba(255, 255, 255, 0.75)' + }), + stroke: new olStyleStroke({ + color: 'rgb(66, 188, 244)', + width: 1 + }) + }) + }), ...vertexLabels] + case 'LineString': { + const lengthLabels = needsDistanceLabels ? [lengthLabel(geometry, resolution, opts, opts)] : [] + + return [new olStyleStyle({ + stroke + }), ...lengthLabels, ...vertexLabels] + } + case 'MultiLineString': { + const lineStrings = geometry.getLineStrings() + const lengthLabels = needsDistanceLabels + ? lineStrings.map(lineString => lengthLabel(lineString, resolution, opts)) : [] + + return [new olStyleStyle({ + stroke + }), ...lengthLabels, ...vertexLabels] + } + case 'MultiPolygon': { + const polygons = geometry.getPolygons() + // create a label for each polygon + const perimeterLabels = needsDistanceLabels + ? polygons.map(polygon => perimeterSegmentLabels(polygon, resolution, opts)) : [] + const areaLabels = needsAreaLabels + ? polygons.map(polygon => areaLabel(polygon, resolution, opts)) : [] + + return [new olStyleStyle({ + stroke, + fill + }), ...areaLabels, ...perimeterLabels.flat(Infinity), ...vertexLabels] + } + case 'Polygon': { + let labels = vertexLabels + + if (needsAreaLabels) labels.push(areaLabel(geometry, resolution, opts)) + if (needsDistanceLabels && isNotCircle) labels = [...labels, ...perimeterSegmentLabels(geometry, resolution, opts)] //eslint-disable-line + if (needsCentroidLabels) labels.push(centroidLabel(geometry, resolution, opts)) + + return [new olStyleStyle({ + stroke, + fill + }), ...labels] + } + case 'GeometryCollection': + return geometry.getGeometries().map(geom => { + return styleMeasure(map, feature, resolution, opts) + }) + case 'Circle': { + const labels = vertexLabels + + if (needsAreaLabels) labels.push(areaLabel(geometry, resolution, opts)) + if (needsCentroidLabels) labels.push(centroidLabel(geometry, resolution, opts)) + + return [new olStyleStyle({ + stroke, + fill + }), ...labels] + } + default: + return new olStyleStyle({ + stroke, + fill + }) + } +} + +export function coordinateLabels (multiPoint, resolution, opts) { + return multiPoint.getPoints().map(point => coordinateLabel(point, resolution, opts)) +} + +export function coordinateLabel (pointGeometry, resolution, opts) { + const geom = pointGeometry.clone() + const pointFeature = new olFeature({ + geometry: geom, + '_vmf_type': '_vmf_measurement', // styleText determines the type of label to render based on the feature's type so we need this temporary feature to be a 'measurement' feature + '_vmf_label': { + fontSize: 16 + } + }) + + return new olStyleStyle({ + text: styleText({ + store: opts, + placement: 'point', + maxAngle: Math.PI / 4, + textAlign: undefined, + textBaseline: 'hanging' + }, pointFeature, resolution), + geometry: geom + }) +} + +export function lengthLabel (lineGeometry, resolution, opts) { + const geom = lineGeometry.clone() + const perimeterFeature = new olFeature({ + geometry: geom, + '_vmf_type': '_vmf_measurement', + '_vmf_label': { + fontSize: 16 + } + }) + + return new olStyleStyle({ + text: styleText({ + store: opts, + placement: 'line', + maxAngle: Math.PI / 4, + textAlign: undefined, + textBaseline: 'hanging' + }, perimeterFeature, resolution), + geometry: geom + }) +} + +export function perimeterLabel (polygonGeometry, resolution, opts) { + const clonedGeom = polygonGeometry.clone() + const perimeterCoords = clonedGeom.getLinearRing(0).getCoordinates() + const perimeterFeature = new olFeature({ + geometry: new olGeomLineString(perimeterCoords), + '_vmf_type': '_vmf_measurement', + '_vmf_label': { + fontSize: 16 + } + }) + + return new olStyleStyle({ + text: styleText({ + store: opts, + placement: 'line', + maxAngle: Math.PI / 4, + textAlign: undefined, + textBaseline: 'hanging' + }, perimeterFeature, resolution), + geometry: clonedGeom + }) +} + +export function perimeterSegmentLabels (polygonGeometry, resolution, opts) { + const labelStyles = [] + const clonedGeom = polygonGeometry.clone() + const perimeterCoords = clonedGeom.getLinearRing(0).getCoordinates() + + for (let i = 0; i < perimeterCoords.length - 1;) { + const segment = [perimeterCoords[i], perimeterCoords[i += 1]] + + if (segment.flat(Infinity).includes(undefined)) break // exit the loop if we get any undefined values. It's better to not label a segment than to break draw entirely. + const segmentGeom = new olGeomLineString(segment) + const segmentFeature = new olFeature({ + geometry: segmentGeom, + '_vmf_type': '_vmf_measurement', + '_vmf_label': { + fontSize: 16 + } + }) + + labelStyles.push(new olStyleStyle({ + text: styleText({ + store: opts, + placement: 'line', + textBaseline: 'hanging' + }, segmentFeature, resolution), + geometry: segmentGeom + })) + } + + return labelStyles +} + +export function areaLabel (polygonGeometry, resolution, opts) { + const areaGeometry = polygonGeometry.clone() + const areaFeature = new olFeature({ + geometry: areaGeometry, + '_vmf_type': '_vmf_measurement', + '_vmf_label': { + fontSize: 16 + } + }) + + return new olStyleStyle({ + text: styleText({ + store: opts + }, areaFeature, resolution), + geometry: areaGeometry + }) +} + +export function centroidLabel (geometry, resolution, opts) { + const point = geometry.getType() === 'Circle ' ? new olPoint(geometry.clone().getCenter()) : turf(centroid, [geometry]).getGeometry() + const pointFeature = new olFeature({ + geometry: point, + '_vmf_type': '_vmf_measurement', // styleText determines the type of label to render based on the feature's type so we need this temporary feature to be a 'measurement' feature + '_vmf_label': { + fontSize: 16 + }, + '_ol_kit_needs_centroid_label': true + }) + + return new olStyleStyle({ + text: styleText({ + store: opts, + placement: 'point', + maxAngle: Math.PI / 4, + textAlign: undefined, + textBaseline: 'hanging' + }, pointFeature, resolution), + geometry: point + }) +} + +export function textStyle (feature, map, styleText) { + return function () { + const opts = { + store: { map } + } + const { rotation } = -feature.get('_vmf_label') || 0 // we need the negative of the rotation due to the way ol works with rotation + + const style = new olStyleStyle({ + geometry: function (feature) { + return feature.getGeometry() + }, + text: styleText({ + store: opts.store, + placement: 'point', + textAlign: 'left', + textBaseLine: 'bottom', + rotation + }, feature, map.getView().getResolution()) + }) + + return style + } +} + +export function getVertices (args) { + const { feature } = resolveStyleFunctionArgs(args) + const geometry = feature.getGeometry() + + switch (geometry.getType()) { + case 'MultiPolygon': { + const polygons = geometry.getPolygons() + const vertexArray = polygons.map(polygon => pointsFromVertices(polygon)) + const vertices = vertexArray.reduce((acc, val) => acc.concat(val), []) + + return new olGeomMultiPoint(vertices) + } + case 'GeometryCollection': { + const deepCoords = getCoordinates(geometry) + const flatCoords = flatten(deepCoords) + const pairedCoords = pairCoords(flatCoords) + + return new olGeomMultiPoint(pairedCoords) + } + default: + return new olGeomMultiPoint(pointsFromVertices(geometry)) + } +} + +/** + * Style an ol/Feature with orange circle vertices, a blue outline, an area label, and a perimeter length label. Can be used with individual features as a style function or call it directly to get a style object for use with `vectorContext.drawFeature` + * @function + * @since 6.3.0 + * @param {object} opts - The config object + * @param {ol/Feature} feature - The feature you want to style + * @param {number} resolution - the resolution of the map + * @returns {object} The style object for the passed feature + */ +export function immediateEditStyle (...args) { // eslint-disable-line + const { feature, resolution, opts = {} } = resolveStyleFunctionArgs(args) + const fill = new olStyleFill({ + color: 'rgba(0, 0, 255, 0.2)' + }) + const stroke = new olStyleStroke({ + color: 'blue', + width: 3 + }) + const image = new olStyleCircle({ + radius: 7, + fill: new olStyleFill({ + color: '#ffcc33' + }) + }) + const vertexGeometry = getVertices(args) + const vertices = [new olStyleStyle({ + image: new olStyleCircle({ + radius: 5, + fill: new olStyleFill({ + color: 'orange' + }) + }), + geometry: vertexGeometry + })] + + // checking to see if opts.geometry is defined allows us to call this function recursively for geometry collections + const geometry = opts.geometry || feature.clone().getGeometry() + const areaLabelsFlag = feature.get('_ol_kit_area_labels') + const distanceLabelsFlag = feature.get('_ol_kit_distance_labels') + const isLegacyMeasure = feature.get('_vmf_type') === '_vmf_measurement' && !(distanceLabelsFlag || areaLabelsFlag) // ignore legacy measure flag if either of the new flags are used. Legacy features won't have the new flags and new features will only have the legacy flag if it also has one of the new flags (we still add the legacy flag to avoid a breaking change). + const needsVertexLabels = feature.get('_ol_kit_coordinate_labels') !== undefined + const needsCentroidLabels = feature.get('_ol_kit_needs_centroid_label') !== undefined + const needsAreaLabels = opts.showMeasurements ? areaLabelsFlag : isLegacyMeasure + const needsDistanceLabels = opts.showMeasurements ? (distanceLabelsFlag) : isLegacyMeasure + const isNotCircle = feature.get('_ol_kit_draw_mode') !== 'circle' + const vertexLabels = (needsVertexLabels && opts.showMeasurements && isNotCircle) ? coordinateLabels(getVertices(feature), resolution, opts) : [] // eslint-disable-line + + switch (geometry.getType()) { + case 'Point': + return [new olStyleStyle({ + image + }), ...vertexLabels] + case 'LineString': { + const lengthLabels = needsDistanceLabels ? [lengthLabel(geometry, resolution, opts)] : [] + + return [new olStyleStyle({ + stroke, + image + }), ...vertices, ...lengthLabels, ...vertexLabels] + } + case 'MultiLineString': { + const lineStrings = geometry.getLineStrings() + const lengthLabels = needsDistanceLabels + ? lineStrings.map(lineString => lengthLabel(lineString, resolution, opts)) : [] + + return [new olStyleStyle({ + stroke, + image + }), ...vertices, ...lengthLabels, ...vertexLabels] + } + case 'MultiPolygon': { + const polygons = geometry.getPolygons() + // create a label for each polygon + const perimeterLabels = needsDistanceLabels + ? polygons.map(polygon => perimeterSegmentLabels(polygon, resolution, opts)) : [] + const areaLabels = needsAreaLabels + ? polygons.map(polygon => areaLabel(polygon, resolution, opts)) : [] + + return [new olStyleStyle({ + stroke, + fill, + image + }), ...vertices, ...areaLabels, ...perimeterLabels.flat(Infinity), ...vertexLabels] + } + case 'Polygon': { + let labels = vertexLabels // eslint-disable-line + + if (needsAreaLabels) labels.push(areaLabel(geometry, resolution, opts)) + if (needsDistanceLabels && isNotCircle) labels = [...labels, ...perimeterSegmentLabels(geometry, resolution, opts)] //eslint-disable-line + if (needsCentroidLabels) labels.push(centroidLabel(geometry, resolution, opts)) + + return [new olStyleStyle({ + stroke, + fill, + image + }), ...vertices, ...labels] + } + case 'GeometryCollection': { + // Recursive. Since feature stores our metadata it needs to be preserved. Therefore we pass the geometry we want to use through the opts object. + const componentStyles = geometry.getGeometries().map(geom => { + return immediateEditStyle.apply(this, [Object.assign(opts, { geometry: geom }), feature, resolution]) + }) + const flatStyles = flatten(componentStyles) + + return flatStyles + } + case 'Circle': { + const labels = vertexLabels + + if (needsAreaLabels) labels.push(areaLabel(geometry, resolution, opts)) + if (needsCentroidLabels) labels.push(centroidLabel(geometry, resolution, opts)) + + return [new olStyleStyle({ + stroke, + fill + }), ...labels] + } + default: + return [new olStyleStyle({ + stroke, + fill, + image + }), ...vertices] + } +} + +export function rotateFeatureArrow (feature, map, rotateIcon) { + return [ + new olStyleStyle({ + image: new olStyleIcon({ + opacity: 1, + rotation: -feature.get('angle'), + anchor: [0.5, 0.5], + src: `data:image/svg+xml;utf8,${getSVGUri(rotateIcon)}`, + scale: 4 + }), + geometry: translateIconGeometry(feature, map), + zIndex: Infinity + }), + new olStyleStyle({ + fill: new olStyleFill({ + color: 'rgb(0, 0, 0, 0)' + }), + stroke: new olStyleStroke({ + color: 'rgba(0, 0, 0, 0)', + width: 1 + }), + geometry: createRotateIconGeometry(feature, map), + zIndex: Infinity + }) + ] +} + +export function translateIconGeometry (feature, map) { + const angle = feature.get('angle') + const point = feature.getGeometry().clone() + + return translatePoint(point, angle, -0.03, map) +} + +export function createRotateIconGeometry (feature, map) { + const translatedGeometry = translateIconGeometry(feature, map) + const center = translatedGeometry.getCoordinates() + const radius = scaleDistanceToMap(15, map) + + return new olGeomCircle(center, radius) +} + +export function editingText (feature, map, styleText) { + return function () { + const opts = { store: { map } } + const label = feature.get('_vmf_label') + const text = getText(label, map.getView().getResolution(), opts) + const { width: baseTextWidth, fontSize: baseFontSize } = getTextWidth(text, map) + const textWidth = baseTextWidth * (label.fontSize / baseFontSize) + const newLabelProps = Object.assign(label, { textWidth }) + + feature.set('_vmf_label', newLabelProps, true) + + return new olStyleStyle({ + geometry: function (feature) { + return feature.getGeometry() + }, + text: styleText({ + store: opts.store, + placement: 'point', + textAlign: 'left', + textBaseLine: 'bottom', + rotation: feature.get('_vmf_id').rotation + }, feature, map.getView().getResolution()), + image: new olStyleCircle({ + radius: 8, + fill: new olStyleFill({ + color: 'orange' + }) + }) + }) + } +} + +export function applyMeasureStyle (map, feature, preferences) { + const safeGetPreference = (key) => preferences?.get?.(key) + const distanceUOM = safeGetPreference('_DISTANCE_LABEL_UOM') + const areaUOM = safeGetPreference('_AREA_LABEL_UOM') + const pointLabels = safeGetPreference('_POINT_LABELS_ENABLED') + const distanceLabelsEnabled = safeGetPreference('_DISTANCE_LABEL_ENABLED') + const areaLabelsEnabled = safeGetPreference('_AREA_LABEL_ENABLED') + const opts = { distanceUOM, areaUOM, map } + + const styleFunc = distanceLabelsEnabled || areaLabelsEnabled || pointLabels + ? styleMeasure(map, feature, map.getView().getResolution(), opts) + : undefined + + feature.setStyle(styleFunc) +} diff --git a/src/FeatureEditor/utils.js b/src/FeatureEditor/utils.js new file mode 100644 index 00000000..e8eea584 --- /dev/null +++ b/src/FeatureEditor/utils.js @@ -0,0 +1,333 @@ +import olGeomPolygon from 'ol/geom/Polygon' +import ugh from 'ugh' +import { radiansToDegrees } from '@turf/helpers' +import turfDestination from '@turf/destination' +import turfDistance from '@turf/distance' +import turfBearing from '@turf/bearing' +import * as turfAssert from '@turf/invariant' + +import { fromCircle } from 'ol/geom/Polygon' +import * as olProj from 'ol/proj' + +import olFormatGeoJson from 'ol/format/GeoJSON' +import olFeature from 'ol/Feature' +import olCollection from 'ol/Collection' + +const defaultDataProjection = 'EPSG:4326' + +export function assertTurf(assertion, hard, ...args) { + try { + turfAssert[assertion].apply(null, args) + } catch (error) { + if (hard) { + throw new Error(error.message) + } else { + return false + } + } + + return true +} + +export default function turf(turfFunc, argArray, projection = 'EPSG:3857') { + const transformedArgs = argArray.map((arg) => { + return transform(arg, projection, true) + }) + const turfResults = turfFunc.apply(this, transformedArgs) + + return transform(turfResults, projection, false) +} + +export function transform(value, projection, toWgs84) { + const { type } = describeType(value) + const format = new olFormatGeoJson({ + defaultDataProjection, + featureProjection: projection + }) + + return toWgs84 + ? input(value, projection, type, format) + : output(value, projection, type, format) +} + +export function input(value, projection, type, format) { + switch (type) { + case 'coordinate': + return olProj.toLonLat(value, projection) + case 'extent': + return olProj.transformExtent(value, projection, defaultDataProjection) + case 'olGeometry': + return format.writeGeometryObject(value) + case 'olFeature': + return format.writeFeatureObject(value) + case 'Array': + return format.writeFeaturesObject(value) + case 'olCollection': + return format.writeFeaturesObject(value.getArray()) + default: + return value + } +} + +export function output(value, projection, type, format) { + switch (type) { + case 'coordinate': + return olProj.transform(value, 'EPSG:4326', projection) + case 'extent': + return olProj.transformExtent(value, defaultDataProjection, projection) + case 'geojsonGeometry': + return format.readGeometry(value) + case 'geojsonFeature': + return format.readFeature(value) + case 'Array': + return value.map(item => { + const itemType = describeType(item).type + + return output(item, projection, itemType, format) + }) + case 'geojsonCollection': + return format.readFeatures(value) + default: + return value + } +} +export function describeType(query) { + if (Array.isArray(query)) { + const containsNumber = assertTurf('containsNumber', false, [query]) + + if (containsNumber) { + switch (query.length) { + case 2: + assertTurf('containsNumber', false, [query]) + + return { + type: 'coordinate' + } + case 4: + assertTurf('containsNumber', false, [query]) + + return { + type: 'extent' + } + default: + return { + type: 'Array', + contains: query.map(val => describeType(val)) + } + } + } else { + return { + type: 'Array', + contains: query.map(val => describeType(val)) + } + } + } else if (query instanceof olFeature) { + return { + type: 'olFeature', + geomType: query.getGeometry().getType() + } + } else if (query.getType) { + return { + type: 'olGeometry', + geomType: query.getType() + } + } else if (query instanceof olCollection) { + return { + type: 'olCollection', + contains: query.getArray().map(val => describeType(val)) + } + } else if (assertTurf('geojsonType', false, query, 'FeatureCollection', 'describeType')) { + return { + type: 'geojsonFeatureCollection' + } + } else if (assertTurf('geojsonType', false, query, 'Feature', 'describeType')) { + return { + type: 'geojsonFeature' + } + } else if (assertTurf('geojsonType', false, query, 'Polygon', 'describeType')) { + return { + type: 'geojsonGeometry', + geomType: 'Polygon' + } + } else if (assertTurf('geojsonType', false, query, 'LineString', 'describeType')) { + return { + type: 'geojsonGeometry', + geomType: 'LineString' + } + } else if (assertTurf('geojsonType', false, query, 'Point', 'describeType')) { + return { + type: 'geojsonGeometry', + geomType: 'Point' + } + } else { + return { + type: typeof query + } + } +} + +export function coordValidator(coord) { + return coord.map((val) => { + return isNaN(val) ? 0 : val || 0 + }) +} + +export function getCoordinates(geometry, optCircle = false) { + switch (geometry.getType()) { + case 'GeometryCollection': + return geometry.getGeometries().map(geom => getCoordinates(geom, optCircle)) + case 'Circle': + return optCircle ? geometry.getCenter() : fromCircle(geometry).getCoordinates() + default: + try { + return geometry.getCoordinates() + } catch (e) { + return undefined + } + } +} + +export const getHeading = (coordinate, index, allowNegative = true) => { + const absCoord = Math.abs(coordinate) + const coord = allowNegative ? coordinate : absCoord + + if (index === 0 && absCoord <= 180) { + if (coordinate > 0) { + return `${coord}˚ E` + } else { + return `${coord}˚ W` + } + } else if (coordinate > 0 && absCoord) { + return `${coord}˚ N` + } else { + return `${coord}˚ S` + } +} + +export function coordDiff(coord1, coord2, view) { + const projection = view.getProjection() + + const args = [coordValidator(coord1), coordValidator(coord2)] + + const distance = turf(turfDistance, args, projection) + const bearing = turf(turfBearing, args, projection) + + return { distance, bearing } +} + +export function targetDestination(startCoord, distance, bearing, view) { + const projection = view.getProjection() + + const coord = olProj.toLonLat(coordValidator(startCoord), projection) + const destination = turfDestination(coord, distance, bearing) + + return olProj.fromLonLat(destination.geometry.coordinates, projection) +} + +export function normalizeExtent(extent) { + const newExtent = [] + + newExtent[0] = extent[1] + newExtent[1] = extent[0] + newExtent[2] = extent[3] + newExtent[3] = extent[2] + + return newExtent.map((coords) => { + if (coords === 90) { + return 89.99 + } else if (coords === -90) { + return -89.99 + } else { + return coords + } + }) +} + +export function pairCoords(flatCoords) { + const pairedCoords = [] + + for (let i = 0; i < flatCoords.length - 1;) { + pairedCoords.push([flatCoords[i], flatCoords[i + 1]]) + i += 2 + } + + return pairedCoords +} + +export function convertXYtoLatLong(map, x, y) { + const coords = map.getCoordinateFromPixel([x, y]) + const transformed = olProj.transform(coords, map.getView().getProjection().getCode(), 'EPSG:4326') + const longitude = Number((Number(transformed[0] || 0) % 180).toFixed(6)) + const latitude = Number((transformed[1] || 0).toFixed(6)) + + return { + longitude, + latitude + } +} + +export function createNewBoxGeom(map, geometry, height, width) { + const coords = geometry.getCoordinates() + const topLeft = map.getPixelFromCoordinate(coords) + + // TODO: ensure this never gets called before the map is loaded + // if this util is called before the map is loaded it's likely possible to get a null value + if (topLeft) { + const moveRight = topLeft[0] + 400 // TODO: change this when we make the width the right dimension again + const moveDown = topLeft[1] + height + const pixelShape = [ + topLeft, + [moveRight, topLeft[1]], + [moveRight, moveDown], + [topLeft[0], moveDown], + topLeft + ] + + return new olGeomPolygon([pixelShape.map(pixel => map.getCoordinateFromPixel(pixel))]) + // TODO: no need to do this if we never get a null value for topLeft (see above comments) + } else { + return geometry + } +} + +export function createTextBox(fontSize = 16, text, resolution) { + const textbox = document.createElement('div') + + textbox.class = 'text-box-size' + textbox.style = `font-size: ${fontSize}; position: absolute; visibility: hidden; height: auto; width: auto; white-space: nowrap;` + textbox.innerHTML += text + document.getElementById('_vmf_hook').appendChild(textbox) + + return { + height: textbox.clientHeight - 1, + width: textbox.clientWidth - resolution + } +} + +export function translatePoint(pointGeom, angle = 0, distance, map) { + const view = map.getView() + const res = view.getResolution() + const projection = view.getProjection() + const properAngle = -radiansToDegrees(angle) + const dist = distance * res + const oneEighty = properAngle > 180 ? properAngle - 360 : properAngle + + return turf(turfDestination, [pointGeom, dist, oneEighty], projection).getGeometry() +} + +export function pointsFromVertices(geometry) { + const featureType = geometry.getType() + const coordinates = getCoordinates(geometry, true) + + if (featureType === 'Polygon' || featureType === 'MultiLineString') { + return [].concat(...coordinates) + } else if (featureType === 'LineString' || featureType === 'MultiPoint') { + return coordinates + } else if (featureType === 'Point' || featureType === 'Circle') { + return [coordinates] + } else { + ugh.warn('Geometries of type %s are not supported', featureType) + + return coordinates + } +} diff --git a/src/Popup/Popup.js b/src/Popup/Popup.js index 5bc9622d..77612309 100644 --- a/src/Popup/Popup.js +++ b/src/Popup/Popup.js @@ -27,7 +27,8 @@ class Popup extends Component { fits: false, pixel: [0, 0] }, - show: false + show: false, + editState: false, } this.timer = 0 @@ -44,6 +45,7 @@ class Popup extends Component { } componentWillUnmount () { + console.log('UNMOUNT') // eslint-disable-line no-console const { map } = this.props map.un('click', this.mapClickHandler) @@ -131,11 +133,12 @@ class Popup extends Component { hidePopup = () => { const { onMapClick } = this.props + const { editState, features } = this.state // stop tracking movement when popup show is set to false this.movementListener && removeMovementListener(this.movementListener) - this.setState({ ...this.defaultState }, () => onMapClick(this.state)) + this.setState({ ...this.defaultState, editState, features: editState ? features : [] }, () => onMapClick(this.state)) } onDragEnd = e => { @@ -148,27 +151,30 @@ class Popup extends Component { }) } + handleEditEvent = e => { + this.setState({ editState: e }) + } + render () { const { actions, children, map, show: propShow } = this.props const { features, loading, popupPosition: { arrow, pixel }, show: stateShow } = this.state const show = typeof propShow === 'boolean' ? propShow : stateShow // keep show prop as source of truth over state + console.log('show:', show) // eslint-disable-line no-console + return ( - !show - ? null - : ( - ReactDOM.createPortal( - - {children || ( // default ui if no children are passed - - )} - , - map.getTargetElement() - ) + ReactDOM.createPortal( + + {children || ( // default ui if no children are passed + + )} + , + map.getTargetElement() ) ) } diff --git a/src/Popup/PopupActions/PopupActionEdit/PopupActionEdit.js b/src/Popup/PopupActions/PopupActionEdit/PopupActionEdit.js new file mode 100644 index 00000000..cd6c5377 --- /dev/null +++ b/src/Popup/PopupActions/PopupActionEdit/PopupActionEdit.js @@ -0,0 +1,135 @@ +import React, { Component } from 'react' +import PropTypes from 'prop-types' +import { PopupActionItem } from 'Popup' +import { FeatureEditor } from 'FeatureEditor' +import { connectToContext } from 'Provider' + +import olStyleStyle from 'ol/style/Style' + +class PopupActionEdit extends Component { + constructor (props) { + super(props) + + this.state = { + showFeatureEditor: false, + style: undefined + } + } + + componentWillUnmount () { + this.onEditCancel([this.props.feature]) + } + + componentDidMount () { + const { feature } = this.props + const style = feature.getStyle() // grab the original feature's style + + // get user preferences + // const prefs = createClient('velocity-map') + + // prefs.promise().then(() => { + // const uom = prefs.get()._UOM || 'imperial' + // Get the feature properties from the preferences or just off the feature + // const areaUOM = prefs.get()._AREA_LABEL_UOM || uom === 'imperial' ? 'acres' : 'hectares' + // const distanceUOM = prefs.get()._DISTANCE_LABEL_UOM || uom === 'imperial' ? 'feet' : 'meters' + + // this.setState({ areaUOM, distanceUOM }) + // }) + + this.setState({ style }) // save that style to state + } + + onEditEnd = (features) => { + const geom = features[0].getGeometry() + const { feature, onEditFinish, persistState, persistedStateKey, onEdit } = this.props + const { style } = this.state + + onEdit(false) + + if (!feature) return + + feature.setGeometry(geom) + + feature.setStyle(style || null) // restore the original feature's style + onEditFinish && onEditFinish(features) + // persistState({ isEditActive: false }, persistedStateKey) + this.setState({ showFeatureEditor: false }) + } + + onEditCancel = (features) => { + const { feature, onEditCancel, persistedStateKey, persistStat, onEdit } = this.props + const { style } = this.state + + onEdit(false) + + feature.setStyle(style || null) // restore the original feature's style + onEditCancel && onEditCancel(features) + // persistState({ isEditActive: false }, persistedStateKey) + this.setState({ showFeatureEditor: false }) + } + + onEditStart = (opts) => { + const { persistState, persistedStateKey, onEdit } = this.props + + onEdit(true) + + // persistState({ isEditActive: true }, persistedStateKey) + const { feature, showPopup, onEditBegin } = this.props + const style = feature.getStyle() // grab the original feature's style + + console.log('showPopup:', showPopup) // eslint-disable-line no-console + + this.setState({ style }) // save that style to state + showPopup && showPopup(false) + onEditBegin && onEditBegin(opts) + feature.setStyle(new olStyleStyle({})) + } + + render () { + const { translations, feature, map } = this.props + const { showFeatureEditor, areaUOM, distanceUOM } = this.state + + return ( +
+ this.setState({ showFeatureEditor: true })} /> + {showFeatureEditor && ( + + )} +
+ ) + } +} + +PopupActionEdit.propTypes = { + translations: PropTypes.object, + feature: PropTypes.object, + map: PropTypes.object, + onEditFinish: PropTypes.func, + onEditCancel: PropTypes.func, + onEditBegin: PropTypes.func, + showPopup: PropTypes.func, + areaUOM: PropTypes.string, + distanceUOM: PropTypes.string, + persistedState: PropTypes.object, + persistedStateKey: PropTypes.string, + persistState: PropTypes.func, + onEdit: PropTypes.func +} + +PopupActionEdit.defaultProps = { + showPopup: () => false, + onEdit: () => false, + persistedStateKey: 'GeokitPopup', + translations: { + 'popup.editGeom': 'Edit Geometry' + } +} + +export default connectToContext(PopupActionEdit) diff --git a/src/Popup/PopupActions/PopupActionEdit/index.js b/src/Popup/PopupActions/PopupActionEdit/index.js new file mode 100644 index 00000000..8ac3d2d2 --- /dev/null +++ b/src/Popup/PopupActions/PopupActionEdit/index.js @@ -0,0 +1 @@ +export { default as PopupActionEdit } from './PopupActionEdit' diff --git a/src/Popup/PopupActions/index.js b/src/Popup/PopupActions/index.js index 52798031..757e40ca 100644 --- a/src/Popup/PopupActions/index.js +++ b/src/Popup/PopupActions/index.js @@ -1,2 +1,3 @@ export * from './PopupActionCopyWkt' export * from './PopupActionGoogleMaps' +export * from './PopupActionEdit' diff --git a/src/Popup/PopupInsert/PopupDefaultInsert.js b/src/Popup/PopupInsert/PopupDefaultInsert.js index 647dfa71..fe0393b2 100644 --- a/src/Popup/PopupInsert/PopupDefaultInsert.js +++ b/src/Popup/PopupInsert/PopupDefaultInsert.js @@ -9,6 +9,7 @@ import { PopupActionGoogleMaps } from 'Popup/PopupActions/PopupActionGoogleMaps' import { PopupActionRemove } from 'Popup/PopupActions/PopupActionRemove' import { PopupActionDuplicate } from 'Popup/PopupActions/PopupActionDuplicate' import { PopupActionCut } from 'Popup/PopupActions/PopupActionCut' +import { PopupActionEdit } from 'Popup/PopupActions/PopupActionEdit' import PopupDefaultPage from './PopupDefaultPage' import PopupPageLayout from './PopupPageLayout' import olGeomPoint from 'ol/geom/Point' @@ -85,7 +86,7 @@ class PopupDefaultInsert extends Component { } render () { - const { actions, features, loading, onClose, onSettingsClick, propertiesFilter, translations } = this.props + const { actions, features, loading, onClose, onSettingsClick, propertiesFilter, translations, onEdit } = this.props const { selectedIdx } = this.state const getChildren = feature => { @@ -94,7 +95,9 @@ class PopupDefaultInsert extends Component { let defaultActions = [, , , - ] + , + , + ] if (!pointGeom) defaultActions = [...defaultActions, ] @@ -104,6 +107,8 @@ class PopupDefaultInsert extends Component { // dedupe the features to remove possible duplicates introduced in ol6 const dedupedFeatures = [...new Set(features).values()] + console.log('dedupedFeatures:', dedupedFeatures) // eslint-disable-line no-console + return ( {dedupedFeatures.length @@ -136,7 +141,8 @@ PopupDefaultInsert.defaultProps = { onClose: () => {}, onSelectFeature: () => {}, propertiesFilter: sanitizeProperties, - translations: en + translations: en, + onEdit: () => false, } PopupDefaultInsert.propTypes = { @@ -166,7 +172,8 @@ PopupDefaultInsert.propTypes = { '_ol_kit.PopupDefaultPage.details': PropTypes.string, '_ol_kit.PopupDefaultPage.actions': PropTypes.string, '_ol_kit.PopupDefaultPage.customize': PropTypes.string - }).isRequired + }).isRequired, + onEdit: PropTypes.func, } export default connectToContext(PopupDefaultInsert) diff --git a/src/Popup/PopupInsert/PopupDefaultPage/index.js b/src/Popup/PopupInsert/PopupDefaultPage/index.js index b29f582f..71baa3a1 100644 --- a/src/Popup/PopupInsert/PopupDefaultPage/index.js +++ b/src/Popup/PopupInsert/PopupDefaultPage/index.js @@ -29,6 +29,9 @@ import { * @example ./example.md */ class PopupDefaultPage extends Component { + componentWillUnmount () { + console.log('UNMOUNT', this.props) // eslint-disable-line no-console + } render () { const { translations, diff --git a/src/classes/Translate.js b/src/classes/Translate.js new file mode 100644 index 00000000..4ecfdff3 --- /dev/null +++ b/src/classes/Translate.js @@ -0,0 +1,35 @@ +import ugh from 'ugh' +import olInteractionTranslate from 'ol/interaction/Translate' +import * as olArray from 'ol/array' + +export default class Translate extends olInteractionTranslate { + constructor (props) { + super(props) + + return this + } + + featuresAtPixel_ = (pixel, map) => { + const mapFeature = map.forEachFeatureAtPixel(pixel, + function (feature) { + if (!this.features_ || + olArray.includes(this.features_.getArray(), feature)) { + return feature + } + }.bind(this), { + layerFilter: this.layerFilter_, + hitTolerance: this.hitTolerance_ + }) + + try { + const coordinate = map.getCoordinateFromPixel(pixel) + const intersectFeatures = this.features_.getArray().filter(f => f.getGeometry().intersectsCoordinate(coordinate)) + + return mapFeature || intersectFeatures[intersectFeatures.length - 1] + } catch (e) { + ugh.warn(`Custom functionality failed: ${e.message}`, e) + + return super.featuresAtPixel_(pixel, map) + } + } +} diff --git a/src/classes/index.js b/src/classes/index.js index c64a1de9..49c1806b 100644 --- a/src/classes/index.js +++ b/src/classes/index.js @@ -1,3 +1,4 @@ export { default as VectorLayer } from './VectorLayer' export { default as VectorTileLayer } from './VectorTileLayer' export { default as Preferences } from './Preferences' +export { default as Translate } from './Translate' diff --git a/src/index.js b/src/index.js index 5d7be61a..42718fa8 100644 --- a/src/index.js +++ b/src/index.js @@ -17,3 +17,4 @@ export * from './TimeSlider' export * from './classes' export * from './Snackbar' export * from './GoogleDirections' +export * from './FeatureEditor' From 0a95f50527fe7049af36b456bed2d9658de47f0a Mon Sep 17 00:00:00 2001 From: Patrick Moulden Date: Tue, 14 Sep 2021 15:52:41 -0400 Subject: [PATCH 02/13] functional edit and rotate --- src/FeatureEditor/FeatureEditor.js | 32 +- src/FeatureEditor/styles.js | 441 +----------------- src/FeatureEditor/utils.js | 145 ++++-- src/Popup/Popup.js | 38 +- .../PopupActionEdit/PopupActionEdit.js | 24 +- src/Popup/PopupInsert/PopupDefaultInsert.js | 4 +- .../PopupInsert/PopupDefaultPage/index.js | 3 - src/__snapshots__/index.test.js.snap | 3 + 8 files changed, 187 insertions(+), 503 deletions(-) diff --git a/src/FeatureEditor/FeatureEditor.js b/src/FeatureEditor/FeatureEditor.js index 95fe5bcc..0d02f93d 100644 --- a/src/FeatureEditor/FeatureEditor.js +++ b/src/FeatureEditor/FeatureEditor.js @@ -8,6 +8,8 @@ import { Knob } from 'react-rotary-knob' import { connectToContext } from 'Provider' import { Card, Grid, CardActions, Button, FormControlLabel } from '@material-ui/core' import { withStyles } from '@material-ui/core/styles' +import centroid from '@turf/centroid' +import { olKitTurf } from './utils' import olInteractionModify from 'ol/interaction/Modify' import olCollection from 'ol/Collection' @@ -56,7 +58,8 @@ class FeatureEditor extends Component { editFeatures: undefined, showMeasurements: false, canceled: false, - finished: false + finished: false, + rotation: 0 } } @@ -86,7 +89,7 @@ class FeatureEditor extends Component { vectorContext, feature, editStyle(feature, map, showMeasurements, { areaUOM, distanceUOM }) - ) // eslint-disable-line Openlayers style functions return style objects or arrays of style objects so we can call functions recursively. + ) // Openlayers style functions return style objects or arrays of style objects so we can call functions recursively. break default: // style object vectorContext.drawFeature(feature, editStyle) @@ -112,13 +115,13 @@ class FeatureEditor extends Component { _addPostComposeListener = () => { const { editFeatures } = this.state - editFeatures.getArray()[0]?.get('_ol_kit_parent')?.on('postrender', this._renderEditOverlay) + editFeatures.getArray()[0]?.get('_ol_kit_parent')?.on('postrender', this._renderEditOverlay) // eslint-disable-line } _removePostComposeListener = () => { const { editFeatures } = this.state - editFeatures.getArray()[0]?.get('_ol_kit_parent')?.un('postrender', this._renderEditOverlay) + editFeatures.getArray()[0]?.get('_ol_kit_parent')?.un('postrender', this._renderEditOverlay) // eslint-disable-line } _end = () => { // this function cleans up our state and map. If this does not execute correctly we could get stuck in a corrupted map state. @@ -175,7 +178,6 @@ class FeatureEditor extends Component { const translateInteraction = new Translate({ features: editFeatures }) // ol/interaction/translate only checks for features on the map and since we are not adding these to the map (see additional comments) we use our own that knows to look for the features we pass to it whether or not they're on the map. const modifyInteraction = new olInteractionModify(opts) // ol/interaction/modify doesn't care about the features being on the map or not so it's good to go - // const rotateInteraction = new Rotate(map, editFeatures.item(0), icons.rotate) this.setState({ interactions: [modifyInteraction, translateInteraction], @@ -185,7 +187,6 @@ class FeatureEditor extends Component { }, () => { this._addPostComposeListener() }) - // map.addInteraction(rotateInteraction) map.addInteraction(translateInteraction) map.addInteraction(modifyInteraction) onEditBegin({ oldFeatures: features, newFeatures: editFeatures.getArray(), newFeaturesCollection: editFeatures }) // callback function for IAs. FeatureEditor doesn't do anything to the original features so we tell the IA which features they passed in as props and what features we are editing. This should help if they want to add custom logic around these features. @@ -199,8 +200,23 @@ class FeatureEditor extends Component { this._end() } + rotate = (val) => { + const { editFeatures, rotation } = this.state + const geometry = editFeatures.getArray()[0].getGeometry() + const anchor = olKitTurf(centroid, [geometry]).getGeometry().getCoordinates() + const rotationDiff = val - rotation + + geometry.rotate(-rotationDiff * (Math.PI / 180), anchor) + this.setState({ rotation: val }) + } + render () { - const { translations } = this.props + // const { translations } = this.props + const knobStyle = { + width: '35px', + height: '35px', + padding: '2px' + } return ( @@ -215,7 +231,7 @@ class FeatureEditor extends Component { console.log(val)} /> + } label={'Rotate'} /> diff --git a/src/FeatureEditor/styles.js b/src/FeatureEditor/styles.js index 8624da5c..8dc1c673 100644 --- a/src/FeatureEditor/styles.js +++ b/src/FeatureEditor/styles.js @@ -5,39 +5,13 @@ import olStyleFill from 'ol/style/Fill' import olStyleStroke from 'ol/style/Stroke' import olStyleStyle from 'ol/style/Style' import olStyleCircle from 'ol/style/Circle' -import olStyleIcon from 'ol/style/Icon' import olStyleText from 'ol/style/Text' -import olGeomCircle from 'ol/geom/Circle' import olPoint from 'ol/geom/Point' import centroid from '@turf/centroid' -// import { icons } from '../../svgs' -// import { flatten, scaleDistanceToMap } from 'utils/general' -// import vmfReserved from 'constants/vmfReserved' -// import { getSVGUri } from 'utils/mapMarkers' -// import { pairCoords, getCoordinates } from 'utils/coords' -// import { getMeasurementText } from 'utils/measure' -// import { getText, calculateScale, getTextWidth } from 'utils/text' -import { pointsFromVertices, translatePoint } from './utils' -// import turf from 'utils/astro' - -export function markerStyler (feature, icons) { - return function () { - const { iconName, iconColor, iconSize } = feature.get('_vmf_icon') - - return new olStyleStyle({ - image: new olStyleIcon({ - anchor: [0.5, 0.5], - opacity: 1, - src: `data:image/svg+xml;utf8,${getSVGUri(icons[iconName], { fill: iconColor, size: iconSize })}`, - scale: 4, - rotation: (feature.get('_vmf_rotation') * -1) || 0 - }) - }) - } -} +import { pointsFromVertices, olKitTurf, pairCoordinates, getCoordinates, getText, calculateScale } from './utils' -export function resolveStyleFunctionArgs (args) { +function resolveStyleFunctionArgs (args) { // Using function.prototype.bind with additional arguments injects those arguments at the zeroth index of the arguements object and since opts is optional we need to handle a variable arguement object const argLength = args.length const feature = args[argLength - 2] || args @@ -47,128 +21,6 @@ export function resolveStyleFunctionArgs (args) { return { feature, resolution, opts } } -export function styleDefault (map, feature, resolution, optsArg = {}) { - const opts = { ...optsArg, map, store: { map } } // backwards compatible with redux store - /** FUNCTION TO DYNAMICALLY DETERMINE STYLE OF FEATURE */ - const image = new olStyleCircle({ - radius: 5, - fill: new olStyleFill({ - color: 'rgba(255, 255, 255, 0.75)' - }), - stroke: new olStyleStroke({ - color: 'rgb(66, 188, 244)', - width: 1 - }) - }) - const styles = { - 'Point': new olStyleStyle({ - image: image - }), - 'LineString': new olStyleStyle({ - stroke: new olStyleStroke({ - color: 'rgb(66, 188, 244)', - width: 5 - }) - }), - 'MultiLineString': new olStyleStyle({ - stroke: new olStyleStroke({ - color: 'rgb(66, 188, 244)', - lineDash: [15, 0, 10], - width: 5 - }) - }), - 'MultiPoint': new olStyleStyle({ - image: image - }), - 'MultiPolygon': new olStyleStyle({ - stroke: new olStyleStroke({ - color: 'rgb(66, 188, 244)', - width: 2 - }), - fill: new olStyleFill({ - color: 'rgba(66, 188, 244, 0.2)' - }) - }), - 'Polygon': new olStyleStyle({ - stroke: new olStyleStroke({ - color: 'rgba(41,128,185,1)', - width: 3 - }), - fill: new olStyleFill({ - color: 'rgba(255, 255, 255, 0.75)' - }) - }), - 'GeometryCollection': new olStyleStyle({ - stroke: new olStyleStroke({ - color: 'magenta', - width: 3 - }), - fill: new olStyleFill({ - color: 'magenta' - }), - image: new olStyleCircle({ - radius: 10, - fill: null, - stroke: new olStyleStroke({ - color: 'magenta' - }) - }) - }), - 'Circle': new olStyleStyle({ - stroke: new olStyleStroke({ - color: 'rgba(41,128,185,1)', - width: 3 - }), - fill: new olStyleFill({ - color: 'rgba(255, 255, 255, 0.75)' - }) - }) - } - const needsVertexLabels = feature.get('_ol_kit_coordinate_labels') - const style = styles[feature.getGeometry().getType()] - const label = () => { - const rotation = -feature.get(vmfReserved.rotation) || 0 // we need the negative of the rotation due to the way ol works with rotation - - return new olStyleStyle({ - geometry: function (olFeature) { - return olFeature.getGeometry() - }, - text: styleText({ - store: opts.store, - placement: 'point', - textAlign: 'left', - textBaseLine: 'bottom', - rotation - }, feature, resolution) - }) - } - - const marker = (iconName, opts) => { - return new olStyleStyle({ - image: new olStyleIcon({ - anchor: [0.5, 0.5], - opacity: 1, - src: `data:image/svg+xml;utf8,${getSVGUri(icons[iconName], { fill: opts.iconColor, size: opts.iconSize })}`, - scale: 4, - rotation: (feature.get('_vmf_rotation') * -1) || 0 - }) - }) - } - const featureProps = feature.getProperties() - const iconProps = featureProps[vmfReserved.marker] - - switch (feature.get(vmfReserved.type)) { - case vmfReserved.annotation: - return label() - case vmfReserved.marker: - return marker(iconProps.iconName, { iconColor: iconProps.iconColor, iconSize: iconProps.iconSize }) - case vmfReserved.drawing: - return [style, ...(needsVertexLabels ? coordinateLabels(getVertices(feature), resolution, opts) : [])] - default: - return [style, ...(needsVertexLabels ? coordinateLabels(getVertices(feature), resolution, opts) : [])] - } -} - /** * Style ol/Features * @function @@ -177,7 +29,7 @@ export function styleDefault (map, feature, resolution, optsArg = {}) { * @param {number} resolution - the resolution of the map * @returns {object} The style object for the passed feature */ -export function styleText (...args) { +function styleText (...args) { const { feature, resolution, opts } = resolveStyleFunctionArgs(args) const label = feature.get('_vmf_label') const isMeasurement = feature.get('_vmf_type') === '_vmf_measurement' @@ -198,9 +50,7 @@ export function styleText (...args) { color: label.color === '#000000' ? '#ffffff' : '#000000', width: 3 }), - text: isMeasurement - ? getMeasurementText(opts.store, feature.getGeometry()) - : getText(label, resolution, opts), + text: getText(label, resolution, opts), scale: calculateScale(opts.store.map, feature), fill: new olStyleFill({ color: label.color || '#ffffff' @@ -218,119 +68,15 @@ export function styleText (...args) { return new olStyleText(styleOpts) } -export function styleMeasure (map, feature, resolution, optsArg = {}) { - const opts = { ...optsArg, map, store: { map } } // backwards compatible with redux store - const fill = new olStyleFill({ - color: 'rgba(255, 255, 255, 0.5)', - opacity: 1 - }) - const stroke = new olStyleStroke({ - color: '#000000', - width: 3, - lineDash: [10, 0, 10], - opacity: 1 - }) - - // checking to see if .geometry is defined allows us to call this function recursively for geometry collections - const geometry = feature.getGeometry() - const areaLabelsFlag = feature.get('_ol_kit_area_labels') - const distanceLabelsFlag = feature.get('_ol_kit_distance_labels') - const isLegacyMeasure = feature.get('_vmf_type') === '_vmf_measurement' && !(distanceLabelsFlag || areaLabelsFlag) // ignore legacy measure flag if either of the new flags are used. Legacy features won't have the new flags and new features will only have the legacy flag if it also has one of the new flags (we still add the legacy flag to avoid a breaking change). - const needsVertexLabels = feature.get('_ol_kit_coordinate_labels') !== undefined - const needsCentroidLabels = feature.get('_ol_kit_needs_centroid_label') !== undefined - const needsAreaLabels = areaLabelsFlag || isLegacyMeasure - const needsDistanceLabels = distanceLabelsFlag || isLegacyMeasure - const isNotCircle = feature.get('_ol_kit_draw_mode') !== 'circle' - const vertexLabels = needsVertexLabels && isNotCircle ? coordinateLabels(getVertices(feature), resolution, opts) : [] - - switch (geometry.getType()) { - case 'Point': - return [new olStyleStyle({ - image: new olStyleCircle({ - radius: 5, - fill: new olStyleFill({ - color: 'rgba(255, 255, 255, 0.75)' - }), - stroke: new olStyleStroke({ - color: 'rgb(66, 188, 244)', - width: 1 - }) - }) - }), ...vertexLabels] - case 'LineString': { - const lengthLabels = needsDistanceLabels ? [lengthLabel(geometry, resolution, opts, opts)] : [] - - return [new olStyleStyle({ - stroke - }), ...lengthLabels, ...vertexLabels] - } - case 'MultiLineString': { - const lineStrings = geometry.getLineStrings() - const lengthLabels = needsDistanceLabels - ? lineStrings.map(lineString => lengthLabel(lineString, resolution, opts)) : [] - - return [new olStyleStyle({ - stroke - }), ...lengthLabels, ...vertexLabels] - } - case 'MultiPolygon': { - const polygons = geometry.getPolygons() - // create a label for each polygon - const perimeterLabels = needsDistanceLabels - ? polygons.map(polygon => perimeterSegmentLabels(polygon, resolution, opts)) : [] - const areaLabels = needsAreaLabels - ? polygons.map(polygon => areaLabel(polygon, resolution, opts)) : [] - - return [new olStyleStyle({ - stroke, - fill - }), ...areaLabels, ...perimeterLabels.flat(Infinity), ...vertexLabels] - } - case 'Polygon': { - let labels = vertexLabels - - if (needsAreaLabels) labels.push(areaLabel(geometry, resolution, opts)) - if (needsDistanceLabels && isNotCircle) labels = [...labels, ...perimeterSegmentLabels(geometry, resolution, opts)] //eslint-disable-line - if (needsCentroidLabels) labels.push(centroidLabel(geometry, resolution, opts)) - - return [new olStyleStyle({ - stroke, - fill - }), ...labels] - } - case 'GeometryCollection': - return geometry.getGeometries().map(geom => { - return styleMeasure(map, feature, resolution, opts) - }) - case 'Circle': { - const labels = vertexLabels - - if (needsAreaLabels) labels.push(areaLabel(geometry, resolution, opts)) - if (needsCentroidLabels) labels.push(centroidLabel(geometry, resolution, opts)) - - return [new olStyleStyle({ - stroke, - fill - }), ...labels] - } - default: - return new olStyleStyle({ - stroke, - fill - }) - } -} - -export function coordinateLabels (multiPoint, resolution, opts) { +function coordinateLabels (multiPoint, resolution, opts) { return multiPoint.getPoints().map(point => coordinateLabel(point, resolution, opts)) } -export function coordinateLabel (pointGeometry, resolution, opts) { +function coordinateLabel (pointGeometry, resolution, opts) { const geom = pointGeometry.clone() const pointFeature = new olFeature({ geometry: geom, - '_vmf_type': '_vmf_measurement', // styleText determines the type of label to render based on the feature's type so we need this temporary feature to be a 'measurement' feature - '_vmf_label': { + _vmf_label: { fontSize: 16 } }) @@ -347,12 +93,12 @@ export function coordinateLabel (pointGeometry, resolution, opts) { }) } -export function lengthLabel (lineGeometry, resolution, opts) { +function lengthLabel (lineGeometry, resolution, opts) { const geom = lineGeometry.clone() const perimeterFeature = new olFeature({ geometry: geom, - '_vmf_type': '_vmf_measurement', - '_vmf_label': { + _vmf_type: '_vmf_measurement', + _vmf_label: { fontSize: 16 } }) @@ -369,30 +115,7 @@ export function lengthLabel (lineGeometry, resolution, opts) { }) } -export function perimeterLabel (polygonGeometry, resolution, opts) { - const clonedGeom = polygonGeometry.clone() - const perimeterCoords = clonedGeom.getLinearRing(0).getCoordinates() - const perimeterFeature = new olFeature({ - geometry: new olGeomLineString(perimeterCoords), - '_vmf_type': '_vmf_measurement', - '_vmf_label': { - fontSize: 16 - } - }) - - return new olStyleStyle({ - text: styleText({ - store: opts, - placement: 'line', - maxAngle: Math.PI / 4, - textAlign: undefined, - textBaseline: 'hanging' - }, perimeterFeature, resolution), - geometry: clonedGeom - }) -} - -export function perimeterSegmentLabels (polygonGeometry, resolution, opts) { +function perimeterSegmentLabels (polygonGeometry, resolution, opts) { const labelStyles = [] const clonedGeom = polygonGeometry.clone() const perimeterCoords = clonedGeom.getLinearRing(0).getCoordinates() @@ -404,8 +127,8 @@ export function perimeterSegmentLabels (polygonGeometry, resolution, opts) { const segmentGeom = new olGeomLineString(segment) const segmentFeature = new olFeature({ geometry: segmentGeom, - '_vmf_type': '_vmf_measurement', - '_vmf_label': { + _vmf_type: '_vmf_measurement', + _vmf_label: { fontSize: 16 } }) @@ -423,12 +146,12 @@ export function perimeterSegmentLabels (polygonGeometry, resolution, opts) { return labelStyles } -export function areaLabel (polygonGeometry, resolution, opts) { +function areaLabel (polygonGeometry, resolution, opts) { const areaGeometry = polygonGeometry.clone() const areaFeature = new olFeature({ geometry: areaGeometry, - '_vmf_type': '_vmf_measurement', - '_vmf_label': { + _vmf_type: '_vmf_measurement', + _vmf_label: { fontSize: 16 } }) @@ -441,15 +164,15 @@ export function areaLabel (polygonGeometry, resolution, opts) { }) } -export function centroidLabel (geometry, resolution, opts) { - const point = geometry.getType() === 'Circle ' ? new olPoint(geometry.clone().getCenter()) : turf(centroid, [geometry]).getGeometry() +function centroidLabel (geometry, resolution, opts) { + const point = geometry.getType() === 'Circle ' ? new olPoint(geometry.clone().getCenter()) : olKitTurf(centroid, [geometry]).getGeometry() const pointFeature = new olFeature({ geometry: point, - '_vmf_type': '_vmf_measurement', // styleText determines the type of label to render based on the feature's type so we need this temporary feature to be a 'measurement' feature - '_vmf_label': { + _vmf_type: '_vmf_measurement', // styleText determines the type of label to render based on the feature's type so we need this temporary feature to be a 'measurement' feature + _vmf_label: { fontSize: 16 }, - '_ol_kit_needs_centroid_label': true + _ol_kit_needs_centroid_label: true }) return new olStyleStyle({ @@ -464,31 +187,7 @@ export function centroidLabel (geometry, resolution, opts) { }) } -export function textStyle (feature, map, styleText) { - return function () { - const opts = { - store: { map } - } - const { rotation } = -feature.get('_vmf_label') || 0 // we need the negative of the rotation due to the way ol works with rotation - - const style = new olStyleStyle({ - geometry: function (feature) { - return feature.getGeometry() - }, - text: styleText({ - store: opts.store, - placement: 'point', - textAlign: 'left', - textBaseLine: 'bottom', - rotation - }, feature, map.getView().getResolution()) - }) - - return style - } -} - -export function getVertices (args) { +function getVertices (args) { const { feature } = resolveStyleFunctionArgs(args) const geometry = feature.getGeometry() @@ -502,8 +201,8 @@ export function getVertices (args) { } case 'GeometryCollection': { const deepCoords = getCoordinates(geometry) - const flatCoords = flatten(deepCoords) - const pairedCoords = pairCoords(flatCoords) + const flatCoords = deepCoords.flat(Infinity) + const pairedCoords = pairCoordinates(flatCoords) return new olGeomMultiPoint(pairedCoords) } @@ -614,7 +313,7 @@ export function immediateEditStyle (...args) { // eslint-disable-line const componentStyles = geometry.getGeometries().map(geom => { return immediateEditStyle.apply(this, [Object.assign(opts, { geometry: geom }), feature, resolution]) }) - const flatStyles = flatten(componentStyles) + const flatStyles = componentStyles.flat(Infinity) return flatStyles } @@ -637,93 +336,3 @@ export function immediateEditStyle (...args) { // eslint-disable-line }), ...vertices] } } - -export function rotateFeatureArrow (feature, map, rotateIcon) { - return [ - new olStyleStyle({ - image: new olStyleIcon({ - opacity: 1, - rotation: -feature.get('angle'), - anchor: [0.5, 0.5], - src: `data:image/svg+xml;utf8,${getSVGUri(rotateIcon)}`, - scale: 4 - }), - geometry: translateIconGeometry(feature, map), - zIndex: Infinity - }), - new olStyleStyle({ - fill: new olStyleFill({ - color: 'rgb(0, 0, 0, 0)' - }), - stroke: new olStyleStroke({ - color: 'rgba(0, 0, 0, 0)', - width: 1 - }), - geometry: createRotateIconGeometry(feature, map), - zIndex: Infinity - }) - ] -} - -export function translateIconGeometry (feature, map) { - const angle = feature.get('angle') - const point = feature.getGeometry().clone() - - return translatePoint(point, angle, -0.03, map) -} - -export function createRotateIconGeometry (feature, map) { - const translatedGeometry = translateIconGeometry(feature, map) - const center = translatedGeometry.getCoordinates() - const radius = scaleDistanceToMap(15, map) - - return new olGeomCircle(center, radius) -} - -export function editingText (feature, map, styleText) { - return function () { - const opts = { store: { map } } - const label = feature.get('_vmf_label') - const text = getText(label, map.getView().getResolution(), opts) - const { width: baseTextWidth, fontSize: baseFontSize } = getTextWidth(text, map) - const textWidth = baseTextWidth * (label.fontSize / baseFontSize) - const newLabelProps = Object.assign(label, { textWidth }) - - feature.set('_vmf_label', newLabelProps, true) - - return new olStyleStyle({ - geometry: function (feature) { - return feature.getGeometry() - }, - text: styleText({ - store: opts.store, - placement: 'point', - textAlign: 'left', - textBaseLine: 'bottom', - rotation: feature.get('_vmf_id').rotation - }, feature, map.getView().getResolution()), - image: new olStyleCircle({ - radius: 8, - fill: new olStyleFill({ - color: 'orange' - }) - }) - }) - } -} - -export function applyMeasureStyle (map, feature, preferences) { - const safeGetPreference = (key) => preferences?.get?.(key) - const distanceUOM = safeGetPreference('_DISTANCE_LABEL_UOM') - const areaUOM = safeGetPreference('_AREA_LABEL_UOM') - const pointLabels = safeGetPreference('_POINT_LABELS_ENABLED') - const distanceLabelsEnabled = safeGetPreference('_DISTANCE_LABEL_ENABLED') - const areaLabelsEnabled = safeGetPreference('_AREA_LABEL_ENABLED') - const opts = { distanceUOM, areaUOM, map } - - const styleFunc = distanceLabelsEnabled || areaLabelsEnabled || pointLabels - ? styleMeasure(map, feature, map.getView().getResolution(), opts) - : undefined - - feature.setStyle(styleFunc) -} diff --git a/src/FeatureEditor/utils.js b/src/FeatureEditor/utils.js index e8eea584..ffccbe21 100644 --- a/src/FeatureEditor/utils.js +++ b/src/FeatureEditor/utils.js @@ -1,4 +1,3 @@ -import olGeomPolygon from 'ol/geom/Polygon' import ugh from 'ugh' import { radiansToDegrees } from '@turf/helpers' import turfDestination from '@turf/destination' @@ -6,7 +5,7 @@ import turfDistance from '@turf/distance' import turfBearing from '@turf/bearing' import * as turfAssert from '@turf/invariant' -import { fromCircle } from 'ol/geom/Polygon' +import olGeomPolygon, { fromCircle } from 'ol/geom/Polygon' import * as olProj from 'ol/proj' import olFormatGeoJson from 'ol/format/GeoJSON' @@ -15,21 +14,7 @@ import olCollection from 'ol/Collection' const defaultDataProjection = 'EPSG:4326' -export function assertTurf(assertion, hard, ...args) { - try { - turfAssert[assertion].apply(null, args) - } catch (error) { - if (hard) { - throw new Error(error.message) - } else { - return false - } - } - - return true -} - -export default function turf(turfFunc, argArray, projection = 'EPSG:3857') { +export function olKitTurf (turfFunc, argArray, projection = 'EPSG:3857') { const transformedArgs = argArray.map((arg) => { return transform(arg, projection, true) }) @@ -38,7 +23,7 @@ export default function turf(turfFunc, argArray, projection = 'EPSG:3857') { return transform(turfResults, projection, false) } -export function transform(value, projection, toWgs84) { +export function transform (value, projection, toWgs84) { const { type } = describeType(value) const format = new olFormatGeoJson({ defaultDataProjection, @@ -50,7 +35,7 @@ export function transform(value, projection, toWgs84) { : output(value, projection, type, format) } -export function input(value, projection, type, format) { +export function input (value, projection, type, format) { switch (type) { case 'coordinate': return olProj.toLonLat(value, projection) @@ -69,7 +54,7 @@ export function input(value, projection, type, format) { } } -export function output(value, projection, type, format) { +export function output (value, projection, type, format) { switch (type) { case 'coordinate': return olProj.transform(value, 'EPSG:4326', projection) @@ -91,7 +76,7 @@ export function output(value, projection, type, format) { return value } } -export function describeType(query) { +export function describeType (query) { if (Array.isArray(query)) { const containsNumber = assertTurf('containsNumber', false, [query]) @@ -166,13 +151,27 @@ export function describeType(query) { } } -export function coordValidator(coord) { +export function assertTurf (assertion, hard, ...args) { + try { + turfAssert[assertion].apply(null, args) + } catch (error) { + if (hard) { + throw new Error(error.message) + } else { + return false + } + } + + return true +} + +export function coordValidator (coord) { return coord.map((val) => { return isNaN(val) ? 0 : val || 0 }) } -export function getCoordinates(geometry, optCircle = false) { +export function getCoordinates (geometry, optCircle = false) { switch (geometry.getType()) { case 'GeometryCollection': return geometry.getGeometries().map(geom => getCoordinates(geom, optCircle)) @@ -204,18 +203,18 @@ export const getHeading = (coordinate, index, allowNegative = true) => { } } -export function coordDiff(coord1, coord2, view) { +export function coordDiff (coord1, coord2, view) { const projection = view.getProjection() const args = [coordValidator(coord1), coordValidator(coord2)] - const distance = turf(turfDistance, args, projection) - const bearing = turf(turfBearing, args, projection) + const distance = olKitTurf(turfDistance, args, projection) + const bearing = olKitTurf(turfBearing, args, projection) return { distance, bearing } } -export function targetDestination(startCoord, distance, bearing, view) { +export function targetDestination (startCoord, distance, bearing, view) { const projection = view.getProjection() const coord = olProj.toLonLat(coordValidator(startCoord), projection) @@ -224,7 +223,7 @@ export function targetDestination(startCoord, distance, bearing, view) { return olProj.fromLonLat(destination.geometry.coordinates, projection) } -export function normalizeExtent(extent) { +export function normalizeExtent (extent) { const newExtent = [] newExtent[0] = extent[1] @@ -243,7 +242,7 @@ export function normalizeExtent(extent) { }) } -export function pairCoords(flatCoords) { +export function pairCoordinates (flatCoords) { const pairedCoords = [] for (let i = 0; i < flatCoords.length - 1;) { @@ -254,7 +253,7 @@ export function pairCoords(flatCoords) { return pairedCoords } -export function convertXYtoLatLong(map, x, y) { +export function convertXYtoLatLong (map, x, y) { const coords = map.getCoordinateFromPixel([x, y]) const transformed = olProj.transform(coords, map.getView().getProjection().getCode(), 'EPSG:4326') const longitude = Number((Number(transformed[0] || 0) % 180).toFixed(6)) @@ -266,7 +265,7 @@ export function convertXYtoLatLong(map, x, y) { } } -export function createNewBoxGeom(map, geometry, height, width) { +export function createNewBoxGeom (map, geometry, height, width) { const coords = geometry.getCoordinates() const topLeft = map.getPixelFromCoordinate(coords) @@ -290,7 +289,7 @@ export function createNewBoxGeom(map, geometry, height, width) { } } -export function createTextBox(fontSize = 16, text, resolution) { +export function createTextBox (fontSize = 16, text, resolution) { const textbox = document.createElement('div') textbox.class = 'text-box-size' @@ -304,7 +303,7 @@ export function createTextBox(fontSize = 16, text, resolution) { } } -export function translatePoint(pointGeom, angle = 0, distance, map) { +export function translatePoint (pointGeom, angle = 0, distance, map) { const view = map.getView() const res = view.getResolution() const projection = view.getProjection() @@ -312,10 +311,10 @@ export function translatePoint(pointGeom, angle = 0, distance, map) { const dist = distance * res const oneEighty = properAngle > 180 ? properAngle - 360 : properAngle - return turf(turfDestination, [pointGeom, dist, oneEighty], projection).getGeometry() + return olKitTurf(turfDestination, [pointGeom, dist, oneEighty], projection).getGeometry() } -export function pointsFromVertices(geometry) { +export function pointsFromVertices (geometry) { const featureType = geometry.getType() const coordinates = getCoordinates(geometry, true) @@ -331,3 +330,79 @@ export function pointsFromVertices(geometry) { return coordinates } } + +export function getText (labelProps = { text: '' }, resolution, opts = {}) { + if (resolution > opts.maxreso) return '' + + switch (opts.type) { + case 'hide': + return '' + case 'shorten': + return labelProps.text.substring(0, 12) + case 'wrap': + return stringDivider(labelProps.text, 32, '\n') + default: + return labelProps.text + } +} + +export function getTextWidth (text = '') { + // We want the width of the longest line of text so split with \n and reverse sort by length + const lines = text.split(`\n`).sort((a, b) => b.length - a.length) + const ctx = document.createElement('canvas').getContext('2d') + const textMetrics = ctx.measureText(lines[0]) + const fontSize = ctx.font.split('px')[0] || 16 + + return { + width: textMetrics.width, + fontSize + } +} + +export function stringDivider (str, width, spaceReplacer) { + if (str.length > width) { + let p = width + + while (p > 0 && (str[p] !== ' ' && str[p] !== '-')) { + p-- + } + if (p > 0) { + let left + + if (str.substring(p, p + 1) === '-') { + left = str.substring(0, p + 1) + } else { + left = str.substring(0, p) + } + const right = str.substring(p + 1) + + return left + spaceReplacer + stringDivider(right, width, spaceReplacer) + } + } + + return str +} + +export function calculateScale (map, feature) { + const currentResolution = map.getView().getResolution() + const vmfLabel = feature.get('_vmf_label') || {} + const labelResolution = vmfLabel.resolution || currentResolution + const scaleFactor = labelResolution / currentResolution + + return vmfLabel.scaling ? scaleFactor : 1 +} + +export function formatStyleString (textStyle) { + const font = textStyle.getFont() || 14 + const scale = textStyle.getScale() || 1 + const px = font.indexOf('px') + const size = font.slice(px - 2, px) + const scaledSize = (size - 6) * scale // Size - 6 is just done to make the text appear the same relative size as it is on the map + const scaledFont = font.replace(`${size}px`, `${scaledSize}px`) + const fillColor = textStyle.getFill().getColor() + const stroke = textStyle.getStroke() + const strokeColor = stroke.getColor() + const strokeWidth = stroke.getWidth() + + return `font: ${scaledFont}; letter-spacing: 0px; paint-order: stroke; stroke: ${strokeColor}; stroke-width: ${strokeWidth}; fill: ${fillColor}` +} diff --git a/src/Popup/Popup.js b/src/Popup/Popup.js index 77612309..fd430eea 100644 --- a/src/Popup/Popup.js +++ b/src/Popup/Popup.js @@ -28,7 +28,7 @@ class Popup extends Component { pixel: [0, 0] }, show: false, - editState: false, + editState: { active: false } } this.timer = 0 @@ -45,7 +45,6 @@ class Popup extends Component { } componentWillUnmount () { - console.log('UNMOUNT') // eslint-disable-line no-console const { map } = this.props map.un('click', this.mapClickHandler) @@ -138,7 +137,10 @@ class Popup extends Component { // stop tracking movement when popup show is set to false this.movementListener && removeMovementListener(this.movementListener) - this.setState({ ...this.defaultState, editState, features: editState ? features : [] }, () => onMapClick(this.state)) + this.setState( + { ...this.defaultState, editState, features: editState?.active ? features : [] }, + () => onMapClick(this.state) + ) } onDragEnd = e => { @@ -157,25 +159,25 @@ class Popup extends Component { render () { const { actions, children, map, show: propShow } = this.props - const { features, loading, popupPosition: { arrow, pixel }, show: stateShow } = this.state + const { features, loading, popupPosition: { arrow, pixel }, show: stateShow, editState } = this.state const show = typeof propShow === 'boolean' ? propShow : stateShow // keep show prop as source of truth over state - console.log('show:', show) // eslint-disable-line no-console + if (!show && !editState?.active) return null return ( - ReactDOM.createPortal( - - {children || ( // default ui if no children are passed - - )} - , - map.getTargetElement() - ) + ReactDOM.createPortal( + + {children || ( // default ui if no children are passed + + )} + , + map.getTargetElement() + ) ) } } diff --git a/src/Popup/PopupActions/PopupActionEdit/PopupActionEdit.js b/src/Popup/PopupActions/PopupActionEdit/PopupActionEdit.js index cd6c5377..8e2a429c 100644 --- a/src/Popup/PopupActions/PopupActionEdit/PopupActionEdit.js +++ b/src/Popup/PopupActions/PopupActionEdit/PopupActionEdit.js @@ -24,18 +24,6 @@ class PopupActionEdit extends Component { const { feature } = this.props const style = feature.getStyle() // grab the original feature's style - // get user preferences - // const prefs = createClient('velocity-map') - - // prefs.promise().then(() => { - // const uom = prefs.get()._UOM || 'imperial' - // Get the feature properties from the preferences or just off the feature - // const areaUOM = prefs.get()._AREA_LABEL_UOM || uom === 'imperial' ? 'acres' : 'hectares' - // const distanceUOM = prefs.get()._DISTANCE_LABEL_UOM || uom === 'imperial' ? 'feet' : 'meters' - - // this.setState({ areaUOM, distanceUOM }) - // }) - this.setState({ style }) // save that style to state } @@ -44,7 +32,7 @@ class PopupActionEdit extends Component { const { feature, onEditFinish, persistState, persistedStateKey, onEdit } = this.props const { style } = this.state - onEdit(false) + onEdit({ active: false }) if (!feature) return @@ -52,7 +40,6 @@ class PopupActionEdit extends Component { feature.setStyle(style || null) // restore the original feature's style onEditFinish && onEditFinish(features) - // persistState({ isEditActive: false }, persistedStateKey) this.setState({ showFeatureEditor: false }) } @@ -60,7 +47,7 @@ class PopupActionEdit extends Component { const { feature, onEditCancel, persistedStateKey, persistStat, onEdit } = this.props const { style } = this.state - onEdit(false) + onEdit({ active: false }) feature.setStyle(style || null) // restore the original feature's style onEditCancel && onEditCancel(features) @@ -70,15 +57,12 @@ class PopupActionEdit extends Component { onEditStart = (opts) => { const { persistState, persistedStateKey, onEdit } = this.props + const { feature, showPopup, onEditBegin } = this.props - onEdit(true) + onEdit({ active: true }) - // persistState({ isEditActive: true }, persistedStateKey) - const { feature, showPopup, onEditBegin } = this.props const style = feature.getStyle() // grab the original feature's style - console.log('showPopup:', showPopup) // eslint-disable-line no-console - this.setState({ style }) // save that style to state showPopup && showPopup(false) onEditBegin && onEditBegin(opts) diff --git a/src/Popup/PopupInsert/PopupDefaultInsert.js b/src/Popup/PopupInsert/PopupDefaultInsert.js index fe0393b2..104d1a20 100644 --- a/src/Popup/PopupInsert/PopupDefaultInsert.js +++ b/src/Popup/PopupInsert/PopupDefaultInsert.js @@ -32,7 +32,7 @@ class PopupDefaultInsert extends Component { super(props) this.state = { - selectedIdx: 0 + selectedIdx: 0, } } @@ -107,8 +107,6 @@ class PopupDefaultInsert extends Component { // dedupe the features to remove possible duplicates introduced in ol6 const dedupedFeatures = [...new Set(features).values()] - console.log('dedupedFeatures:', dedupedFeatures) // eslint-disable-line no-console - return ( {dedupedFeatures.length diff --git a/src/Popup/PopupInsert/PopupDefaultPage/index.js b/src/Popup/PopupInsert/PopupDefaultPage/index.js index 71baa3a1..b29f582f 100644 --- a/src/Popup/PopupInsert/PopupDefaultPage/index.js +++ b/src/Popup/PopupInsert/PopupDefaultPage/index.js @@ -29,9 +29,6 @@ import { * @example ./example.md */ class PopupDefaultPage extends Component { - componentWillUnmount () { - console.log('UNMOUNT', this.props) // eslint-disable-line no-console - } render () { const { translations, diff --git a/src/__snapshots__/index.test.js.snap b/src/__snapshots__/index.test.js.snap index 07fc1b2a..d17705a4 100644 --- a/src/__snapshots__/index.test.js.snap +++ b/src/__snapshots__/index.test.js.snap @@ -27,6 +27,7 @@ Object { "DrawPin": [Function], "DrawPoint": [Function], "DrawPolygon": [Function], + "FeatureEditor": [Function], "FlexMap": Object { "$$typeof": Symbol(react.forward_ref), "attrs": Array [], @@ -216,6 +217,7 @@ Object { "MultiMapManager": [Function], "Popup": [Function], "PopupActionCopyWkt": [Function], + "PopupActionEdit": [Function], "PopupActionGoogleMaps": [Function], "PopupActionGroup": [Function], "PopupActionItem": [Function], @@ -276,6 +278,7 @@ Object { "TabbedPanelPage": [Function], "TimeSlider": [Function], "TimeSliderBase": [Function], + "Translate": [Function], "VectorLayer": [Function], "VectorTileLayer": [Function], "ZoomControls": [Function], From 1afc0669a5adb7634c2fd3bb1608760c8c2ced9c Mon Sep 17 00:00:00 2001 From: Patrick Moulden Date: Tue, 21 Sep 2021 11:48:41 -0400 Subject: [PATCH 03/13] drifting centroid fix --- src/FeatureEditor/FeatureEditor.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/FeatureEditor/FeatureEditor.js b/src/FeatureEditor/FeatureEditor.js index 0d02f93d..c88a7890 100644 --- a/src/FeatureEditor/FeatureEditor.js +++ b/src/FeatureEditor/FeatureEditor.js @@ -180,6 +180,7 @@ class FeatureEditor extends Component { const modifyInteraction = new olInteractionModify(opts) // ol/interaction/modify doesn't care about the features being on the map or not so it's good to go this.setState({ + anchor: olKitTurf(centroid, [editFeatures.getArray()[0].getGeometry()]).getGeometry().getCoordinates(), interactions: [modifyInteraction, translateInteraction], editFeatures, canceled: false, @@ -201,13 +202,11 @@ class FeatureEditor extends Component { } rotate = (val) => { - const { editFeatures, rotation } = this.state + const { editFeatures, rotation, anchor } = this.state const geometry = editFeatures.getArray()[0].getGeometry() - const anchor = olKitTurf(centroid, [geometry]).getGeometry().getCoordinates() const rotationDiff = val - rotation - geometry.rotate(-rotationDiff * (Math.PI / 180), anchor) - this.setState({ rotation: val }) + this.setState({ rotation: val }, () => geometry.rotate(-rotationDiff * (Math.PI / 45), anchor)) } render () { From 40d97f3e1b3b99a0f77e79e8128b9099e128b15f Mon Sep 17 00:00:00 2001 From: Patrick Moulden Date: Tue, 28 Sep 2021 10:45:53 -0400 Subject: [PATCH 04/13] Use context for edit feature bubbling --- src/FeatureEditor/FeatureEditor.js | 19 +++-- src/Map/Map.js | 55 ++++++++++++++- src/Popup/Popup.js | 32 +++++---- .../PopupActionEdit/PopupActionEdit.js | 70 +++---------------- src/Provider/Provider.js | 10 ++- 5 files changed, 97 insertions(+), 89 deletions(-) diff --git a/src/FeatureEditor/FeatureEditor.js b/src/FeatureEditor/FeatureEditor.js index c88a7890..ff40dfc8 100644 --- a/src/FeatureEditor/FeatureEditor.js +++ b/src/FeatureEditor/FeatureEditor.js @@ -142,7 +142,7 @@ class FeatureEditor extends Component { } cancelEdit = () => { - const { onEditCancel, editOpts: { features } } = this.props + const { onEditCancel, features } = this.props this.setState({ canceled: true }, () => onEditCancel(features)) } @@ -155,17 +155,16 @@ class FeatureEditor extends Component { } componentDidMount () { - const { editOpts, map, onEditBegin } = this.props - const { features } = editOpts + const { features, editOpts, map, onEditBegin } = this.props const { interactions } = this.state if (interactions.length === 2) return - const editFeatures = new olCollection(features.map(f => f.clone())) // create a collection of clones of the features in props, this avoids modifying the existing features + const clonedFeatures = new olCollection(features.map(f => f.clone())) // create a collection of clones of the features in props, this avoids modifying the existing features const opts = Object.assign({}, editOpts, { pixelTolerance: 10, - features: editFeatures, + features: clonedFeatures, deleteCondition: ({ originalEvent, type }) => { const { altKey, ctrlKey, shiftKey, metaKey } = originalEvent const modifierKeyActive = altKey || ctrlKey || shiftKey || metaKey @@ -176,13 +175,13 @@ class FeatureEditor extends Component { } }) - const translateInteraction = new Translate({ features: editFeatures }) // ol/interaction/translate only checks for features on the map and since we are not adding these to the map (see additional comments) we use our own that knows to look for the features we pass to it whether or not they're on the map. + const translateInteraction = new Translate({ features: clonedFeatures }) // ol/interaction/translate only checks for features on the map and since we are not adding these to the map (see additional comments) we use our own that knows to look for the features we pass to it whether or not they're on the map. const modifyInteraction = new olInteractionModify(opts) // ol/interaction/modify doesn't care about the features being on the map or not so it's good to go this.setState({ - anchor: olKitTurf(centroid, [editFeatures.getArray()[0].getGeometry()]).getGeometry().getCoordinates(), + anchor: olKitTurf(centroid, [clonedFeatures.getArray()[0].getGeometry()]).getGeometry().getCoordinates(), interactions: [modifyInteraction, translateInteraction], - editFeatures, + editFeatures: clonedFeatures, canceled: false, finished: false }, () => { @@ -190,7 +189,7 @@ class FeatureEditor extends Component { }) map.addInteraction(translateInteraction) map.addInteraction(modifyInteraction) - onEditBegin({ oldFeatures: features, newFeatures: editFeatures.getArray(), newFeaturesCollection: editFeatures }) // callback function for IAs. FeatureEditor doesn't do anything to the original features so we tell the IA which features they passed in as props and what features we are editing. This should help if they want to add custom logic around these features. + onEditBegin({ oldFeatures: features, newFeatures: clonedFeatures.getArray(), newFeaturesCollection: clonedFeatures }) // callback function for IAs. FeatureEditor doesn't do anything to the original features so we tell the IA which features they passed in as props and what features we are editing. This should help if they want to add custom logic around these features. } componentWillUnmount () { @@ -255,9 +254,9 @@ FeatureEditor.propTypes = { pixelTolerance: PropTypes.number, style: PropTypes.object, source: PropTypes.object, - features: PropTypes.array, wrapX: PropTypes.bool }).isRequired, + features: PropTypes.array, map: PropTypes.object, onEditBegin: PropTypes.func, onEditFinish: PropTypes.func, diff --git a/src/Map/Map.js b/src/Map/Map.js index 180c81ce..b14cfa0c 100644 --- a/src/Map/Map.js +++ b/src/Map/Map.js @@ -13,9 +13,12 @@ import { } from './utils' import { StyledMap } from './styled' import { connectToContext } from 'Provider' +import { FeatureEditor } from 'FeatureEditor' import en from 'locales/en' import ugh from 'ugh' +import olStyleStyle from 'ol/style/Style' + /** * A Reactified ol.Map wrapper component * @component @@ -133,8 +136,51 @@ class Map extends React.Component { if (!selectInteractionOnMap) map.addInteraction(this.selectInteraction) } + onEditEnd = (features) => { + const geom = features[0].getGeometry() + const { editFeature, onEditFinish, onEdit, addEditFeatureToContext } = this.props + const { style } = this.state + + onEdit({ active: false }) + + if (!editFeature) return + + editFeature.setGeometry(geom) + + editFeature.setStyle(style || null) // restore the original feature's style + onEditFinish?.(features) + this.setState({ showFeatureEditor: false }) + addEditFeatureToContext(null) + } + + onEditCancel = (features) => { + const { editFeature, onEditCancel, onEdit, addEditFeatureToContext } = this.props + const { style } = this.state + + onEdit({ active: false }) + + editFeature.setStyle(style || null) // restore the original feature's style + onEditCancel?.(features) + this.setState({ showFeatureEditor: false }) + addEditFeatureToContext(null) + } + + onEditStart = (opts) => { + const { onEdit, editFeature, showPopup, onEditBegin } = this.props + + onEdit({ active: true }) + + const style = editFeature.getStyle() // grab the original feature's style + + this.setState({ style }) // save that style to state + showPopup?.(false) + onEditBegin?.(opts) + editFeature.setStyle(new olStyleStyle({})) + } + + render () { - const { children, fullScreen, logoPosition, style, translations } = this.props + const { children, fullScreen, logoPosition, style, translations, editFeature } = this.props const { mapInitialized } = this.state return ( @@ -144,7 +190,8 @@ class Map extends React.Component { id={this.target} fullScreen={fullScreen} style={style}> - + {editFeature && } + } {mapInitialized // wait for map to initialize before rendering children @@ -158,6 +205,7 @@ class Map extends React.Component { Map.defaultProps = { addMapToContext: () => {}, + onEdit: () => {}, fullScreen: false, logoPosition: 'right', isMultiMap: false, @@ -180,6 +228,9 @@ Map.propTypes = { PropTypes.arrayOf(PropTypes.node), PropTypes.node ]), + editFeature: PropTypes.object, + addEditFeatureToContext: PropTypes.func, + onEdit: PropTypes.func, /** if this is set to false, the map will fill it's parent container */ fullScreen: PropTypes.bool, /** optional id to set on openlayers map and htmk id that map renders into (defaulted to unique id internally) */ diff --git a/src/Popup/Popup.js b/src/Popup/Popup.js index fd430eea..46113b17 100644 --- a/src/Popup/Popup.js +++ b/src/Popup/Popup.js @@ -162,22 +162,24 @@ class Popup extends Component { const { features, loading, popupPosition: { arrow, pixel }, show: stateShow, editState } = this.state const show = typeof propShow === 'boolean' ? propShow : stateShow // keep show prop as source of truth over state - if (!show && !editState?.active) return null - return ( - ReactDOM.createPortal( - - {children || ( // default ui if no children are passed - - )} - , - map.getTargetElement() - ) + !show + ? null + : ( + ReactDOM.createPortal( + + {children || ( // default ui if no children are passed + + )} + , + map.getTargetElement() + ) + ) ) } } diff --git a/src/Popup/PopupActions/PopupActionEdit/PopupActionEdit.js b/src/Popup/PopupActions/PopupActionEdit/PopupActionEdit.js index 8e2a429c..7f00dca7 100644 --- a/src/Popup/PopupActions/PopupActionEdit/PopupActionEdit.js +++ b/src/Popup/PopupActions/PopupActionEdit/PopupActionEdit.js @@ -1,11 +1,8 @@ import React, { Component } from 'react' import PropTypes from 'prop-types' import { PopupActionItem } from 'Popup' -import { FeatureEditor } from 'FeatureEditor' import { connectToContext } from 'Provider' -import olStyleStyle from 'ol/style/Style' - class PopupActionEdit extends Component { constructor (props) { super(props) @@ -16,10 +13,6 @@ class PopupActionEdit extends Component { } } - componentWillUnmount () { - this.onEditCancel([this.props.feature]) - } - componentDidMount () { const { feature } = this.props const style = feature.getStyle() // grab the original feature's style @@ -27,65 +20,19 @@ class PopupActionEdit extends Component { this.setState({ style }) // save that style to state } - onEditEnd = (features) => { - const geom = features[0].getGeometry() - const { feature, onEditFinish, persistState, persistedStateKey, onEdit } = this.props - const { style } = this.state - - onEdit({ active: false }) - - if (!feature) return - - feature.setGeometry(geom) - - feature.setStyle(style || null) // restore the original feature's style - onEditFinish && onEditFinish(features) - this.setState({ showFeatureEditor: false }) - } - - onEditCancel = (features) => { - const { feature, onEditCancel, persistedStateKey, persistStat, onEdit } = this.props - const { style } = this.state - - onEdit({ active: false }) - - feature.setStyle(style || null) // restore the original feature's style - onEditCancel && onEditCancel(features) - // persistState({ isEditActive: false }, persistedStateKey) - this.setState({ showFeatureEditor: false }) - } - - onEditStart = (opts) => { - const { persistState, persistedStateKey, onEdit } = this.props - const { feature, showPopup, onEditBegin } = this.props - - onEdit({ active: true }) - - const style = feature.getStyle() // grab the original feature's style - - this.setState({ style }) // save that style to state - showPopup && showPopup(false) - onEditBegin && onEditBegin(opts) - feature.setStyle(new olStyleStyle({})) + onClick = () => { + const { addEditFeatureToContext, feature } = this.props + + addEditFeatureToContext(feature) + this.forceUpdate() } render () { - const { translations, feature, map } = this.props - const { showFeatureEditor, areaUOM, distanceUOM } = this.state + const { translations } = this.props return (
- this.setState({ showFeatureEditor: true })} /> - {showFeatureEditor && ( - - )} +
) } @@ -104,7 +51,8 @@ PopupActionEdit.propTypes = { persistedState: PropTypes.object, persistedStateKey: PropTypes.string, persistState: PropTypes.func, - onEdit: PropTypes.func + onEdit: PropTypes.func, + addEditFeatureToContext: PropTypes.func } PopupActionEdit.defaultProps = { diff --git a/src/Provider/Provider.js b/src/Provider/Provider.js index 4c6cfa20..737f34d7 100644 --- a/src/Provider/Provider.js +++ b/src/Provider/Provider.js @@ -36,6 +36,10 @@ class Provider extends React.Component { this.setState({ mapContext }) } + addEditFeatureToContext = editFeature => { + this.setState({ editFeature }) + } + getContextValue = () => { const { contextProps, map: mapProp, maps: mapsProp, translations } = this.props @@ -51,6 +55,8 @@ class Provider extends React.Component { addMapToContext: this.addMapToContext, map, maps, + editFeature: this.state.editFeature, + addEditFeatureToContext: this.addEditFeatureToContext, persistedState: this.state, persistState: this.persistState, translations, @@ -87,7 +93,9 @@ Provider.propTypes = { /** Array of Openlayers map objects for components that support multi-map (this will override anything passed to map prop) */ maps: PropTypes.array, /** Object with key/value pairs for component translation strings */ - translations: PropTypes.object + translations: PropTypes.object, + /** An Ol feature object that is being edited */ + editFeature: PropTypes.object, } export default Provider From e8b92730bb00d2d99ad677800c2c48d3133c279a Mon Sep 17 00:00:00 2001 From: Patrick Moulden Date: Tue, 28 Sep 2021 11:17:33 -0400 Subject: [PATCH 05/13] snapshot and lint fixes --- .../__snapshots__/DrawContainer.test.js.snap | 4 +-- src/FeatureEditor/FeatureEditor.js | 6 +++- src/Map/Map.js | 31 +++++++------------ src/Popup/Popup.js | 17 ++++------ src/Provider/Provider.js | 2 +- 5 files changed, 26 insertions(+), 34 deletions(-) diff --git a/src/Draw/__snapshots__/DrawContainer.test.js.snap b/src/Draw/__snapshots__/DrawContainer.test.js.snap index c2f7d7c0..e35f4292 100644 --- a/src/Draw/__snapshots__/DrawContainer.test.js.snap +++ b/src/Draw/__snapshots__/DrawContainer.test.js.snap @@ -63,7 +63,7 @@ exports[` should render a basic prebuilt DrawContainer componen    
  should render a basic prebuilt DrawContainer componen  
  { const geom = features[0].getGeometry() - const { editFeature, onEditFinish, onEdit, addEditFeatureToContext } = this.props + const { editFeature, addEditFeatureToContext } = this.props const { style } = this.state - onEdit({ active: false }) - if (!editFeature) return editFeature.setGeometry(geom) editFeature.setStyle(style || null) // restore the original feature's style - onEditFinish?.(features) this.setState({ showFeatureEditor: false }) addEditFeatureToContext(null) } - onEditCancel = (features) => { - const { editFeature, onEditCancel, onEdit, addEditFeatureToContext } = this.props + onEditCancel = () => { + const { editFeature, addEditFeatureToContext } = this.props const { style } = this.state - onEdit({ active: false }) - editFeature.setStyle(style || null) // restore the original feature's style - onEditCancel?.(features) this.setState({ showFeatureEditor: false }) addEditFeatureToContext(null) } - onEditStart = (opts) => { - const { onEdit, editFeature, showPopup, onEditBegin } = this.props - - onEdit({ active: true }) + onEditStart = () => { + const { editFeature } = this.props const style = editFeature.getStyle() // grab the original feature's style this.setState({ style }) // save that style to state - showPopup?.(false) - onEditBegin?.(opts) editFeature.setStyle(new olStyleStyle({})) } - render () { const { children, fullScreen, logoPosition, style, translations, editFeature } = this.props const { mapInitialized } = this.state @@ -190,8 +179,13 @@ class Map extends React.Component { id={this.target} fullScreen={fullScreen} style={style}> - {editFeature && } - + {editFeature && } + } {mapInitialized // wait for map to initialize before rendering children @@ -230,7 +224,6 @@ Map.propTypes = { ]), editFeature: PropTypes.object, addEditFeatureToContext: PropTypes.func, - onEdit: PropTypes.func, /** if this is set to false, the map will fill it's parent container */ fullScreen: PropTypes.bool, /** optional id to set on openlayers map and htmk id that map renders into (defaulted to unique id internally) */ diff --git a/src/Popup/Popup.js b/src/Popup/Popup.js index 46113b17..f2453b95 100644 --- a/src/Popup/Popup.js +++ b/src/Popup/Popup.js @@ -27,8 +27,7 @@ class Popup extends Component { fits: false, pixel: [0, 0] }, - show: false, - editState: { active: false } + show: false } this.timer = 0 @@ -153,19 +152,15 @@ class Popup extends Component { }) } - handleEditEvent = e => { - this.setState({ editState: e }) - } - render () { const { actions, children, map, show: propShow } = this.props - const { features, loading, popupPosition: { arrow, pixel }, show: stateShow, editState } = this.state + const { features, loading, popupPosition: { arrow, pixel }, show: stateShow } = this.state const show = typeof propShow === 'boolean' ? propShow : stateShow // keep show prop as source of truth over state return ( !show - ? null - : ( + ? null + : ( ReactDOM.createPortal( {children || ( // default ui if no children are passed @@ -174,12 +169,12 @@ class Popup extends Component { features={features} loading={loading} onClose={this.hidePopup} - onEdit={this.handleEditEvent} /> + /> )} , map.getTargetElement() ) - ) + ) ) } } diff --git a/src/Provider/Provider.js b/src/Provider/Provider.js index 737f34d7..147eb713 100644 --- a/src/Provider/Provider.js +++ b/src/Provider/Provider.js @@ -95,7 +95,7 @@ Provider.propTypes = { /** Object with key/value pairs for component translation strings */ translations: PropTypes.object, /** An Ol feature object that is being edited */ - editFeature: PropTypes.object, + editFeature: PropTypes.object } export default Provider From 629abfbb11e04617f7f81365c38b5245c343362f Mon Sep 17 00:00:00 2001 From: Patrick Moulden Date: Fri, 1 Oct 2021 16:51:56 -0400 Subject: [PATCH 06/13] fix vertex rendering --- src/FeatureEditor/styles.js | 11 ++++++----- src/FeatureEditor/utils.js | 4 ++-- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/FeatureEditor/styles.js b/src/FeatureEditor/styles.js index 8dc1c673..0df4089d 100644 --- a/src/FeatureEditor/styles.js +++ b/src/FeatureEditor/styles.js @@ -190,19 +190,20 @@ function centroidLabel (geometry, resolution, opts) { function getVertices (args) { const { feature } = resolveStyleFunctionArgs(args) const geometry = feature.getGeometry() + const layout = geometry.getLayout() switch (geometry.getType()) { case 'MultiPolygon': { - const polygons = geometry.getPolygons() - const vertexArray = polygons.map(polygon => pointsFromVertices(polygon)) - const vertices = vertexArray.reduce((acc, val) => acc.concat(val), []) + const coordinates = geometry.getCoordinates() + const flatCoords = coordinates.flat(Infinity) + const pairedCoords = pairCoordinates(flatCoords, layout.length) - return new olGeomMultiPoint(vertices) + return new olGeomMultiPoint(pairedCoords, 'XYZ') } case 'GeometryCollection': { const deepCoords = getCoordinates(geometry) const flatCoords = deepCoords.flat(Infinity) - const pairedCoords = pairCoordinates(flatCoords) + const pairedCoords = pairCoordinates(flatCoords, layout.length) return new olGeomMultiPoint(pairedCoords) } diff --git a/src/FeatureEditor/utils.js b/src/FeatureEditor/utils.js index ffccbe21..2e6965fa 100644 --- a/src/FeatureEditor/utils.js +++ b/src/FeatureEditor/utils.js @@ -242,12 +242,12 @@ export function normalizeExtent (extent) { }) } -export function pairCoordinates (flatCoords) { +export function pairCoordinates (flatCoords, groupSize = 2) { const pairedCoords = [] for (let i = 0; i < flatCoords.length - 1;) { pairedCoords.push([flatCoords[i], flatCoords[i + 1]]) - i += 2 + i += groupSize } return pairedCoords From ac1865d4c79f6dea8b95d5abf65d3b5ee0675eb1 Mon Sep 17 00:00:00 2001 From: Patrick Moulden Date: Wed, 6 Oct 2021 15:59:58 -0400 Subject: [PATCH 07/13] make feature editor more self contained --- src/FeatureEditor/FeatureEditor.js | 117 +++++++++++++++++------------ src/FeatureEditor/styles.js | 2 +- src/Map/Map.js | 45 +---------- src/locales/en.js | 6 +- 4 files changed, 78 insertions(+), 92 deletions(-) diff --git a/src/FeatureEditor/FeatureEditor.js b/src/FeatureEditor/FeatureEditor.js index 34c84d9a..49728205 100644 --- a/src/FeatureEditor/FeatureEditor.js +++ b/src/FeatureEditor/FeatureEditor.js @@ -13,6 +13,7 @@ import { olKitTurf } from './utils' import olInteractionModify from 'ol/interaction/Modify' import olCollection from 'ol/Collection' +import olStyleStyle from 'ol/style/Style' const ButtonCardActions = withStyles(() => ({ root: { @@ -55,11 +56,12 @@ class FeatureEditor extends Component { super(props) this.state = { interactions: [], - editFeatures: undefined, + editingFeature: null, showMeasurements: false, canceled: false, finished: false, - rotation: 0 + rotation: 0, + style: null } } @@ -101,27 +103,27 @@ class FeatureEditor extends Component { _renderEditOverlay = (e) => { const { map, editStyle } = this.props - const { editFeatures } = this.state + const { editingFeature } = this.state const vectorContext = getVectorContext(e) - if (!editFeatures) return // to avoid using a setStateCallback we just check for editFeatures first + if (!editingFeature) return // to avoid using a setStateCallback we just check for editFeatures first - editFeatures.forEach(feature => this._renderFeature(vectorContext, feature, editStyle)) + this._renderFeature(vectorContext, editingFeature, editStyle) return map.render() // render the results asynchronously } _addPostComposeListener = () => { - const { editFeatures } = this.state + const { editingFeature } = this.state - editFeatures.getArray()[0]?.get('_ol_kit_parent')?.on('postrender', this._renderEditOverlay) // eslint-disable-line + editingFeature?.get('_ol_kit_parent')?.on('postrender', this._renderEditOverlay) // eslint-disable-line } _removePostComposeListener = () => { - const { editFeatures } = this.state + const { editingFeature } = this.state - editFeatures.getArray()[0]?.get('_ol_kit_parent')?.un('postrender', this._renderEditOverlay) // eslint-disable-line + editingFeature?.get('_ol_kit_parent')?.un('postrender', this._renderEditOverlay) // eslint-disable-line } _end = () => { // this function cleans up our state and map. If this does not execute correctly we could get stuck in a corrupted map state. @@ -132,6 +134,7 @@ class FeatureEditor extends Component { this._removePostComposeListener() interactions.forEach(i => map.removeInteraction(i)) + this.setState({ editingFeature: null, style: null }) } catch (err) { console.warn(`Geokit encountered a problem while editing a feature: ${err.message}. \n`, err) // eslint-disable-line no-console } @@ -142,29 +145,40 @@ class FeatureEditor extends Component { } cancelEdit = () => { - const { onEditCancel, features } = this.props + const { onEditCancel, editFeature, addEditFeatureToContext } = this.props + const { style } = this.state - this.setState({ canceled: true }, () => onEditCancel(features)) + this.setState({ canceled: true }, () => onEditCancel(editFeature, addEditFeatureToContext, style)) } finishEdit = () => { - const { onEditFinish } = this.props - const { editFeatures } = this.state + const { onEditFinish, addEditFeatureToContext, editFeature } = this.props + const { editingFeature, style } = this.state - this.setState({ finished: true }, () => onEditFinish(editFeatures.getArray())) + this.setState({ finished: true }, () => onEditFinish(editFeature, editingFeature, addEditFeatureToContext, style)) } - componentDidMount () { - const { features, editOpts, map, onEditBegin } = this.props + componentWillUnmount = () => { + const { canceled, finished } = this.state + + if (!canceled && !finished) console.warn(`Geokit FeatureEditor has been unmounted unexpectedly. This may lead undesirable behaviour in your application.`) // eslint-disable-line no-console + + return this._end() + } + + componentDidUpdate (prevProps) { + const { editOpts, map, onEditBegin, editFeature } = this.props const { interactions } = this.state - if (interactions.length === 2) return + if (prevProps.editFeature && !editFeature) return this._end() - const clonedFeatures = new olCollection(features.map(f => f.clone())) // create a collection of clones of the features in props, this avoids modifying the existing features + if (interactions.length === 2 || !editFeature) return + + const clonedFeature = editFeature.clone() // create a collection of clones of the features in props, this avoids modifying the existing features const opts = Object.assign({}, editOpts, { pixelTolerance: 10, - features: clonedFeatures, + features: new olCollection([clonedFeature]), deleteCondition: ({ originalEvent, type }) => { const { altKey, ctrlKey, shiftKey, metaKey } = originalEvent const modifierKeyActive = altKey || ctrlKey || shiftKey || metaKey @@ -175,58 +189,51 @@ class FeatureEditor extends Component { } }) - const translateInteraction = new Translate({ features: clonedFeatures }) // ol/interaction/translate only checks for features on the map and since we are not adding these to the map (see additional comments) we use our own that knows to look for the features we pass to it whether or not they're on the map. + const translateInteraction = new Translate({ features: new olCollection([clonedFeature]) }) // ol/interaction/translate only checks for features on the map and since we are not adding these to the map (see additional comments) we use our own that knows to look for the features we pass to it whether or not they're on the map. const modifyInteraction = new olInteractionModify(opts) // ol/interaction/modify doesn't care about the features being on the map or not so it's good to go + const style = clonedFeature.getStyle() this.setState({ - anchor: olKitTurf(centroid, [clonedFeatures.getArray()[0].getGeometry()]).getGeometry().getCoordinates(), + anchor: olKitTurf(centroid, [clonedFeature.getGeometry()]).getGeometry().getCoordinates(), interactions: [modifyInteraction, translateInteraction], - editFeatures: clonedFeatures, + editingFeature: clonedFeature, canceled: false, - finished: false + finished: false, + style }, () => { this._addPostComposeListener() }) map.addInteraction(translateInteraction) map.addInteraction(modifyInteraction) - onEditBegin({ - oldFeatures: features, - newFeatures: clonedFeatures.getArray(), - newFeaturesCollection: clonedFeatures - }) // callback function for IAs. FeatureEditor doesn't do anything to the original features so we tell the IA which features they passed in as props and what features we are editing. This should help if they want to add custom logic around these features. - } - - componentWillUnmount () { - const { canceled, finished } = this.state - - if (!canceled && !finished) console.warn(`Geokit FeatureEditor has been unmounted unexpectedly. This may lead undesirable behaviour in your application.`) // eslint-disable-line no-console - - this._end() + onEditBegin(clonedFeature) // callback function for IAs. FeatureEditor doesn't do anything to the original features so we tell the IA which features they passed in as props and what features we are editing. This should help if they want to add custom logic around these features. } rotate = (val) => { - const { editFeatures, rotation, anchor } = this.state - const geometry = editFeatures.getArray()[0].getGeometry() + const { editingFeature, rotation, anchor } = this.state + const geometry = editingFeature.getGeometry() const rotationDiff = val - rotation this.setState({ rotation: val }, () => geometry.rotate(-rotationDiff * (Math.PI / 45), anchor)) } render () { - // const { translations } = this.props + const { translations } = this.props + const { editingFeature } = this.state const knobStyle = { width: '35px', height: '35px', padding: '2px' } + if (!editingFeature) return null + return ( @@ -235,12 +242,12 @@ class FeatureEditor extends Component { control={ } - label={'Rotate'} + label={translations['_ol_kit.edit.rotate']} /> @@ -260,7 +267,8 @@ FeatureEditor.propTypes = { source: PropTypes.object, wrapX: PropTypes.bool }).isRequired, - features: PropTypes.array, + editFeature: PropTypes.object, + addEditFeatureToContext: PropTypes.func, map: PropTypes.object, onEditBegin: PropTypes.func, onEditFinish: PropTypes.func, @@ -271,14 +279,29 @@ FeatureEditor.propTypes = { PropTypes.array ]), areaUOM: PropTypes.string, - distanceUOM: PropTypes.string + distanceUOM: PropTypes.string, + translations: PropTypes.object } FeatureEditor.defaultProps = { editOpts: {}, - onEditFinish: () => {}, - onEditBegin: () => {}, - onEditCancel: () => {}, + onEditFinish: (feature, updatedFeature, addEditFeatureToContext, style) => { + const geom = updatedFeature.getGeometry() + + if (!feature) return + + feature.setGeometry(geom) + + feature.setStyle(style || null) // restore the original feature's style + addEditFeatureToContext(null) + }, + onEditBegin: (feature) => { + feature.setStyle(new olStyleStyle({})) + }, + onEditCancel: (feature, addEditFeatureToContext, style) => { + feature.setStyle(style || null) // restore the original feature's style + addEditFeatureToContext(null) + }, editStyle: (feature, map, showMeasurements = false, { areaUOM, distanceUOM }) => { // eslint-disable-line const translations = { '_ol_kit.DrawToolbar.cancel': 'Cancel [ESC]', diff --git a/src/FeatureEditor/styles.js b/src/FeatureEditor/styles.js index 0df4089d..8c7df459 100644 --- a/src/FeatureEditor/styles.js +++ b/src/FeatureEditor/styles.js @@ -198,7 +198,7 @@ function getVertices (args) { const flatCoords = coordinates.flat(Infinity) const pairedCoords = pairCoordinates(flatCoords, layout.length) - return new olGeomMultiPoint(pairedCoords, 'XYZ') + return new olGeomMultiPoint(pairedCoords, layout) } case 'GeometryCollection': { const deepCoords = getCoordinates(geometry) diff --git a/src/Map/Map.js b/src/Map/Map.js index 68100bfa..0afba43c 100644 --- a/src/Map/Map.js +++ b/src/Map/Map.js @@ -17,8 +17,6 @@ import { FeatureEditor } from 'FeatureEditor' import en from 'locales/en' import ugh from 'ugh' -import olStyleStyle from 'ol/style/Style' - /** * A Reactified ol.Map wrapper component * @component @@ -136,40 +134,8 @@ class Map extends React.Component { if (!selectInteractionOnMap) map.addInteraction(this.selectInteraction) } - onEditEnd = (features) => { - const geom = features[0].getGeometry() - const { editFeature, addEditFeatureToContext } = this.props - const { style } = this.state - - if (!editFeature) return - - editFeature.setGeometry(geom) - - editFeature.setStyle(style || null) // restore the original feature's style - this.setState({ showFeatureEditor: false }) - addEditFeatureToContext(null) - } - - onEditCancel = () => { - const { editFeature, addEditFeatureToContext } = this.props - const { style } = this.state - - editFeature.setStyle(style || null) // restore the original feature's style - this.setState({ showFeatureEditor: false }) - addEditFeatureToContext(null) - } - - onEditStart = () => { - const { editFeature } = this.props - - const style = editFeature.getStyle() // grab the original feature's style - - this.setState({ style }) // save that style to state - editFeature.setStyle(new olStyleStyle({})) - } - render () { - const { children, fullScreen, logoPosition, style, translations, editFeature } = this.props + const { children, fullScreen, logoPosition, style, translations } = this.props const { mapInitialized } = this.state return ( @@ -179,12 +145,7 @@ class Map extends React.Component { id={this.target} fullScreen={fullScreen} style={style}> - {editFeature && } + } @@ -222,8 +183,6 @@ Map.propTypes = { PropTypes.arrayOf(PropTypes.node), PropTypes.node ]), - editFeature: PropTypes.object, - addEditFeatureToContext: PropTypes.func, /** if this is set to false, the map will fill it's parent container */ fullScreen: PropTypes.bool, /** optional id to set on openlayers map and htmk id that map renders into (defaulted to unique id internally) */ diff --git a/src/locales/en.js b/src/locales/en.js index 82d6b606..9b45a077 100644 --- a/src/locales/en.js +++ b/src/locales/en.js @@ -120,5 +120,9 @@ export default { // Google Directions '_ol_kit.directions.placeOriginPoint': 'Place a point at your origin', '_ol_kit.directions.placeWaypoint': 'Now place a point to add a destination and click finish when you\'re done:', - '_ol_kit.directions.waypointLabel': 'Waypoint' + '_ol_kit.directions.waypointLabel': 'Waypoint', + // FeatureEditor + '_ol_kit.edit.cancel': 'Cancel', + '_ol_kit.edit.finish': 'Finish', + '_ol_kit.edit.rotate': 'Rotate' } From 46a078859e7e2084affbef27b8f3bda59b3aca76 Mon Sep 17 00:00:00 2001 From: Patrick Moulden Date: Fri, 8 Oct 2021 10:52:46 -0400 Subject: [PATCH 08/13] Enable controlling edit through mount/unmount --- package-lock.json | 6 +-- src/FeatureEditor/FeatureEditor.js | 70 +++++++++++++++++------------- src/Map/Map.js | 2 - 3 files changed, 42 insertions(+), 36 deletions(-) diff --git a/package-lock.json b/package-lock.json index 689443a9..0d021bcb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9695,9 +9695,9 @@ } }, "caniuse-lite": { - "version": "1.0.30001150", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001150.tgz", - "integrity": "sha512-kiNKvihW0m36UhAFnl7bOAv0i1K1f6wpfVtTF5O5O82XzgtBnb05V0XeV3oZ968vfg2sRNChsHw8ASH2hDfoYQ==", + "version": "1.0.30001265", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001265.tgz", + "integrity": "sha512-YzBnspggWV5hep1m9Z6sZVLOt7vrju8xWooFAgN6BA5qvy98qPAPb7vNUzypFaoh2pb3vlfzbDO8tB57UPGbtw==", "dev": true }, "capture-exit": { diff --git a/src/FeatureEditor/FeatureEditor.js b/src/FeatureEditor/FeatureEditor.js index 49728205..68b5ed97 100644 --- a/src/FeatureEditor/FeatureEditor.js +++ b/src/FeatureEditor/FeatureEditor.js @@ -58,8 +58,6 @@ class FeatureEditor extends Component { interactions: [], editingFeature: null, showMeasurements: false, - canceled: false, - finished: false, rotation: 0, style: null } @@ -71,9 +69,9 @@ class FeatureEditor extends Component { or layers than we can get left in a broken state that requires a reload. Using vectorContext.drawFeature instead of a layer added to the map alleviates at least some of this risk.*/ _renderFeature = (vectorContext, feature, editStyle = this.props.editStyle) => { // vectorContext.drawFeature only respects a style object and since it is common to have style functions and arrays in Openlayers we need to break the other formats down into objects - const { map, areaUOM, distanceUOM } = this.props + const { map, areaUOM, distanceUOM, translations } = this.props const { showMeasurements } = this.state - const measurementStyles = showMeasurements ? editStyle(feature, map, showMeasurements, { areaUOM, distanceUOM }) : editStyle // eslint-disable-line + const measurementStyles = showMeasurements ? editStyle(feature, map, showMeasurements, { areaUOM, distanceUOM }, translations) : editStyle // eslint-disable-line const styleType = Array.isArray(measurementStyles) ? 'array' : typeof editStyle try { @@ -90,7 +88,7 @@ class FeatureEditor extends Component { this._renderFeature( vectorContext, feature, - editStyle(feature, map, showMeasurements, { areaUOM, distanceUOM }) + editStyle(feature, map, showMeasurements, { areaUOM, distanceUOM }, translations) ) // Openlayers style functions return style objects or arrays of style objects so we can call functions recursively. break default: // style object @@ -134,7 +132,7 @@ class FeatureEditor extends Component { this._removePostComposeListener() interactions.forEach(i => map.removeInteraction(i)) - this.setState({ editingFeature: null, style: null }) + this.setState({ editingFeature: null, style: null, interactions: [] }) } catch (err) { console.warn(`Geokit encountered a problem while editing a feature: ${err.message}. \n`, err) // eslint-disable-line no-console } @@ -148,31 +146,24 @@ class FeatureEditor extends Component { const { onEditCancel, editFeature, addEditFeatureToContext } = this.props const { style } = this.state - this.setState({ canceled: true }, () => onEditCancel(editFeature, addEditFeatureToContext, style)) + this.setState( + { canceled: true, editingFeature: null }, + () => onEditCancel(editFeature, addEditFeatureToContext, style) + ) } finishEdit = () => { const { onEditFinish, addEditFeatureToContext, editFeature } = this.props const { editingFeature, style } = this.state - this.setState({ finished: true }, () => onEditFinish(editFeature, editingFeature, addEditFeatureToContext, style)) - } - - componentWillUnmount = () => { - const { canceled, finished } = this.state - - if (!canceled && !finished) console.warn(`Geokit FeatureEditor has been unmounted unexpectedly. This may lead undesirable behaviour in your application.`) // eslint-disable-line no-console - - return this._end() + this.setState( + { editingFeature: null }, + () => onEditFinish(editFeature, editingFeature, addEditFeatureToContext, style) + ) } - componentDidUpdate (prevProps) { + init () { const { editOpts, map, onEditBegin, editFeature } = this.props - const { interactions } = this.state - - if (prevProps.editFeature && !editFeature) return this._end() - - if (interactions.length === 2 || !editFeature) return const clonedFeature = editFeature.clone() // create a collection of clones of the features in props, this avoids modifying the existing features @@ -197,8 +188,6 @@ class FeatureEditor extends Component { anchor: olKitTurf(centroid, [clonedFeature.getGeometry()]).getGeometry().getCoordinates(), interactions: [modifyInteraction, translateInteraction], editingFeature: clonedFeature, - canceled: false, - finished: false, style }, () => { this._addPostComposeListener() @@ -208,6 +197,31 @@ class FeatureEditor extends Component { onEditBegin(clonedFeature) // callback function for IAs. FeatureEditor doesn't do anything to the original features so we tell the IA which features they passed in as props and what features we are editing. This should help if they want to add custom logic around these features. } + componentDidMount () { + if (!this.props.editFeature) return + + this.init() + } + + componentDidUpdate (prevProps) { + const { editFeature } = this.props + const { interactions } = this.state + + if (prevProps.editFeature && !editFeature) return this._end() + + if (interactions.length === 2 || !editFeature) return + + this.init() + } + + componentWillUnmount = () => { + const { editingFeature } = this.state + + if (editingFeature) console.warn(`Geokit FeatureEditor has been unmounted unexpectedly. This may lead undesirable behaviour in your application.`) // eslint-disable-line no-console + + return this._end() + } + rotate = (val) => { const { editingFeature, rotation, anchor } = this.state const geometry = editingFeature.getGeometry() @@ -302,13 +316,7 @@ FeatureEditor.defaultProps = { feature.setStyle(style || null) // restore the original feature's style addEditFeatureToContext(null) }, - editStyle: (feature, map, showMeasurements = false, { areaUOM, distanceUOM }) => { // eslint-disable-line - const translations = { - '_ol_kit.DrawToolbar.cancel': 'Cancel [ESC]', - '_ol_kit.DrawToolbar.finish': 'Finish', - '_ol_kit.DrawToolbar.showMeasurements': 'Show measurements' - } - + editStyle: (feature, map, showMeasurements = false, { areaUOM, distanceUOM }, translations) => { // eslint-disable-line return immediateEditStyle( { areaUOM, distanceUOM, showMeasurements, map, translations, language: navigator.language }, feature, diff --git a/src/Map/Map.js b/src/Map/Map.js index 0afba43c..029ded1b 100644 --- a/src/Map/Map.js +++ b/src/Map/Map.js @@ -13,7 +13,6 @@ import { } from './utils' import { StyledMap } from './styled' import { connectToContext } from 'Provider' -import { FeatureEditor } from 'FeatureEditor' import en from 'locales/en' import ugh from 'ugh' @@ -145,7 +144,6 @@ class Map extends React.Component { id={this.target} fullScreen={fullScreen} style={style}> - } From 1875db324977694ef31d0da4df64430738a4cb40 Mon Sep 17 00:00:00 2001 From: Patrick Moulden Date: Tue, 12 Oct 2021 09:54:58 -0400 Subject: [PATCH 09/13] clean up code and simplify --- app/demos/world/App.js | 25 +++++++++++++------ .../__snapshots__/DrawContainer.test.js.snap | 4 +-- src/FeatureEditor/FeatureEditor.js | 4 +-- src/FeatureEditor/styles.js | 11 ++++---- .../LayerPanelActionImport/utils.js | 1 - src/Map/Map.js | 1 - src/Popup/Popup.js | 6 +---- .../PopupActionEdit/PopupActionEdit.js | 8 +++--- 8 files changed, 30 insertions(+), 30 deletions(-) diff --git a/app/demos/world/App.js b/app/demos/world/App.js index 87f0973a..baa999a5 100644 --- a/app/demos/world/App.js +++ b/app/demos/world/App.js @@ -11,7 +11,8 @@ import { TabbedPanelPage, BasemapContainer, VectorLayer, - DrawContainer + DrawContainer, + FeatureEditor } from '@bayer/ol-kit' import { fromLonLat } from 'ol/proj' import olFeature from 'ol/Feature' @@ -20,26 +21,33 @@ import olSourceVector from 'ol/source/Vector' import Welcome from '../../Welcome' +const labs = new olFeature({ + feature_type: ['1904Labs HQ'], + title: '1904Labs HQ', + name: '1904Labs HQ', + geometry: new olGeomPoint(fromLonLat([-90.24618, 38.636069])) +}) + class App extends React.Component { onMapInit = async (map) => { // create a vector layer and add to the map const layer = new VectorLayer({ title: '1904Labs HQ', source: new olSourceVector({ - features: [new olFeature({ - feature_type: ['1904Labs HQ'], - title: '1904Labs HQ', - name: '1904Labs HQ', - geometry: new olGeomPoint(fromLonLat([-90.24618, 38.636069])) - })] + features: [labs] }) }) + labs.set('_ol_kit_parent', layer) + map.addLayer(layer) const dataLayer = await loadDataLayer(map, 'https://data.nasa.gov/api/geospatial/7zbq-j77a?method=export&format=KML') - dataLayer.getSource().getFeatures().forEach(f => f.set('title', f.get('name'))) + dataLayer.getSource().getFeatures().forEach(f => { + f.set('_ol_kit_parent', dataLayer) + f.set('title', f.get('name')) + }) window.map = map } @@ -47,6 +55,7 @@ class App extends React.Component { render () { return ( + diff --git a/src/Draw/__snapshots__/DrawContainer.test.js.snap b/src/Draw/__snapshots__/DrawContainer.test.js.snap index e35f4292..c2f7d7c0 100644 --- a/src/Draw/__snapshots__/DrawContainer.test.js.snap +++ b/src/Draw/__snapshots__/DrawContainer.test.js.snap @@ -63,7 +63,7 @@ exports[` should render a basic prebuilt DrawContainer componen    
  should render a basic prebuilt DrawContainer componen  
  geometry.rotate(-rotationDiff * (Math.PI / 45), anchor)) + this.setState({ rotation: val }, () => geometry.rotate(-rotationDiff * (Math.PI / 180), anchor)) } render () { @@ -254,7 +254,7 @@ class FeatureEditor extends Component { + } label={translations['_ol_kit.edit.rotate']} /> diff --git a/src/FeatureEditor/styles.js b/src/FeatureEditor/styles.js index 8c7df459..5a1937a0 100644 --- a/src/FeatureEditor/styles.js +++ b/src/FeatureEditor/styles.js @@ -215,13 +215,12 @@ function getVertices (args) { /** * Style an ol/Feature with orange circle vertices, a blue outline, an area label, and a perimeter length label. Can be used with individual features as a style function or call it directly to get a style object for use with `vectorContext.drawFeature` * @function - * @since 6.3.0 * @param {object} opts - The config object * @param {ol/Feature} feature - The feature you want to style * @param {number} resolution - the resolution of the map * @returns {object} The style object for the passed feature */ -export function immediateEditStyle (...args) { // eslint-disable-line +export function immediateEditStyle (...args) { const { feature, resolution, opts = {} } = resolveStyleFunctionArgs(args) const fill = new olStyleFill({ color: 'rgba(0, 0, 255, 0.2)' @@ -251,15 +250,15 @@ export function immediateEditStyle (...args) { // eslint-disable-line const geometry = opts.geometry || feature.clone().getGeometry() const areaLabelsFlag = feature.get('_ol_kit_area_labels') const distanceLabelsFlag = feature.get('_ol_kit_distance_labels') - const isLegacyMeasure = feature.get('_vmf_type') === '_vmf_measurement' && !(distanceLabelsFlag || areaLabelsFlag) // ignore legacy measure flag if either of the new flags are used. Legacy features won't have the new flags and new features will only have the legacy flag if it also has one of the new flags (we still add the legacy flag to avoid a breaking change). const needsVertexLabels = feature.get('_ol_kit_coordinate_labels') !== undefined const needsCentroidLabels = feature.get('_ol_kit_needs_centroid_label') !== undefined - const needsAreaLabels = opts.showMeasurements ? areaLabelsFlag : isLegacyMeasure - const needsDistanceLabels = opts.showMeasurements ? (distanceLabelsFlag) : isLegacyMeasure + const needsAreaLabels = opts.showMeasurements && areaLabelsFlag + const needsDistanceLabels = opts.showMeasurements && distanceLabelsFlag const isNotCircle = feature.get('_ol_kit_draw_mode') !== 'circle' const vertexLabels = (needsVertexLabels && opts.showMeasurements && isNotCircle) ? coordinateLabels(getVertices(feature), resolution, opts) : [] // eslint-disable-line + const type = geometry.getType() - switch (geometry.getType()) { + switch (type) { case 'Point': return [new olStyleStyle({ image diff --git a/src/LayerPanel/LayerPanelActionImport/utils.js b/src/LayerPanel/LayerPanelActionImport/utils.js index 5fc80140..7ab03acc 100644 --- a/src/LayerPanel/LayerPanelActionImport/utils.js +++ b/src/LayerPanel/LayerPanelActionImport/utils.js @@ -26,7 +26,6 @@ export function arrRegexIndexOf (arr, re) { /** * Converts the given file into a layer * @function convertFileToLayer - * @since 6.5.0 * @param {Blob} [file] - the file to be converted. Accepts, 'kmz', 'kml', 'geojson', 'wkt', 'csv', 'zip', and 'json' file types. * @param {olMap} [map] - the openlayers map */ diff --git a/src/Map/Map.js b/src/Map/Map.js index 029ded1b..180c81ce 100644 --- a/src/Map/Map.js +++ b/src/Map/Map.js @@ -158,7 +158,6 @@ class Map extends React.Component { Map.defaultProps = { addMapToContext: () => {}, - onEdit: () => {}, fullScreen: false, logoPosition: 'right', isMultiMap: false, diff --git a/src/Popup/Popup.js b/src/Popup/Popup.js index f2453b95..d8fcdfba 100644 --- a/src/Popup/Popup.js +++ b/src/Popup/Popup.js @@ -131,15 +131,11 @@ class Popup extends Component { hidePopup = () => { const { onMapClick } = this.props - const { editState, features } = this.state // stop tracking movement when popup show is set to false this.movementListener && removeMovementListener(this.movementListener) - this.setState( - { ...this.defaultState, editState, features: editState?.active ? features : [] }, - () => onMapClick(this.state) - ) + this.setState({ ...this.defaultState }, () => onMapClick(this.state)) } onDragEnd = e => { diff --git a/src/Popup/PopupActions/PopupActionEdit/PopupActionEdit.js b/src/Popup/PopupActions/PopupActionEdit/PopupActionEdit.js index 7f00dca7..3f625779 100644 --- a/src/Popup/PopupActions/PopupActionEdit/PopupActionEdit.js +++ b/src/Popup/PopupActions/PopupActionEdit/PopupActionEdit.js @@ -21,10 +21,10 @@ class PopupActionEdit extends Component { } onClick = () => { - const { addEditFeatureToContext, feature } = this.props + const { addEditFeatureToContext, feature, onEdit } = this.props addEditFeatureToContext(feature) - this.forceUpdate() + onEdit() } render () { @@ -42,9 +42,7 @@ PopupActionEdit.propTypes = { translations: PropTypes.object, feature: PropTypes.object, map: PropTypes.object, - onEditFinish: PropTypes.func, - onEditCancel: PropTypes.func, - onEditBegin: PropTypes.func, + onEdit: PropTypes.func, showPopup: PropTypes.func, areaUOM: PropTypes.string, distanceUOM: PropTypes.string, From 51d22b5c05482ae7008940b5536c35442cd17a34 Mon Sep 17 00:00:00 2001 From: Patrick Moulden Date: Tue, 19 Oct 2021 11:59:44 -0400 Subject: [PATCH 10/13] pass feature through callback --- src/Popup/PopupActions/PopupActionEdit/PopupActionEdit.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Popup/PopupActions/PopupActionEdit/PopupActionEdit.js b/src/Popup/PopupActions/PopupActionEdit/PopupActionEdit.js index 3f625779..a40a77b0 100644 --- a/src/Popup/PopupActions/PopupActionEdit/PopupActionEdit.js +++ b/src/Popup/PopupActions/PopupActionEdit/PopupActionEdit.js @@ -24,7 +24,7 @@ class PopupActionEdit extends Component { const { addEditFeatureToContext, feature, onEdit } = this.props addEditFeatureToContext(feature) - onEdit() + onEdit(feature) } render () { From 490d55d5d5b4471af219d5c0a5002d4831ca2cb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jake=20St=C3=A4zrad?= Date: Wed, 27 Oct 2021 14:09:34 -0500 Subject: [PATCH 11/13] bump to 1.16.0 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index fbda8f9f..1c0ab1a4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@bayer/ol-kit", - "version": "1.15.0", + "version": "1.16.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 108ba037..d3cea055 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@bayer/ol-kit", - "version": "1.15.0", + "version": "1.16.0", "license": "BSD", "description": "Mapping components & utils built with openlayers + react", "keywords": [ From 7e677abd2c079350b43531616176a3462dbb6fc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jake=20St=C3=A4zrad?= Date: Wed, 27 Oct 2021 14:14:43 -0500 Subject: [PATCH 12/13] build docs --- docs/BasemapBingMaps.html | 2 +- docs/BasemapBlankWhite.html | 2 +- docs/BasemapContainer.html | 2 +- docs/BasemapManager.html | 2 +- docs/BasemapOpenStreetMap.html | 2 +- docs/BasemapStamenTerrain.html | 2 +- docs/BasemapStamenTonerDark.html | 2 +- docs/BasemapStamenTonerLite.html | 2 +- docs/Basemaps_BasemapContainer.js.html | 2 +- docs/Basemaps_BasemapManager.js.html | 2 +- docs/Basemaps_BingMaps.js.html | 2 +- docs/Basemaps_BlankWhite.js.html | 2 +- docs/Basemaps_OpenStreetMap.js.html | 2 +- docs/Basemaps_StamenTerrain.js.html | 2 +- docs/Basemaps_StamenTonerDark.js.html | 2 +- docs/Basemaps_StamenTonerLite.js.html | 2 +- docs/Basemaps_utils.js.html | 2 +- docs/Compass.html | 2 +- docs/ContextMenu.html | 2 +- docs/ContextMenuCoords.html | 2 +- docs/ContextMenuListItem.html | 2 +- docs/ContextMenu_ContextMenu.js.html | 2 +- docs/ContextMenu_ContextMenuCoords.js.html | 2 +- docs/ContextMenu_ContextMenuListItem.js.html | 2 +- docs/ControlGroup.html | 2 +- docs/ControlGroupButton.html | 2 +- docs/Controls.html | 2 +- docs/Controls_Compass.js.html | 2 +- docs/Controls_ControlGroup.js.html | 2 +- docs/Controls_ControlGroupButton.js.html | 2 +- docs/Controls_Controls.js.html | 2 +- docs/Controls_CurrentLocation.js.html | 2 +- docs/Controls_ScaleLine.js.html | 2 +- docs/Controls_ZoomControls.js.html | 2 +- docs/Controls_ZoomIn.js.html | 2 +- docs/Controls_ZoomOut.js.html | 2 +- docs/CurrentLocation.html | 2 +- docs/DataLayers_utils.js.html | 2 +- docs/Draw.html | 2 +- docs/DrawBox.html | 2 +- docs/DrawCircle.html | 2 +- docs/DrawContainer.html | 2 +- docs/DrawFreehand.html | 2 +- docs/DrawLine.html | 2 +- docs/DrawPin.html | 2 +- docs/DrawPoint.html | 2 +- docs/DrawPolygon.html | 2 +- docs/Draw_Box.js.html | 2 +- docs/Draw_Circle.js.html | 2 +- docs/Draw_Draw.js.html | 2 +- docs/Draw_DrawContainer.js.html | 2 +- docs/Draw_Freehand.js.html | 2 +- docs/Draw_Line.js.html | 2 +- docs/Draw_Pin.js.html | 2 +- docs/Draw_Point.js.html | 2 +- docs/Draw_Polygon.js.html | 2 +- docs/Draw_utils.js.html | 2 +- docs/FeatureEditor.html | 495 ++++++++++++++++++ docs/FeatureEditor_FeatureEditor.js.html | 446 ++++++++++++++++ docs/FeatureEditor_styles.js.html | 450 ++++++++++++++++ docs/GoogleDirections.html | 2 +- .../GoogleDirections_GoogleDirections.js.html | 2 +- docs/GooglePlacesSearch.html | 2 +- ...glePlacesSearch_GooglePlacesSearch.js.html | 2 +- docs/HeatmapControls.html | 2 +- docs/Heatmap_HeatmapControls.js.html | 2 +- docs/ImageExif_ImageExif.js.html | 2 +- docs/LayerPanel.html | 2 +- docs/LayerPanelActionDuplicate.html | 2 +- docs/LayerPanelActionExport.html | 2 +- docs/LayerPanelActionExtent.html | 2 +- docs/LayerPanelActionHeatmap.html | 2 +- docs/LayerPanelActionImport.html | 2 +- docs/LayerPanelActionMerge.html | 2 +- docs/LayerPanelActionOpacity.html | 2 +- docs/LayerPanelActionRemove.html | 2 +- docs/LayerPanelActions.html | 2 +- docs/LayerPanelBase.html | 2 +- docs/LayerPanelCheckbox.html | 2 +- docs/LayerPanelContent.html | 2 +- docs/LayerPanelHeader.html | 2 +- docs/LayerPanelLayersPage.html | 2 +- docs/LayerPanelList.html | 2 +- docs/LayerPanelListItem.html | 2 +- docs/LayerPanelMenu.html | 2 +- docs/LayerPanelPage.html | 2 +- docs/LayerPanel_LayerPanel.js.html | 2 +- ...el_LayerPanelActionDuplicate_index.js.html | 2 +- ...Panel_LayerPanelActionExport_index.js.html | 2 +- ...Panel_LayerPanelActionExport_utils.js.html | 2 +- ...Panel_LayerPanelActionExtent_index.js.html | 2 +- ...anel_LayerPanelActionHeatmap_index.js.html | 2 +- ...anel_LayerPanelActionHeatmap_utils.js.html | 2 +- ...Panel_LayerPanelActionImport_index.js.html | 2 +- ...Panel_LayerPanelActionImport_utils.js.html | 3 +- ...rPanel_LayerPanelActionMerge_index.js.html | 2 +- ...rPanel_LayerPanelActionMerge_utils.js.html | 2 +- ...anel_LayerPanelActionOpacity_index.js.html | 2 +- ...Panel_LayerPanelActionRemove_index.js.html | 2 +- ...LayerPanel_LayerPanelActions_index.js.html | 2 +- docs/LayerPanel_LayerPanelBase_index.js.html | 2 +- ...ayerPanel_LayerPanelCheckbox_index.js.html | 2 +- ...LayerPanel_LayerPanelContent_index.js.html | 2 +- .../LayerPanel_LayerPanelHeader_index.js.html | 2 +- ...erPanel_LayerPanelLayersPage_index.js.html | 2 +- ...ayerPanel_LayerPanelListItem_index.js.html | 2 +- docs/LayerPanel_LayerPanelList_index.js.html | 2 +- docs/LayerPanel_LayerPanelMenu_index.js.html | 2 +- docs/LayerPanel_LayerPanelPage_index.js.html | 2 +- docs/LayerStyler.html | 2 +- docs/LayerStyler_LayerStyler.js.html | 2 +- docs/LayerStyler_StyleManager_index.js.html | 2 +- ...ayerStyler__AttributesFilter_index.js.html | 2 +- docs/LayerStyler__ColorPicker_index.js.html | 2 +- docs/LayerStyler__LabelStyler_index.js.html | 2 +- ...tyleGroup__GenericSymbolizer_index.js.html | 2 +- ...ler__LayerStyler__StyleGroup_index.js.html | 2 +- docs/LayerStyler__LayerStyler_index.js.html | 2 +- docs/LayerStyler__Popover_index.js.html | 2 +- docs/LayerStyler__SelectTabs_index.js.html | 2 +- docs/LayerStyler__Selector_index.js.html | 2 +- docs/Map.html | 2 +- docs/Map_Map.js.html | 2 +- docs/Map_utils.js.html | 2 +- docs/Measure_utils.js.html | 2 +- docs/MultiMapManager.html | 2 +- docs/MultiMapManager_MultiMapManager.js.html | 2 +- docs/MultiMapManager_SafeParent.js.html | 2 +- docs/MultiMapManager_utils.js.html | 2 +- docs/Pdf_utils.js.html | 2 +- docs/Popup.html | 2 +- docs/PopupActionCopyWkt.html | 2 +- docs/PopupActionGoogleMaps.html | 2 +- docs/PopupActionGroup.html | 8 +- docs/PopupActionItem.html | 8 +- docs/PopupActionLink.html | 8 +- docs/PopupBase.html | 2 +- docs/PopupDataList.html | 8 +- docs/PopupDefaultInsert.html | 17 +- docs/PopupDefaultPage.html | 8 +- docs/PopupPageLayout.html | 8 +- docs/PopupPageLayoutChild.html | 8 +- docs/PopupTabs.html | 8 +- docs/Popup_Popup.js.html | 5 +- ...upActionCopyWkt_PopupActionCopyWkt.js.html | 2 +- ...upActions_PopupActionCopyWkt_utils.js.html | 2 +- ...ions_PopupActionCut_PopupActionCut.js.html | 2 +- ...tionDuplicate_PopupActionDuplicate.js.html | 2 +- ...onGoogleMaps_PopupActionGoogleMaps.js.html | 2 +- ...opupActionRemove_PopupActionRemove.js.html | 2 +- ...omToExtent_PopupActionZoomToExtent.js.html | 2 +- docs/Popup_PopupBase.js.html | 2 +- ...PopupInsert_PopupActionGroup_index.js.html | 2 +- ..._PopupInsert_PopupActionItem_index.js.html | 2 +- ..._PopupInsert_PopupActionLink_index.js.html | 2 +- ...up_PopupInsert_PopupDataList_index.js.html | 2 +- ...pup_PopupInsert_PopupDefaultInsert.js.html | 14 +- ...PopupInsert_PopupDefaultPage_index.js.html | 2 +- ...pInsert_PopupPageLayoutChild_index.js.html | 2 +- ..._PopupInsert_PopupPageLayout_index.js.html | 2 +- .../Popup_PopupInsert_PopupTabs_index.js.html | 2 +- ..._PopupInsert__LoadingSpinner_index.js.html | 2 +- docs/Popup_utils.js.html | 2 +- docs/ProjectMenu.html | 2 +- docs/Project_Project.js.html | 2 +- docs/Project_utils.js.html | 2 +- docs/Provider.html | 21 +- docs/Provider_Provider.js.html | 12 +- docs/Provider_utils.js.html | 2 +- docs/TabbedPanel.html | 2 +- docs/TabbedPanel_TabbedPanel.js.html | 2 +- docs/TimeSlider.html | 2 +- docs/TimeSliderBase.html | 2 +- docs/TimeSlider_TimeSlider.js.html | 2 +- docs/TimeSlider_TimeSliderBase.js.html | 2 +- docs/Toolbar_Toolbar.js.html | 2 +- docs/ZoomControls.html | 2 +- docs/ZoomIn.html | 2 +- docs/ZoomOut.html | 2 +- docs/classes_VectorLayer.js.html | 2 +- docs/classes_VectorTileLayer.js.html | 2 +- docs/docs.html | 2 +- docs/entry.js | 187 +++---- docs/global.html | 451 +++++++++++++++- docs/tutorial-Getting Started.html | 2 +- docs/tutorial-LayerPanel_.html | 2 +- docs/tutorial-Popup_.html | 2 +- docs/tutorial-TimeSlider_.html | 2 +- docs/tutorial-connectToContext.html | 2 +- docs/tutorial-contextProps.html | 2 +- docs/tutorial-i18n Support.html | 2 +- src/FeatureEditor/FeatureEditor.js | 6 + 192 files changed, 2195 insertions(+), 320 deletions(-) create mode 100644 docs/FeatureEditor.html create mode 100644 docs/FeatureEditor_FeatureEditor.js.html create mode 100644 docs/FeatureEditor_styles.js.html diff --git a/docs/BasemapBingMaps.html b/docs/BasemapBingMaps.html index 31f191d1..c3b738d0 100644 --- a/docs/BasemapBingMaps.html +++ b/docs/BasemapBingMaps.html @@ -65,7 +65,7 @@ diff --git a/docs/BasemapBlankWhite.html b/docs/BasemapBlankWhite.html index ef979599..5dca36b3 100644 --- a/docs/BasemapBlankWhite.html +++ b/docs/BasemapBlankWhite.html @@ -65,7 +65,7 @@ diff --git a/docs/BasemapContainer.html b/docs/BasemapContainer.html index 9fb02905..9f1089d2 100644 --- a/docs/BasemapContainer.html +++ b/docs/BasemapContainer.html @@ -65,7 +65,7 @@ diff --git a/docs/BasemapManager.html b/docs/BasemapManager.html index 6a0298d0..5baa6ebc 100644 --- a/docs/BasemapManager.html +++ b/docs/BasemapManager.html @@ -65,7 +65,7 @@ diff --git a/docs/BasemapOpenStreetMap.html b/docs/BasemapOpenStreetMap.html index d3c8b12f..b7e7356f 100644 --- a/docs/BasemapOpenStreetMap.html +++ b/docs/BasemapOpenStreetMap.html @@ -65,7 +65,7 @@ diff --git a/docs/BasemapStamenTerrain.html b/docs/BasemapStamenTerrain.html index 7b9b3c18..cd80fbbf 100644 --- a/docs/BasemapStamenTerrain.html +++ b/docs/BasemapStamenTerrain.html @@ -65,7 +65,7 @@ diff --git a/docs/BasemapStamenTonerDark.html b/docs/BasemapStamenTonerDark.html index 3904e4ac..a4ae1b23 100644 --- a/docs/BasemapStamenTonerDark.html +++ b/docs/BasemapStamenTonerDark.html @@ -65,7 +65,7 @@ diff --git a/docs/BasemapStamenTonerLite.html b/docs/BasemapStamenTonerLite.html index c40d0b20..e2ef8d58 100644 --- a/docs/BasemapStamenTonerLite.html +++ b/docs/BasemapStamenTonerLite.html @@ -65,7 +65,7 @@ diff --git a/docs/Basemaps_BasemapContainer.js.html b/docs/Basemaps_BasemapContainer.js.html index b1ef5ea3..31952819 100644 --- a/docs/Basemaps_BasemapContainer.js.html +++ b/docs/Basemaps_BasemapContainer.js.html @@ -67,7 +67,7 @@ diff --git a/docs/Basemaps_BasemapManager.js.html b/docs/Basemaps_BasemapManager.js.html index c5640a59..8b1a9613 100644 --- a/docs/Basemaps_BasemapManager.js.html +++ b/docs/Basemaps_BasemapManager.js.html @@ -67,7 +67,7 @@ diff --git a/docs/Basemaps_BingMaps.js.html b/docs/Basemaps_BingMaps.js.html index dcbc8222..21d4d93d 100644 --- a/docs/Basemaps_BingMaps.js.html +++ b/docs/Basemaps_BingMaps.js.html @@ -67,7 +67,7 @@ diff --git a/docs/Basemaps_BlankWhite.js.html b/docs/Basemaps_BlankWhite.js.html index d8a11bb9..b67fb6ca 100644 --- a/docs/Basemaps_BlankWhite.js.html +++ b/docs/Basemaps_BlankWhite.js.html @@ -67,7 +67,7 @@ diff --git a/docs/Basemaps_OpenStreetMap.js.html b/docs/Basemaps_OpenStreetMap.js.html index 56475cf7..a11354a5 100644 --- a/docs/Basemaps_OpenStreetMap.js.html +++ b/docs/Basemaps_OpenStreetMap.js.html @@ -67,7 +67,7 @@ diff --git a/docs/Basemaps_StamenTerrain.js.html b/docs/Basemaps_StamenTerrain.js.html index f8857823..d7825303 100644 --- a/docs/Basemaps_StamenTerrain.js.html +++ b/docs/Basemaps_StamenTerrain.js.html @@ -67,7 +67,7 @@ diff --git a/docs/Basemaps_StamenTonerDark.js.html b/docs/Basemaps_StamenTonerDark.js.html index d2cbcd90..9d726fc2 100644 --- a/docs/Basemaps_StamenTonerDark.js.html +++ b/docs/Basemaps_StamenTonerDark.js.html @@ -67,7 +67,7 @@ diff --git a/docs/Basemaps_StamenTonerLite.js.html b/docs/Basemaps_StamenTonerLite.js.html index ceb191fe..3e792c7d 100644 --- a/docs/Basemaps_StamenTonerLite.js.html +++ b/docs/Basemaps_StamenTonerLite.js.html @@ -67,7 +67,7 @@ diff --git a/docs/Basemaps_utils.js.html b/docs/Basemaps_utils.js.html index 993033e2..17cc8655 100644 --- a/docs/Basemaps_utils.js.html +++ b/docs/Basemaps_utils.js.html @@ -67,7 +67,7 @@ diff --git a/docs/Compass.html b/docs/Compass.html index a522e7ba..d5bc157f 100644 --- a/docs/Compass.html +++ b/docs/Compass.html @@ -65,7 +65,7 @@ diff --git a/docs/ContextMenu.html b/docs/ContextMenu.html index 22bb7086..8fa8126c 100644 --- a/docs/ContextMenu.html +++ b/docs/ContextMenu.html @@ -65,7 +65,7 @@ diff --git a/docs/ContextMenuCoords.html b/docs/ContextMenuCoords.html index fa29634e..07ac6765 100644 --- a/docs/ContextMenuCoords.html +++ b/docs/ContextMenuCoords.html @@ -65,7 +65,7 @@ diff --git a/docs/ContextMenuListItem.html b/docs/ContextMenuListItem.html index b2af912b..00a1a9fc 100644 --- a/docs/ContextMenuListItem.html +++ b/docs/ContextMenuListItem.html @@ -65,7 +65,7 @@ diff --git a/docs/ContextMenu_ContextMenu.js.html b/docs/ContextMenu_ContextMenu.js.html index 50405345..18ff7ece 100644 --- a/docs/ContextMenu_ContextMenu.js.html +++ b/docs/ContextMenu_ContextMenu.js.html @@ -67,7 +67,7 @@ diff --git a/docs/ContextMenu_ContextMenuCoords.js.html b/docs/ContextMenu_ContextMenuCoords.js.html index 8400c30d..fa427a66 100644 --- a/docs/ContextMenu_ContextMenuCoords.js.html +++ b/docs/ContextMenu_ContextMenuCoords.js.html @@ -67,7 +67,7 @@ diff --git a/docs/ContextMenu_ContextMenuListItem.js.html b/docs/ContextMenu_ContextMenuListItem.js.html index b6cf77da..552cebe0 100644 --- a/docs/ContextMenu_ContextMenuListItem.js.html +++ b/docs/ContextMenu_ContextMenuListItem.js.html @@ -67,7 +67,7 @@ diff --git a/docs/ControlGroup.html b/docs/ControlGroup.html index 51640d3a..1dad08b0 100644 --- a/docs/ControlGroup.html +++ b/docs/ControlGroup.html @@ -65,7 +65,7 @@ diff --git a/docs/ControlGroupButton.html b/docs/ControlGroupButton.html index 44309342..41fb2e30 100644 --- a/docs/ControlGroupButton.html +++ b/docs/ControlGroupButton.html @@ -65,7 +65,7 @@ diff --git a/docs/Controls.html b/docs/Controls.html index 94a46ce6..5231d0f6 100644 --- a/docs/Controls.html +++ b/docs/Controls.html @@ -65,7 +65,7 @@ diff --git a/docs/Controls_Compass.js.html b/docs/Controls_Compass.js.html index c7dc41a3..48038519 100644 --- a/docs/Controls_Compass.js.html +++ b/docs/Controls_Compass.js.html @@ -67,7 +67,7 @@ diff --git a/docs/Controls_ControlGroup.js.html b/docs/Controls_ControlGroup.js.html index a42e8545..dd32fcce 100644 --- a/docs/Controls_ControlGroup.js.html +++ b/docs/Controls_ControlGroup.js.html @@ -67,7 +67,7 @@ diff --git a/docs/Controls_ControlGroupButton.js.html b/docs/Controls_ControlGroupButton.js.html index c30f8e3f..b2d480b2 100644 --- a/docs/Controls_ControlGroupButton.js.html +++ b/docs/Controls_ControlGroupButton.js.html @@ -67,7 +67,7 @@ diff --git a/docs/Controls_Controls.js.html b/docs/Controls_Controls.js.html index ac7e9ef5..8fc50bba 100644 --- a/docs/Controls_Controls.js.html +++ b/docs/Controls_Controls.js.html @@ -67,7 +67,7 @@ diff --git a/docs/Controls_CurrentLocation.js.html b/docs/Controls_CurrentLocation.js.html index ab38d6ad..c60ee85a 100644 --- a/docs/Controls_CurrentLocation.js.html +++ b/docs/Controls_CurrentLocation.js.html @@ -67,7 +67,7 @@ diff --git a/docs/Controls_ScaleLine.js.html b/docs/Controls_ScaleLine.js.html index a3144c43..937d066c 100644 --- a/docs/Controls_ScaleLine.js.html +++ b/docs/Controls_ScaleLine.js.html @@ -67,7 +67,7 @@ diff --git a/docs/Controls_ZoomControls.js.html b/docs/Controls_ZoomControls.js.html index d79a7215..21602954 100644 --- a/docs/Controls_ZoomControls.js.html +++ b/docs/Controls_ZoomControls.js.html @@ -67,7 +67,7 @@ diff --git a/docs/Controls_ZoomIn.js.html b/docs/Controls_ZoomIn.js.html index fed6cd68..213cb308 100644 --- a/docs/Controls_ZoomIn.js.html +++ b/docs/Controls_ZoomIn.js.html @@ -67,7 +67,7 @@ diff --git a/docs/Controls_ZoomOut.js.html b/docs/Controls_ZoomOut.js.html index 3a9fcd50..7722c049 100644 --- a/docs/Controls_ZoomOut.js.html +++ b/docs/Controls_ZoomOut.js.html @@ -67,7 +67,7 @@ diff --git a/docs/CurrentLocation.html b/docs/CurrentLocation.html index d2cfc407..23ac1a85 100644 --- a/docs/CurrentLocation.html +++ b/docs/CurrentLocation.html @@ -65,7 +65,7 @@ diff --git a/docs/DataLayers_utils.js.html b/docs/DataLayers_utils.js.html index 124a84b5..d54cd842 100644 --- a/docs/DataLayers_utils.js.html +++ b/docs/DataLayers_utils.js.html @@ -67,7 +67,7 @@ diff --git a/docs/Draw.html b/docs/Draw.html index 9b54db5f..6c719465 100644 --- a/docs/Draw.html +++ b/docs/Draw.html @@ -65,7 +65,7 @@ diff --git a/docs/DrawBox.html b/docs/DrawBox.html index 8fbacbf6..bbfa8fd0 100644 --- a/docs/DrawBox.html +++ b/docs/DrawBox.html @@ -65,7 +65,7 @@ diff --git a/docs/DrawCircle.html b/docs/DrawCircle.html index 82e4ccf9..532b37c3 100644 --- a/docs/DrawCircle.html +++ b/docs/DrawCircle.html @@ -65,7 +65,7 @@ diff --git a/docs/DrawContainer.html b/docs/DrawContainer.html index 0051ebf7..879db84f 100644 --- a/docs/DrawContainer.html +++ b/docs/DrawContainer.html @@ -65,7 +65,7 @@ diff --git a/docs/DrawFreehand.html b/docs/DrawFreehand.html index 17898443..cee37354 100644 --- a/docs/DrawFreehand.html +++ b/docs/DrawFreehand.html @@ -65,7 +65,7 @@ diff --git a/docs/DrawLine.html b/docs/DrawLine.html index 803af447..5bf69a75 100644 --- a/docs/DrawLine.html +++ b/docs/DrawLine.html @@ -65,7 +65,7 @@ diff --git a/docs/DrawPin.html b/docs/DrawPin.html index b549e912..3baeb466 100644 --- a/docs/DrawPin.html +++ b/docs/DrawPin.html @@ -65,7 +65,7 @@ diff --git a/docs/DrawPoint.html b/docs/DrawPoint.html index bb4c371c..4adc3bcd 100644 --- a/docs/DrawPoint.html +++ b/docs/DrawPoint.html @@ -65,7 +65,7 @@ diff --git a/docs/DrawPolygon.html b/docs/DrawPolygon.html index 61b49248..c9021b32 100644 --- a/docs/DrawPolygon.html +++ b/docs/DrawPolygon.html @@ -65,7 +65,7 @@ diff --git a/docs/Draw_Box.js.html b/docs/Draw_Box.js.html index 483b7edd..f4c9fbcc 100644 --- a/docs/Draw_Box.js.html +++ b/docs/Draw_Box.js.html @@ -67,7 +67,7 @@ diff --git a/docs/Draw_Circle.js.html b/docs/Draw_Circle.js.html index e832a534..7643ce5c 100644 --- a/docs/Draw_Circle.js.html +++ b/docs/Draw_Circle.js.html @@ -67,7 +67,7 @@ diff --git a/docs/Draw_Draw.js.html b/docs/Draw_Draw.js.html index be35f2a9..e2c4ff18 100644 --- a/docs/Draw_Draw.js.html +++ b/docs/Draw_Draw.js.html @@ -67,7 +67,7 @@ diff --git a/docs/Draw_DrawContainer.js.html b/docs/Draw_DrawContainer.js.html index 883b618c..fab59fae 100644 --- a/docs/Draw_DrawContainer.js.html +++ b/docs/Draw_DrawContainer.js.html @@ -67,7 +67,7 @@ diff --git a/docs/Draw_Freehand.js.html b/docs/Draw_Freehand.js.html index 2d73c08c..5159925d 100644 --- a/docs/Draw_Freehand.js.html +++ b/docs/Draw_Freehand.js.html @@ -67,7 +67,7 @@ diff --git a/docs/Draw_Line.js.html b/docs/Draw_Line.js.html index 5cad416d..ecf204de 100644 --- a/docs/Draw_Line.js.html +++ b/docs/Draw_Line.js.html @@ -67,7 +67,7 @@ diff --git a/docs/Draw_Pin.js.html b/docs/Draw_Pin.js.html index 05d8a470..734257d8 100644 --- a/docs/Draw_Pin.js.html +++ b/docs/Draw_Pin.js.html @@ -67,7 +67,7 @@ diff --git a/docs/Draw_Point.js.html b/docs/Draw_Point.js.html index df6c75a6..2d864d1c 100644 --- a/docs/Draw_Point.js.html +++ b/docs/Draw_Point.js.html @@ -67,7 +67,7 @@ diff --git a/docs/Draw_Polygon.js.html b/docs/Draw_Polygon.js.html index fb1a1817..1d2f830b 100644 --- a/docs/Draw_Polygon.js.html +++ b/docs/Draw_Polygon.js.html @@ -67,7 +67,7 @@ diff --git a/docs/Draw_utils.js.html b/docs/Draw_utils.js.html index a7e6f56a..8cfa57ec 100644 --- a/docs/Draw_utils.js.html +++ b/docs/Draw_utils.js.html @@ -67,7 +67,7 @@ diff --git a/docs/FeatureEditor.html b/docs/FeatureEditor.html new file mode 100644 index 00000000..55862a38 --- /dev/null +++ b/docs/FeatureEditor.html @@ -0,0 +1,495 @@ + + + + + ol-kit | FeatureEditor + + + + + + + + + + + + + + + + +
+
+ +
+
+
+
+ +
+
+ + + + + +
+ +
+ +

+ <FeatureEditor /> +

+ +
A component to edit geometries
+ + +
+ +
+
+ + +
+

Constructor

+
+
+ + + +

+ # + + + + <FeatureEditor editOpts editFeature addEditFeatureToContext map onEditBegin onEditFinish onEditCancel editStyle areaUOM distanceUOM translations /> + + +

+ + + + + + + + + + + + +
PropTypes:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeRequiredDescriptionDefault
editOpts + exact + No{}
editFeature + object + No
addEditFeatureToContext + func + No
map + object + No
onEditBegin + func + No(feature) => { + feature.setStyle(new olStyleStyle({})) +}
onEditFinish + func + No(feature, updatedFeature, addEditFeatureToContext, style) => { + const geom = updatedFeature.getGeometry() + + if (!feature) return + + feature.setGeometry(geom) + + feature.setStyle(style || null) // restore the original feature's style + addEditFeatureToContext(null) +}
onEditCancel + func + No(feature, addEditFeatureToContext, style) => { + feature.setStyle(style || null) // restore the original feature's style + addEditFeatureToContext(null) +}
editStyle + union + No(feature, map, showMeasurements = false, { areaUOM, distanceUOM }, translations) => { // eslint-disable-line + return immediateEditStyle( + { areaUOM, distanceUOM, showMeasurements, map, translations, language: navigator.language }, + feature, + map.getView().getResolution() + ) +}
areaUOM + string + No
distanceUOM + string + No
translations + object + No
+ + + + + + + + +
+ + + + +
Since:
+
  • 1.16.0
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ View Source + + FeatureEditor/FeatureEditor.js, line 60 + +

+ +
+ + + + + + + + + + + + + + + + + + + + + +
+
+
+ + +
+ + + + + + + + + + + + + + + + +
+

Members

+
+ +
+ +

+ # + + + _renderFeature + + +

+ + + + +
+ In the past we've used temporary layers added to the map to avoid modifying the original features directly. + However, this leads to a lot of cleanup since those layers need to be added/removed from both the map and state. + That is fine as long as everything goes smoothly but if there is additional logic listening to the map for changes to it's features + or layers than we can get left in a broken state that requires a reload. Using vectorContext.drawFeature instead of + a layer added to the map alleviates at least some of this risk. +
+ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ View Source + + FeatureEditor/FeatureEditor.js, line 77 + +

+ +
+ + + + + +
+ +
+
+ + + + + + + + + + +
+ +
+ + + + +
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/docs/FeatureEditor_FeatureEditor.js.html b/docs/FeatureEditor_FeatureEditor.js.html new file mode 100644 index 00000000..53c0389d --- /dev/null +++ b/docs/FeatureEditor_FeatureEditor.js.html @@ -0,0 +1,446 @@ + + + + + + + ol-kit | FeatureEditor/FeatureEditor.js + + + + + + + + + + + + + + + + +
+
+ +
+
+
+
+ +
+
+ + + + + +
+
+
import React, { Component } from 'react'
+import PropTypes from 'prop-types'
+import Translate from 'classes/Translate'
+import { immediateEditStyle } from './styles'
+import { getVectorContext } from 'ol/render'
+import { Toolbar } from 'Toolbar'
+import { Knob } from 'react-rotary-knob'
+import { connectToContext } from 'Provider'
+import { Card, Grid, CardActions, Button, FormControlLabel } from '@material-ui/core'
+import { withStyles } from '@material-ui/core/styles'
+import centroid from '@turf/centroid'
+import { olKitTurf } from './utils'
+
+import olInteractionModify from 'ol/interaction/Modify'
+import olCollection from 'ol/Collection'
+import olStyleStyle from 'ol/style/Style'
+
+const ButtonCardActions = withStyles(() => ({
+  root: {
+    padding: '4px 4px 3px 4px'
+  }
+}))(CardActions)
+
+const LeftCard = withStyles(() => ({
+  root: {
+    borderTopLeftRadius: '4px',
+    borderBottomLeftRadius: '4px',
+    borderBottomRightRadius: '0px',
+    borderTopRightRadius: '0px',
+    height: '38px'
+  }
+}))(Card)
+
+const CenterCard = withStyles(() => ({
+  root: {
+    borderRadius: '0px',
+    paddingLeft: '20px',
+    marginLeft: '0px',
+    height: '38px'
+  }
+}))(Card)
+
+const RightCard = withStyles(() => ({
+  root: {
+    borderTopRightRadius: '4px',
+    borderBottomRightRadius: '4px',
+    borderTopLeftRadius: '0px',
+    borderBottomLeftRadius: '0px',
+    marginLeft: '0px !important',
+    height: '38px'
+  }
+}))(Card)
+
+/**
+ * A component to edit geometries
+ * @component
+ * @category FeatureEditor
+ * @since 1.16.0
+ */
+class FeatureEditor extends Component {
+  constructor (props) {
+    super(props)
+    this.state = {
+      interactions: [],
+      editingFeature: null,
+      showMeasurements: false,
+      rotation: 0,
+      style: null
+    }
+  }
+
+  /** In the past we've used temporary layers added to the map to avoid modifying the original features directly.
+  However, this leads to a lot of cleanup since those layers need to be added/removed from both the map and state.
+  That is fine as long as everything goes smoothly but if there is additional logic listening to the map for changes to it's features
+   or layers than we can get left in a broken state that requires a reload.  Using vectorContext.drawFeature instead of
+   a layer added to the map alleviates at least some of this risk.*/
+  _renderFeature = (vectorContext, feature, editStyle = this.props.editStyle) => { // vectorContext.drawFeature only respects a style object and since it is common to have style functions and arrays in Openlayers we need to break the other formats down into objects
+    const { map, areaUOM, distanceUOM, translations } = this.props
+    const { showMeasurements } = this.state
+    const measurementStyles = showMeasurements ? editStyle(feature, map, showMeasurements, { areaUOM, distanceUOM }, translations) : editStyle // eslint-disable-line
+    const styleType = Array.isArray(measurementStyles) ? 'array' : typeof editStyle
+
+    try {
+      switch (styleType) {
+        case 'array':
+          measurementStyles.map(style => {
+            const geom = style.getGeometry() ? style.getGeometry() : feature.getGeometry()
+
+            vectorContext.setStyle(style)
+            vectorContext.drawGeometry(geom)
+          }) // Arrays of style objects require a feature to be drawn for each style object in the array.  This could also be recursively called but that would add extra complexity.
+          break
+        case 'function':
+          this._renderFeature(
+            vectorContext,
+            feature,
+            editStyle(feature, map, showMeasurements, { areaUOM, distanceUOM }, translations)
+          ) // Openlayers style functions return style objects or arrays of style objects so we can call functions recursively.
+          break
+        default: // style object
+          vectorContext.drawFeature(feature, editStyle)
+      }
+    } catch (e) {
+      console.warn(`Geokit was unable to draw the features to the map, this is most likely due to an invalid style object: ${e.message}`, e) // eslint-disable-line
+    }
+  }
+
+  _renderEditOverlay = (e) => {
+    const { map, editStyle } = this.props
+    const { editingFeature } = this.state
+
+    const vectorContext = getVectorContext(e)
+
+    if (!editingFeature) return // to avoid using a setStateCallback we just check for editFeatures first
+
+    this._renderFeature(vectorContext, editingFeature, editStyle)
+
+    return map.render() // render the results asynchronously
+  }
+
+  _addPostComposeListener = () => {
+    const { editingFeature } = this.state
+
+    editingFeature?.get('_ol_kit_parent')?.on('postrender', this._renderEditOverlay) // eslint-disable-line
+  }
+
+  _removePostComposeListener = () => {
+    const { editingFeature } = this.state
+
+    editingFeature?.get('_ol_kit_parent')?.un('postrender', this._renderEditOverlay) // eslint-disable-line
+  }
+
+  _end = () => { // this function cleans up our state and map.  If this does not execute correctly we could get stuck in a corrupted map state.
+    try {
+      const { map } = this.props
+      const { interactions } = this.state
+
+      this._removePostComposeListener()
+
+      interactions.forEach(i => map.removeInteraction(i))
+      this.setState({ editingFeature: null, style: null, interactions: [] })
+    } catch (err) {
+      console.warn(`Geokit encountered a problem while editing a feature: ${err.message}. \n`, err) // eslint-disable-line no-console
+    }
+  }
+
+  showMeasurements = () => {
+    this.setState({ showMeasurements: !this.state.showMeasurements })
+  }
+
+  cancelEdit = () => {
+    const { onEditCancel, editFeature, addEditFeatureToContext } = this.props
+    const { style } = this.state
+
+    this.setState(
+      { canceled: true, editingFeature: null },
+      () => onEditCancel(editFeature, addEditFeatureToContext, style)
+    )
+  }
+
+  finishEdit = () => {
+    const { onEditFinish, addEditFeatureToContext, editFeature } = this.props
+    const { editingFeature, style } = this.state
+
+    this.setState(
+      { editingFeature: null },
+      () => onEditFinish(editFeature, editingFeature, addEditFeatureToContext, style)
+    )
+  }
+
+  init () {
+    const { editOpts, map, onEditBegin, editFeature } = this.props
+
+    const clonedFeature = editFeature.clone() // create a collection of clones of the features in props, this avoids modifying the existing features
+
+    const opts = Object.assign({}, editOpts, {
+      pixelTolerance: 10,
+      features: new olCollection([clonedFeature]),
+      deleteCondition: ({ originalEvent, type }) => {
+        const { altKey, ctrlKey, shiftKey, metaKey } = originalEvent
+        const modifierKeyActive = altKey || ctrlKey || shiftKey || metaKey
+        const altClick = type === 'click' && modifierKeyActive
+        const rightClick = (type === 'pointerdown' || type === 'click') && originalEvent.button === 2
+
+        return rightClick || altClick
+      }
+    })
+
+    const translateInteraction = new Translate({ features: new olCollection([clonedFeature]) }) // ol/interaction/translate only checks for features on the map and since we are not adding these to the map (see additional comments) we use our own that knows to look for the features we pass to it whether or not they're on the map.
+    const modifyInteraction = new olInteractionModify(opts) // ol/interaction/modify doesn't care about the features being on the map or not so it's good to go
+    const style = clonedFeature.getStyle()
+
+    this.setState({
+      anchor: olKitTurf(centroid, [clonedFeature.getGeometry()]).getGeometry().getCoordinates(),
+      interactions: [modifyInteraction, translateInteraction],
+      editingFeature: clonedFeature,
+      style
+    }, () => {
+      this._addPostComposeListener()
+    })
+    map.addInteraction(translateInteraction)
+    map.addInteraction(modifyInteraction)
+    onEditBegin(clonedFeature) // callback function for IAs.  FeatureEditor doesn't do anything to the original features so we tell the IA which features they passed in as props and what features we are editing.  This should help if they want to add custom logic around these features.
+  }
+
+  componentDidMount () {
+    if (!this.props.editFeature) return
+
+    this.init()
+  }
+
+  componentDidUpdate (prevProps) {
+    const { editFeature } = this.props
+    const { interactions } = this.state
+
+    if (prevProps.editFeature && !editFeature) return this._end()
+
+    if (interactions.length === 2 || !editFeature) return
+
+    this.init()
+  }
+
+  componentWillUnmount = () => {
+    const { editingFeature } = this.state
+
+    if (editingFeature) console.warn(`Geokit FeatureEditor has been unmounted unexpectedly.  This may lead undesirable behaviour in your application.`) // eslint-disable-line no-console
+
+    return this._end()
+  }
+
+  rotate = (val) => {
+    const { editingFeature, rotation, anchor } = this.state
+    const geometry = editingFeature.getGeometry()
+    const rotationDiff = val - rotation
+
+    this.setState({ rotation: val }, () => geometry.rotate(-rotationDiff * (Math.PI / 180), anchor))
+  }
+
+  render () {
+    const { translations } = this.props
+    const { editingFeature } = this.state
+    const knobStyle = {
+      width: '35px',
+      height: '35px',
+      padding: '2px'
+    }
+
+    if (!editingFeature) return null
+
+    return (
+      <Toolbar>
+        <Grid item>
+          <ButtonCardActions>
+            <LeftCard>
+              <Button color='secondary' onClick={this.cancelEdit}>
+                {translations['_ol_kit.edit.cancel']}
+              </Button>
+            </LeftCard>
+            <CenterCard style={{ paddingLeft: '20px', marginLeft: '0px' }}>
+              <FormControlLabel
+                style={{ marginBottom: '0px' }}
+                control={
+                  <Knob style={knobStyle} unlockDistance={0} defaultValue={0} max={360} onChange={this.rotate} />
+                }
+                label={translations['_ol_kit.edit.rotate']}
+              />
+            </CenterCard>
+            <RightCard>
+              <Button color='primary' onClick={this.finishEdit}>
+                {translations['_ol_kit.edit.finish']}
+              </Button>
+            </RightCard>
+          </ButtonCardActions>
+        </Grid>
+      </Toolbar>
+    )
+  }
+}
+
+FeatureEditor.propTypes = {
+  editOpts: PropTypes.exact({
+    condition: PropTypes.string,
+    deleteCondition: PropTypes.string,
+    insertVertexCondition: PropTypes.string,
+    pixelTolerance: PropTypes.number,
+    style: PropTypes.object,
+    source: PropTypes.object,
+    wrapX: PropTypes.bool
+  }).isRequired,
+  editFeature: PropTypes.object,
+  addEditFeatureToContext: PropTypes.func,
+  map: PropTypes.object,
+  onEditBegin: PropTypes.func,
+  onEditFinish: PropTypes.func,
+  onEditCancel: PropTypes.func,
+  editStyle: PropTypes.oneOfType([
+    PropTypes.func,
+    PropTypes.object,
+    PropTypes.array
+  ]),
+  areaUOM: PropTypes.string,
+  distanceUOM: PropTypes.string,
+  translations: PropTypes.object
+}
+
+FeatureEditor.defaultProps = {
+  editOpts: {},
+  onEditFinish: (feature, updatedFeature, addEditFeatureToContext, style) => {
+    const geom = updatedFeature.getGeometry()
+
+    if (!feature) return
+
+    feature.setGeometry(geom)
+
+    feature.setStyle(style || null) // restore the original feature's style
+    addEditFeatureToContext(null)
+  },
+  onEditBegin: (feature) => {
+    feature.setStyle(new olStyleStyle({}))
+  },
+  onEditCancel: (feature, addEditFeatureToContext, style) => {
+    feature.setStyle(style || null) // restore the original feature's style
+    addEditFeatureToContext(null)
+  },
+  editStyle: (feature, map, showMeasurements = false, { areaUOM, distanceUOM }, translations) => { // eslint-disable-line
+    return immediateEditStyle(
+      { areaUOM, distanceUOM, showMeasurements, map, translations, language: navigator.language },
+      feature,
+      map.getView().getResolution()
+    )
+  }
+}
+
+export default connectToContext(FeatureEditor)
+
+
+
+ + + + +
+
+
+
+ + + + + + + + diff --git a/docs/FeatureEditor_styles.js.html b/docs/FeatureEditor_styles.js.html new file mode 100644 index 00000000..f898b3c0 --- /dev/null +++ b/docs/FeatureEditor_styles.js.html @@ -0,0 +1,450 @@ + + + + + + + ol-kit | FeatureEditor/styles.js + + + + + + + + + + + + + + + + +
+
+ +
+
+
+
+ +
+
+ + + + + +
+
+
import olFeature from 'ol/Feature'
+import olGeomLineString from 'ol/geom/LineString'
+import olGeomMultiPoint from 'ol/geom/MultiPoint'
+import olStyleFill from 'ol/style/Fill'
+import olStyleStroke from 'ol/style/Stroke'
+import olStyleStyle from 'ol/style/Style'
+import olStyleCircle from 'ol/style/Circle'
+import olStyleText from 'ol/style/Text'
+import olPoint from 'ol/geom/Point'
+import centroid from '@turf/centroid'
+
+import { pointsFromVertices, olKitTurf, pairCoordinates, getCoordinates, getText, calculateScale } from './utils'
+
+function resolveStyleFunctionArgs (args) {
+  // Using function.prototype.bind with additional arguments injects those arguments at the zeroth index of the arguements object and since opts is optional we need to handle a variable arguement object
+  const argLength = args.length
+  const feature = args[argLength - 2] || args
+  const resolution = args[argLength - 1]
+  const opts = argLength >= 3 ? args[0] : {}
+
+  return { feature, resolution, opts }
+}
+
+/**
+ * Style ol/Features
+ * @function
+ * @param {object} opts - The config object
+ * @param {ol/Feature} feature - The feature you want to style
+ * @param {number} resolution - the resolution of the map
+ * @returns {object} The style object for the passed feature
+ */
+function styleText (...args) {
+  const { feature, resolution, opts } = resolveStyleFunctionArgs(args)
+  const label = feature.get('_vmf_label')
+  const isMeasurement = feature.get('_vmf_type') === '_vmf_measurement'
+  const isCentroidLabel = feature.get('_ol_kit_needs_centroid_label')
+  const offsetY = isCentroidLabel ? 20 : isMeasurement ? 0 : (label.fontSize || 16) / 2 // eslint-disable-line
+  const styleOpts = {
+    placement: opts.placement || 'point',
+    textAlign: opts.textAlign,
+    textBaseline: opts.textBaseLine || 'top',
+    maxAngle: opts.maxAngle || Infinity,
+    // These weird calculations provide the most accurate placement relative to the textarea where people edit their text
+    offsetX: isMeasurement ? 0 : (label.fontSize || 16) / 2,
+    offsetY: offsetY,
+    rotation: -feature.get('_vmf_rotation') || 0,
+    font: `bold ${label.fontSize || 16}px sans-serif`,
+    stroke: new olStyleStroke({
+      // show a black outline unless the text color is black; then show a white outline
+      color: label.color === '#000000' ? '#ffffff' : '#000000',
+      width: 3
+    }),
+    text: getText(label, resolution, opts),
+    scale: calculateScale(opts.store.map, feature),
+    fill: new olStyleFill({
+      color: label.color || '#ffffff'
+    }),
+    image: new olStyleCircle({
+      radius: 7,
+      fill: new olStyleFill({
+        color: '#ffcc33'
+      })
+    }),
+    rotateWithView: false,
+    overflow: true
+  }
+
+  return new olStyleText(styleOpts)
+}
+
+function coordinateLabels (multiPoint, resolution, opts) {
+  return multiPoint.getPoints().map(point => coordinateLabel(point, resolution, opts))
+}
+
+function coordinateLabel (pointGeometry, resolution, opts) {
+  const geom = pointGeometry.clone()
+  const pointFeature = new olFeature({
+    geometry: geom,
+    _vmf_label: {
+      fontSize: 16
+    }
+  })
+
+  return new olStyleStyle({
+    text: styleText({
+      store: opts,
+      placement: 'point',
+      maxAngle: Math.PI / 4,
+      textAlign: undefined,
+      textBaseline: 'hanging'
+    }, pointFeature, resolution),
+    geometry: geom
+  })
+}
+
+function lengthLabel (lineGeometry, resolution, opts) {
+  const geom = lineGeometry.clone()
+  const perimeterFeature = new olFeature({
+    geometry: geom,
+    _vmf_type: '_vmf_measurement',
+    _vmf_label: {
+      fontSize: 16
+    }
+  })
+
+  return new olStyleStyle({
+    text: styleText({
+      store: opts,
+      placement: 'line',
+      maxAngle: Math.PI / 4,
+      textAlign: undefined,
+      textBaseline: 'hanging'
+    }, perimeterFeature, resolution),
+    geometry: geom
+  })
+}
+
+function perimeterSegmentLabels (polygonGeometry, resolution, opts) {
+  const labelStyles = []
+  const clonedGeom = polygonGeometry.clone()
+  const perimeterCoords = clonedGeom.getLinearRing(0).getCoordinates()
+
+  for (let i = 0; i < perimeterCoords.length - 1;) {
+    const segment = [perimeterCoords[i], perimeterCoords[i += 1]]
+
+    if (segment.flat(Infinity).includes(undefined)) break // exit the loop if we get any undefined values.  It's better to not label a segment than to break draw entirely.
+    const segmentGeom = new olGeomLineString(segment)
+    const segmentFeature = new olFeature({
+      geometry: segmentGeom,
+      _vmf_type: '_vmf_measurement',
+      _vmf_label: {
+        fontSize: 16
+      }
+    })
+
+    labelStyles.push(new olStyleStyle({
+      text: styleText({
+        store: opts,
+        placement: 'line',
+        textBaseline: 'hanging'
+      }, segmentFeature, resolution),
+      geometry: segmentGeom
+    }))
+  }
+
+  return labelStyles
+}
+
+function areaLabel (polygonGeometry, resolution, opts) {
+  const areaGeometry = polygonGeometry.clone()
+  const areaFeature = new olFeature({
+    geometry: areaGeometry,
+    _vmf_type: '_vmf_measurement',
+    _vmf_label: {
+      fontSize: 16
+    }
+  })
+
+  return new olStyleStyle({
+    text: styleText({
+      store: opts
+    }, areaFeature, resolution),
+    geometry: areaGeometry
+  })
+}
+
+function centroidLabel (geometry, resolution, opts) {
+  const point = geometry.getType() === 'Circle ' ? new olPoint(geometry.clone().getCenter()) : olKitTurf(centroid, [geometry]).getGeometry()
+  const pointFeature = new olFeature({
+    geometry: point,
+    _vmf_type: '_vmf_measurement', // styleText determines the type of label to render based on the feature's type so we need this temporary feature to be a 'measurement' feature
+    _vmf_label: {
+      fontSize: 16
+    },
+    _ol_kit_needs_centroid_label: true
+  })
+
+  return new olStyleStyle({
+    text: styleText({
+      store: opts,
+      placement: 'point',
+      maxAngle: Math.PI / 4,
+      textAlign: undefined,
+      textBaseline: 'hanging'
+    }, pointFeature, resolution),
+    geometry: point
+  })
+}
+
+function getVertices (args) {
+  const { feature } = resolveStyleFunctionArgs(args)
+  const geometry = feature.getGeometry()
+  const layout = geometry.getLayout()
+
+  switch (geometry.getType()) {
+    case 'MultiPolygon': {
+      const coordinates = geometry.getCoordinates()
+      const flatCoords = coordinates.flat(Infinity)
+      const pairedCoords = pairCoordinates(flatCoords, layout.length)
+
+      return new olGeomMultiPoint(pairedCoords, layout)
+    }
+    case 'GeometryCollection': {
+      const deepCoords = getCoordinates(geometry)
+      const flatCoords = deepCoords.flat(Infinity)
+      const pairedCoords = pairCoordinates(flatCoords, layout.length)
+
+      return new olGeomMultiPoint(pairedCoords)
+    }
+    default:
+      return new olGeomMultiPoint(pointsFromVertices(geometry))
+  }
+}
+
+/**
+ * Style an ol/Feature with orange circle vertices, a blue outline, an area label, and a perimeter length label. Can be used with individual features as a style function or call it directly to get a style object for use with `vectorContext.drawFeature`
+ * @function
+ * @param {object} opts - The config object
+ * @param {ol/Feature} feature - The feature you want to style
+ * @param {number} resolution - the resolution of the map
+ * @returns {object} The style object for the passed feature
+ */
+export function immediateEditStyle (...args) {
+  const { feature, resolution, opts = {} } = resolveStyleFunctionArgs(args)
+  const fill = new olStyleFill({
+    color: 'rgba(0, 0, 255, 0.2)'
+  })
+  const stroke = new olStyleStroke({
+    color: 'blue',
+    width: 3
+  })
+  const image = new olStyleCircle({
+    radius: 7,
+    fill: new olStyleFill({
+      color: '#ffcc33'
+    })
+  })
+  const vertexGeometry = getVertices(args)
+  const vertices = [new olStyleStyle({
+    image: new olStyleCircle({
+      radius: 5,
+      fill: new olStyleFill({
+        color: 'orange'
+      })
+    }),
+    geometry: vertexGeometry
+  })]
+
+  // checking to see if opts.geometry is defined allows us to call this function recursively for geometry collections
+  const geometry = opts.geometry || feature.clone().getGeometry()
+  const areaLabelsFlag = feature.get('_ol_kit_area_labels')
+  const distanceLabelsFlag = feature.get('_ol_kit_distance_labels')
+  const needsVertexLabels = feature.get('_ol_kit_coordinate_labels') !== undefined
+  const needsCentroidLabels = feature.get('_ol_kit_needs_centroid_label') !== undefined
+  const needsAreaLabels = opts.showMeasurements && areaLabelsFlag
+  const needsDistanceLabels = opts.showMeasurements && distanceLabelsFlag
+  const isNotCircle = feature.get('_ol_kit_draw_mode') !== 'circle'
+  const vertexLabels = (needsVertexLabels && opts.showMeasurements && isNotCircle) ? coordinateLabels(getVertices(feature), resolution, opts) : [] // eslint-disable-line
+  const type = geometry.getType()
+
+  switch (type) {
+    case 'Point':
+      return [new olStyleStyle({
+        image
+      }), ...vertexLabels]
+    case 'LineString': {
+      const lengthLabels = needsDistanceLabels ? [lengthLabel(geometry, resolution, opts)] : []
+
+      return [new olStyleStyle({
+        stroke,
+        image
+      }), ...vertices, ...lengthLabels, ...vertexLabels]
+    }
+    case 'MultiLineString': {
+      const lineStrings = geometry.getLineStrings()
+      const lengthLabels = needsDistanceLabels
+        ? lineStrings.map(lineString => lengthLabel(lineString, resolution, opts)) : []
+
+      return [new olStyleStyle({
+        stroke,
+        image
+      }), ...vertices, ...lengthLabels, ...vertexLabels]
+    }
+    case 'MultiPolygon': {
+      const polygons = geometry.getPolygons()
+      // create a label for each polygon
+      const perimeterLabels = needsDistanceLabels
+        ? polygons.map(polygon => perimeterSegmentLabels(polygon, resolution, opts)) : []
+      const areaLabels = needsAreaLabels
+        ? polygons.map(polygon => areaLabel(polygon, resolution, opts)) : []
+
+      return [new olStyleStyle({
+        stroke,
+        fill,
+        image
+      }), ...vertices, ...areaLabels, ...perimeterLabels.flat(Infinity), ...vertexLabels]
+    }
+    case 'Polygon': {
+      let labels = vertexLabels // eslint-disable-line
+
+      if (needsAreaLabels) labels.push(areaLabel(geometry, resolution, opts))
+      if (needsDistanceLabels && isNotCircle) labels = [...labels, ...perimeterSegmentLabels(geometry, resolution, opts)] //eslint-disable-line
+      if (needsCentroidLabels) labels.push(centroidLabel(geometry, resolution, opts))
+
+      return [new olStyleStyle({
+        stroke,
+        fill,
+        image
+      }), ...vertices, ...labels]
+    }
+    case 'GeometryCollection': {
+      // Recursive.  Since feature stores our metadata it needs to be preserved.  Therefore we pass the geometry we want to use through the opts object.
+      const componentStyles = geometry.getGeometries().map(geom => {
+        return immediateEditStyle.apply(this, [Object.assign(opts, { geometry: geom }), feature, resolution])
+      })
+      const flatStyles = componentStyles.flat(Infinity)
+
+      return flatStyles
+    }
+    case 'Circle': {
+      const labels = vertexLabels
+
+      if (needsAreaLabels) labels.push(areaLabel(geometry, resolution, opts))
+      if (needsCentroidLabels) labels.push(centroidLabel(geometry, resolution, opts))
+
+      return [new olStyleStyle({
+        stroke,
+        fill
+      }), ...labels]
+    }
+    default:
+      return [new olStyleStyle({
+        stroke,
+        fill,
+        image
+      }), ...vertices]
+  }
+}
+
+
+
+ + + + +
+
+
+
+ + + + + + + + diff --git a/docs/GoogleDirections.html b/docs/GoogleDirections.html index c180924d..f0ffc516 100644 --- a/docs/GoogleDirections.html +++ b/docs/GoogleDirections.html @@ -65,7 +65,7 @@ diff --git a/docs/GoogleDirections_GoogleDirections.js.html b/docs/GoogleDirections_GoogleDirections.js.html index 967630d5..7a94de2d 100644 --- a/docs/GoogleDirections_GoogleDirections.js.html +++ b/docs/GoogleDirections_GoogleDirections.js.html @@ -67,7 +67,7 @@ diff --git a/docs/GooglePlacesSearch.html b/docs/GooglePlacesSearch.html index 42409efa..4633b12c 100644 --- a/docs/GooglePlacesSearch.html +++ b/docs/GooglePlacesSearch.html @@ -65,7 +65,7 @@ diff --git a/docs/GooglePlacesSearch_GooglePlacesSearch.js.html b/docs/GooglePlacesSearch_GooglePlacesSearch.js.html index 559c7edc..159c4bd3 100644 --- a/docs/GooglePlacesSearch_GooglePlacesSearch.js.html +++ b/docs/GooglePlacesSearch_GooglePlacesSearch.js.html @@ -67,7 +67,7 @@ diff --git a/docs/HeatmapControls.html b/docs/HeatmapControls.html index ebe8142b..08f3a8db 100644 --- a/docs/HeatmapControls.html +++ b/docs/HeatmapControls.html @@ -65,7 +65,7 @@ diff --git a/docs/Heatmap_HeatmapControls.js.html b/docs/Heatmap_HeatmapControls.js.html index afce39ed..48a75522 100644 --- a/docs/Heatmap_HeatmapControls.js.html +++ b/docs/Heatmap_HeatmapControls.js.html @@ -67,7 +67,7 @@ diff --git a/docs/ImageExif_ImageExif.js.html b/docs/ImageExif_ImageExif.js.html index 442b4b8b..a093396f 100644 --- a/docs/ImageExif_ImageExif.js.html +++ b/docs/ImageExif_ImageExif.js.html @@ -67,7 +67,7 @@ diff --git a/docs/LayerPanel.html b/docs/LayerPanel.html index deda92a0..2b101065 100644 --- a/docs/LayerPanel.html +++ b/docs/LayerPanel.html @@ -65,7 +65,7 @@ diff --git a/docs/LayerPanelActionDuplicate.html b/docs/LayerPanelActionDuplicate.html index 76f5e81f..39d05572 100644 --- a/docs/LayerPanelActionDuplicate.html +++ b/docs/LayerPanelActionDuplicate.html @@ -65,7 +65,7 @@ diff --git a/docs/LayerPanelActionExport.html b/docs/LayerPanelActionExport.html index c368c79d..6bf69283 100644 --- a/docs/LayerPanelActionExport.html +++ b/docs/LayerPanelActionExport.html @@ -65,7 +65,7 @@ diff --git a/docs/LayerPanelActionExtent.html b/docs/LayerPanelActionExtent.html index f2e77851..4ac5a093 100644 --- a/docs/LayerPanelActionExtent.html +++ b/docs/LayerPanelActionExtent.html @@ -65,7 +65,7 @@ diff --git a/docs/LayerPanelActionHeatmap.html b/docs/LayerPanelActionHeatmap.html index 05e5ddf1..bdf94e23 100644 --- a/docs/LayerPanelActionHeatmap.html +++ b/docs/LayerPanelActionHeatmap.html @@ -65,7 +65,7 @@ diff --git a/docs/LayerPanelActionImport.html b/docs/LayerPanelActionImport.html index c2b40d93..8a9d3029 100644 --- a/docs/LayerPanelActionImport.html +++ b/docs/LayerPanelActionImport.html @@ -65,7 +65,7 @@ diff --git a/docs/LayerPanelActionMerge.html b/docs/LayerPanelActionMerge.html index 06849c48..a9ae92c6 100644 --- a/docs/LayerPanelActionMerge.html +++ b/docs/LayerPanelActionMerge.html @@ -65,7 +65,7 @@ diff --git a/docs/LayerPanelActionOpacity.html b/docs/LayerPanelActionOpacity.html index 265fbb97..b42e00c6 100644 --- a/docs/LayerPanelActionOpacity.html +++ b/docs/LayerPanelActionOpacity.html @@ -65,7 +65,7 @@ diff --git a/docs/LayerPanelActionRemove.html b/docs/LayerPanelActionRemove.html index c52a173f..0ae48ecf 100644 --- a/docs/LayerPanelActionRemove.html +++ b/docs/LayerPanelActionRemove.html @@ -65,7 +65,7 @@ diff --git a/docs/LayerPanelActions.html b/docs/LayerPanelActions.html index abaf5b30..df1a7a8e 100644 --- a/docs/LayerPanelActions.html +++ b/docs/LayerPanelActions.html @@ -65,7 +65,7 @@ diff --git a/docs/LayerPanelBase.html b/docs/LayerPanelBase.html index c21dd854..1ea44940 100644 --- a/docs/LayerPanelBase.html +++ b/docs/LayerPanelBase.html @@ -65,7 +65,7 @@ diff --git a/docs/LayerPanelCheckbox.html b/docs/LayerPanelCheckbox.html index a1731a2b..700576c0 100644 --- a/docs/LayerPanelCheckbox.html +++ b/docs/LayerPanelCheckbox.html @@ -65,7 +65,7 @@ diff --git a/docs/LayerPanelContent.html b/docs/LayerPanelContent.html index 02be66bb..13142b3f 100644 --- a/docs/LayerPanelContent.html +++ b/docs/LayerPanelContent.html @@ -65,7 +65,7 @@ diff --git a/docs/LayerPanelHeader.html b/docs/LayerPanelHeader.html index 017cf56b..553a0fe8 100644 --- a/docs/LayerPanelHeader.html +++ b/docs/LayerPanelHeader.html @@ -65,7 +65,7 @@ diff --git a/docs/LayerPanelLayersPage.html b/docs/LayerPanelLayersPage.html index ea402032..82b3ed16 100644 --- a/docs/LayerPanelLayersPage.html +++ b/docs/LayerPanelLayersPage.html @@ -65,7 +65,7 @@ diff --git a/docs/LayerPanelList.html b/docs/LayerPanelList.html index 06a51003..fd98d3ad 100644 --- a/docs/LayerPanelList.html +++ b/docs/LayerPanelList.html @@ -65,7 +65,7 @@ diff --git a/docs/LayerPanelListItem.html b/docs/LayerPanelListItem.html index a94d0ee9..07c44ed7 100644 --- a/docs/LayerPanelListItem.html +++ b/docs/LayerPanelListItem.html @@ -65,7 +65,7 @@ diff --git a/docs/LayerPanelMenu.html b/docs/LayerPanelMenu.html index d3044044..dd63fe62 100644 --- a/docs/LayerPanelMenu.html +++ b/docs/LayerPanelMenu.html @@ -65,7 +65,7 @@ diff --git a/docs/LayerPanelPage.html b/docs/LayerPanelPage.html index ccecc0c2..b38c333f 100644 --- a/docs/LayerPanelPage.html +++ b/docs/LayerPanelPage.html @@ -65,7 +65,7 @@ diff --git a/docs/LayerPanel_LayerPanel.js.html b/docs/LayerPanel_LayerPanel.js.html index 537f28ff..d633cca3 100644 --- a/docs/LayerPanel_LayerPanel.js.html +++ b/docs/LayerPanel_LayerPanel.js.html @@ -67,7 +67,7 @@ diff --git a/docs/LayerPanel_LayerPanelActionDuplicate_index.js.html b/docs/LayerPanel_LayerPanelActionDuplicate_index.js.html index 9dc78f95..f501dcfc 100644 --- a/docs/LayerPanel_LayerPanelActionDuplicate_index.js.html +++ b/docs/LayerPanel_LayerPanelActionDuplicate_index.js.html @@ -67,7 +67,7 @@ diff --git a/docs/LayerPanel_LayerPanelActionExport_index.js.html b/docs/LayerPanel_LayerPanelActionExport_index.js.html index 030199c3..28d3765b 100644 --- a/docs/LayerPanel_LayerPanelActionExport_index.js.html +++ b/docs/LayerPanel_LayerPanelActionExport_index.js.html @@ -67,7 +67,7 @@ diff --git a/docs/LayerPanel_LayerPanelActionExport_utils.js.html b/docs/LayerPanel_LayerPanelActionExport_utils.js.html index 032fd644..21d39ede 100644 --- a/docs/LayerPanel_LayerPanelActionExport_utils.js.html +++ b/docs/LayerPanel_LayerPanelActionExport_utils.js.html @@ -67,7 +67,7 @@ diff --git a/docs/LayerPanel_LayerPanelActionExtent_index.js.html b/docs/LayerPanel_LayerPanelActionExtent_index.js.html index 57f45415..8be3754a 100644 --- a/docs/LayerPanel_LayerPanelActionExtent_index.js.html +++ b/docs/LayerPanel_LayerPanelActionExtent_index.js.html @@ -67,7 +67,7 @@ diff --git a/docs/LayerPanel_LayerPanelActionHeatmap_index.js.html b/docs/LayerPanel_LayerPanelActionHeatmap_index.js.html index 6116533d..1c524411 100644 --- a/docs/LayerPanel_LayerPanelActionHeatmap_index.js.html +++ b/docs/LayerPanel_LayerPanelActionHeatmap_index.js.html @@ -67,7 +67,7 @@ diff --git a/docs/LayerPanel_LayerPanelActionHeatmap_utils.js.html b/docs/LayerPanel_LayerPanelActionHeatmap_utils.js.html index 20a16b8c..4aafa636 100644 --- a/docs/LayerPanel_LayerPanelActionHeatmap_utils.js.html +++ b/docs/LayerPanel_LayerPanelActionHeatmap_utils.js.html @@ -67,7 +67,7 @@ diff --git a/docs/LayerPanel_LayerPanelActionImport_index.js.html b/docs/LayerPanel_LayerPanelActionImport_index.js.html index d1f362b7..4868035d 100644 --- a/docs/LayerPanel_LayerPanelActionImport_index.js.html +++ b/docs/LayerPanel_LayerPanelActionImport_index.js.html @@ -67,7 +67,7 @@ diff --git a/docs/LayerPanel_LayerPanelActionImport_utils.js.html b/docs/LayerPanel_LayerPanelActionImport_utils.js.html index ac0d2ec7..8052a96f 100644 --- a/docs/LayerPanel_LayerPanelActionImport_utils.js.html +++ b/docs/LayerPanel_LayerPanelActionImport_utils.js.html @@ -67,7 +67,7 @@ @@ -109,7 +109,6 @@ * Converts the given file into a layer * @function convertFileToLayer * @category LayerPanel - * @since 6.5.0 * @param {Blob} [file] - the file to be converted. Accepts, 'kmz', 'kml', 'geojson', 'wkt', 'csv', 'zip', and 'json' file types. * @param {olMap} [map] - the openlayers map */ diff --git a/docs/LayerPanel_LayerPanelActionMerge_index.js.html b/docs/LayerPanel_LayerPanelActionMerge_index.js.html index ee9f75ab..c7271948 100644 --- a/docs/LayerPanel_LayerPanelActionMerge_index.js.html +++ b/docs/LayerPanel_LayerPanelActionMerge_index.js.html @@ -67,7 +67,7 @@ diff --git a/docs/LayerPanel_LayerPanelActionMerge_utils.js.html b/docs/LayerPanel_LayerPanelActionMerge_utils.js.html index ae82ad7e..6f265db8 100644 --- a/docs/LayerPanel_LayerPanelActionMerge_utils.js.html +++ b/docs/LayerPanel_LayerPanelActionMerge_utils.js.html @@ -67,7 +67,7 @@ diff --git a/docs/LayerPanel_LayerPanelActionOpacity_index.js.html b/docs/LayerPanel_LayerPanelActionOpacity_index.js.html index eb86889c..e71ae1cd 100644 --- a/docs/LayerPanel_LayerPanelActionOpacity_index.js.html +++ b/docs/LayerPanel_LayerPanelActionOpacity_index.js.html @@ -67,7 +67,7 @@ diff --git a/docs/LayerPanel_LayerPanelActionRemove_index.js.html b/docs/LayerPanel_LayerPanelActionRemove_index.js.html index 996933e8..cacd012b 100644 --- a/docs/LayerPanel_LayerPanelActionRemove_index.js.html +++ b/docs/LayerPanel_LayerPanelActionRemove_index.js.html @@ -67,7 +67,7 @@ diff --git a/docs/LayerPanel_LayerPanelActions_index.js.html b/docs/LayerPanel_LayerPanelActions_index.js.html index 7d02533e..a2db399d 100644 --- a/docs/LayerPanel_LayerPanelActions_index.js.html +++ b/docs/LayerPanel_LayerPanelActions_index.js.html @@ -67,7 +67,7 @@ diff --git a/docs/LayerPanel_LayerPanelBase_index.js.html b/docs/LayerPanel_LayerPanelBase_index.js.html index 9c84aadb..e2cf9621 100644 --- a/docs/LayerPanel_LayerPanelBase_index.js.html +++ b/docs/LayerPanel_LayerPanelBase_index.js.html @@ -67,7 +67,7 @@ diff --git a/docs/LayerPanel_LayerPanelCheckbox_index.js.html b/docs/LayerPanel_LayerPanelCheckbox_index.js.html index 48448cb8..975b408b 100644 --- a/docs/LayerPanel_LayerPanelCheckbox_index.js.html +++ b/docs/LayerPanel_LayerPanelCheckbox_index.js.html @@ -67,7 +67,7 @@ diff --git a/docs/LayerPanel_LayerPanelContent_index.js.html b/docs/LayerPanel_LayerPanelContent_index.js.html index e0b77534..3399ded4 100644 --- a/docs/LayerPanel_LayerPanelContent_index.js.html +++ b/docs/LayerPanel_LayerPanelContent_index.js.html @@ -67,7 +67,7 @@ diff --git a/docs/LayerPanel_LayerPanelHeader_index.js.html b/docs/LayerPanel_LayerPanelHeader_index.js.html index 1a0965c2..d6760c35 100644 --- a/docs/LayerPanel_LayerPanelHeader_index.js.html +++ b/docs/LayerPanel_LayerPanelHeader_index.js.html @@ -67,7 +67,7 @@ diff --git a/docs/LayerPanel_LayerPanelLayersPage_index.js.html b/docs/LayerPanel_LayerPanelLayersPage_index.js.html index 6d341338..e6230149 100644 --- a/docs/LayerPanel_LayerPanelLayersPage_index.js.html +++ b/docs/LayerPanel_LayerPanelLayersPage_index.js.html @@ -67,7 +67,7 @@ diff --git a/docs/LayerPanel_LayerPanelListItem_index.js.html b/docs/LayerPanel_LayerPanelListItem_index.js.html index 77f2bde9..8d245202 100644 --- a/docs/LayerPanel_LayerPanelListItem_index.js.html +++ b/docs/LayerPanel_LayerPanelListItem_index.js.html @@ -67,7 +67,7 @@ diff --git a/docs/LayerPanel_LayerPanelList_index.js.html b/docs/LayerPanel_LayerPanelList_index.js.html index 0ba213ab..86943cbe 100644 --- a/docs/LayerPanel_LayerPanelList_index.js.html +++ b/docs/LayerPanel_LayerPanelList_index.js.html @@ -67,7 +67,7 @@ diff --git a/docs/LayerPanel_LayerPanelMenu_index.js.html b/docs/LayerPanel_LayerPanelMenu_index.js.html index bc84671a..cce2e0a6 100644 --- a/docs/LayerPanel_LayerPanelMenu_index.js.html +++ b/docs/LayerPanel_LayerPanelMenu_index.js.html @@ -67,7 +67,7 @@ diff --git a/docs/LayerPanel_LayerPanelPage_index.js.html b/docs/LayerPanel_LayerPanelPage_index.js.html index ca5318bb..9e315833 100644 --- a/docs/LayerPanel_LayerPanelPage_index.js.html +++ b/docs/LayerPanel_LayerPanelPage_index.js.html @@ -67,7 +67,7 @@ diff --git a/docs/LayerStyler.html b/docs/LayerStyler.html index c7d5e5b3..859d6ee6 100644 --- a/docs/LayerStyler.html +++ b/docs/LayerStyler.html @@ -65,7 +65,7 @@ diff --git a/docs/LayerStyler_LayerStyler.js.html b/docs/LayerStyler_LayerStyler.js.html index d625c0e9..c2951b0b 100644 --- a/docs/LayerStyler_LayerStyler.js.html +++ b/docs/LayerStyler_LayerStyler.js.html @@ -67,7 +67,7 @@ diff --git a/docs/LayerStyler_StyleManager_index.js.html b/docs/LayerStyler_StyleManager_index.js.html index 8f7b8ca0..e167fa6a 100644 --- a/docs/LayerStyler_StyleManager_index.js.html +++ b/docs/LayerStyler_StyleManager_index.js.html @@ -67,7 +67,7 @@ diff --git a/docs/LayerStyler__AttributesFilter_index.js.html b/docs/LayerStyler__AttributesFilter_index.js.html index 4546fe57..c4ce9de9 100644 --- a/docs/LayerStyler__AttributesFilter_index.js.html +++ b/docs/LayerStyler__AttributesFilter_index.js.html @@ -67,7 +67,7 @@ diff --git a/docs/LayerStyler__ColorPicker_index.js.html b/docs/LayerStyler__ColorPicker_index.js.html index 57aa61ec..2aa86f3d 100644 --- a/docs/LayerStyler__ColorPicker_index.js.html +++ b/docs/LayerStyler__ColorPicker_index.js.html @@ -67,7 +67,7 @@ diff --git a/docs/LayerStyler__LabelStyler_index.js.html b/docs/LayerStyler__LabelStyler_index.js.html index 14e5e306..a5ca91d4 100644 --- a/docs/LayerStyler__LabelStyler_index.js.html +++ b/docs/LayerStyler__LabelStyler_index.js.html @@ -67,7 +67,7 @@ diff --git a/docs/LayerStyler__LayerStyler__StyleGroup__GenericSymbolizer_index.js.html b/docs/LayerStyler__LayerStyler__StyleGroup__GenericSymbolizer_index.js.html index 59d044a9..85cc735e 100644 --- a/docs/LayerStyler__LayerStyler__StyleGroup__GenericSymbolizer_index.js.html +++ b/docs/LayerStyler__LayerStyler__StyleGroup__GenericSymbolizer_index.js.html @@ -67,7 +67,7 @@ diff --git a/docs/LayerStyler__LayerStyler__StyleGroup_index.js.html b/docs/LayerStyler__LayerStyler__StyleGroup_index.js.html index 34e0f90e..9c3fc360 100644 --- a/docs/LayerStyler__LayerStyler__StyleGroup_index.js.html +++ b/docs/LayerStyler__LayerStyler__StyleGroup_index.js.html @@ -67,7 +67,7 @@ diff --git a/docs/LayerStyler__LayerStyler_index.js.html b/docs/LayerStyler__LayerStyler_index.js.html index 40473c73..7c4f80b2 100644 --- a/docs/LayerStyler__LayerStyler_index.js.html +++ b/docs/LayerStyler__LayerStyler_index.js.html @@ -67,7 +67,7 @@ diff --git a/docs/LayerStyler__Popover_index.js.html b/docs/LayerStyler__Popover_index.js.html index 193840d5..999594e1 100644 --- a/docs/LayerStyler__Popover_index.js.html +++ b/docs/LayerStyler__Popover_index.js.html @@ -67,7 +67,7 @@ diff --git a/docs/LayerStyler__SelectTabs_index.js.html b/docs/LayerStyler__SelectTabs_index.js.html index 98beea2a..099440a2 100644 --- a/docs/LayerStyler__SelectTabs_index.js.html +++ b/docs/LayerStyler__SelectTabs_index.js.html @@ -67,7 +67,7 @@ diff --git a/docs/LayerStyler__Selector_index.js.html b/docs/LayerStyler__Selector_index.js.html index 17014c77..ed81ddf7 100644 --- a/docs/LayerStyler__Selector_index.js.html +++ b/docs/LayerStyler__Selector_index.js.html @@ -67,7 +67,7 @@ diff --git a/docs/Map.html b/docs/Map.html index 17256853..130f2cb3 100644 --- a/docs/Map.html +++ b/docs/Map.html @@ -65,7 +65,7 @@ diff --git a/docs/Map_Map.js.html b/docs/Map_Map.js.html index 9e116345..9b9d168e 100644 --- a/docs/Map_Map.js.html +++ b/docs/Map_Map.js.html @@ -67,7 +67,7 @@ diff --git a/docs/Map_utils.js.html b/docs/Map_utils.js.html index 9f1b4fb1..f18f15fa 100644 --- a/docs/Map_utils.js.html +++ b/docs/Map_utils.js.html @@ -67,7 +67,7 @@ diff --git a/docs/Measure_utils.js.html b/docs/Measure_utils.js.html index 9709b4bc..d8476bb4 100644 --- a/docs/Measure_utils.js.html +++ b/docs/Measure_utils.js.html @@ -67,7 +67,7 @@ diff --git a/docs/MultiMapManager.html b/docs/MultiMapManager.html index dd7875e7..2361083a 100644 --- a/docs/MultiMapManager.html +++ b/docs/MultiMapManager.html @@ -65,7 +65,7 @@ diff --git a/docs/MultiMapManager_MultiMapManager.js.html b/docs/MultiMapManager_MultiMapManager.js.html index 8719f946..e9a7758a 100644 --- a/docs/MultiMapManager_MultiMapManager.js.html +++ b/docs/MultiMapManager_MultiMapManager.js.html @@ -67,7 +67,7 @@ diff --git a/docs/MultiMapManager_SafeParent.js.html b/docs/MultiMapManager_SafeParent.js.html index 10a3858a..bf675533 100644 --- a/docs/MultiMapManager_SafeParent.js.html +++ b/docs/MultiMapManager_SafeParent.js.html @@ -67,7 +67,7 @@ diff --git a/docs/MultiMapManager_utils.js.html b/docs/MultiMapManager_utils.js.html index b0787ae6..77e2dec0 100644 --- a/docs/MultiMapManager_utils.js.html +++ b/docs/MultiMapManager_utils.js.html @@ -67,7 +67,7 @@ diff --git a/docs/Pdf_utils.js.html b/docs/Pdf_utils.js.html index d0552df1..866bd8a8 100644 --- a/docs/Pdf_utils.js.html +++ b/docs/Pdf_utils.js.html @@ -67,7 +67,7 @@ diff --git a/docs/Popup.html b/docs/Popup.html index 74f6d733..2a8ce79f 100644 --- a/docs/Popup.html +++ b/docs/Popup.html @@ -65,7 +65,7 @@ diff --git a/docs/PopupActionCopyWkt.html b/docs/PopupActionCopyWkt.html index 3ef1e415..0e262d92 100644 --- a/docs/PopupActionCopyWkt.html +++ b/docs/PopupActionCopyWkt.html @@ -65,7 +65,7 @@ diff --git a/docs/PopupActionGoogleMaps.html b/docs/PopupActionGoogleMaps.html index 16fc1491..b729752e 100644 --- a/docs/PopupActionGoogleMaps.html +++ b/docs/PopupActionGoogleMaps.html @@ -65,7 +65,7 @@ diff --git a/docs/PopupActionGroup.html b/docs/PopupActionGroup.html index 16cc248a..fd4f1a4c 100644 --- a/docs/PopupActionGroup.html +++ b/docs/PopupActionGroup.html @@ -65,7 +65,7 @@ @@ -244,13 +244,13 @@
Example
-
+
diff --git a/docs/PopupActionItem.html b/docs/PopupActionItem.html index 17e90abf..2fe89380 100644 --- a/docs/PopupActionItem.html +++ b/docs/PopupActionItem.html @@ -65,7 +65,7 @@ @@ -277,13 +277,13 @@
Example
-
+
diff --git a/docs/PopupActionLink.html b/docs/PopupActionLink.html index f14ce22e..a4ccc9de 100644 --- a/docs/PopupActionLink.html +++ b/docs/PopupActionLink.html @@ -65,7 +65,7 @@ @@ -312,13 +312,13 @@
Example
-
+
diff --git a/docs/PopupBase.html b/docs/PopupBase.html index 0fdc1ed9..e7d85d82 100644 --- a/docs/PopupBase.html +++ b/docs/PopupBase.html @@ -65,7 +65,7 @@ diff --git a/docs/PopupDataList.html b/docs/PopupDataList.html index 395987ef..0dbc5652 100644 --- a/docs/PopupDataList.html +++ b/docs/PopupDataList.html @@ -65,7 +65,7 @@ @@ -222,13 +222,13 @@
Example
-
+
diff --git a/docs/PopupDefaultInsert.html b/docs/PopupDefaultInsert.html index 8212ce73..fe37f0d0 100644 --- a/docs/PopupDefaultInsert.html +++ b/docs/PopupDefaultInsert.html @@ -65,7 +65,7 @@ @@ -103,7 +103,7 @@

- <PopupDefaultInsert actions features loading onClose onSelectFeature onSettingsClick propertiesFilter selectedIdx selectInteraction translations /> + <PopupDefaultInsert actions features loading onClose onSelectFeature onSettingsClick propertiesFilter selectedIdx selectInteraction translations onEdit />

@@ -245,6 +245,17 @@
PropTypes:
+ + onEdit + + func + + No + + () => false + + + @@ -290,7 +301,7 @@
PropTypes:

View Source - Popup/PopupInsert/PopupDefaultInsert.js, line 30 + Popup/PopupInsert/PopupDefaultInsert.js, line 31

diff --git a/docs/PopupDefaultPage.html b/docs/PopupDefaultPage.html index 3cb5cd65..fc153ad0 100644 --- a/docs/PopupDefaultPage.html +++ b/docs/PopupDefaultPage.html @@ -65,7 +65,7 @@ @@ -354,13 +354,13 @@
Example
-
+
diff --git a/docs/PopupPageLayout.html b/docs/PopupPageLayout.html index 60717120..1fb901cb 100644 --- a/docs/PopupPageLayout.html +++ b/docs/PopupPageLayout.html @@ -65,7 +65,7 @@ @@ -255,13 +255,13 @@
Example
-
+
diff --git a/docs/PopupPageLayoutChild.html b/docs/PopupPageLayoutChild.html index 1e8756de..2d7f3198 100644 --- a/docs/PopupPageLayoutChild.html +++ b/docs/PopupPageLayoutChild.html @@ -65,7 +65,7 @@ @@ -266,13 +266,13 @@
Example
-
+
diff --git a/docs/PopupTabs.html b/docs/PopupTabs.html index 175f7928..0e36c3d7 100644 --- a/docs/PopupTabs.html +++ b/docs/PopupTabs.html @@ -65,7 +65,7 @@ @@ -244,13 +244,13 @@
Example
-
+
diff --git a/docs/Popup_Popup.js.html b/docs/Popup_Popup.js.html index 14de1941..614168e7 100644 --- a/docs/Popup_Popup.js.html +++ b/docs/Popup_Popup.js.html @@ -67,7 +67,7 @@ @@ -246,7 +246,8 @@ actions={actions} features={features} loading={loading} - onClose={this.hidePopup} /> + onClose={this.hidePopup} + /> )} </PopupBase>, map.getTargetElement() diff --git a/docs/Popup_PopupActions_PopupActionCopyWkt_PopupActionCopyWkt.js.html b/docs/Popup_PopupActions_PopupActionCopyWkt_PopupActionCopyWkt.js.html index 7bb53c92..337bc14c 100644 --- a/docs/Popup_PopupActions_PopupActionCopyWkt_PopupActionCopyWkt.js.html +++ b/docs/Popup_PopupActions_PopupActionCopyWkt_PopupActionCopyWkt.js.html @@ -67,7 +67,7 @@ diff --git a/docs/Popup_PopupActions_PopupActionCopyWkt_utils.js.html b/docs/Popup_PopupActions_PopupActionCopyWkt_utils.js.html index c8b40fae..f0952188 100644 --- a/docs/Popup_PopupActions_PopupActionCopyWkt_utils.js.html +++ b/docs/Popup_PopupActions_PopupActionCopyWkt_utils.js.html @@ -67,7 +67,7 @@ diff --git a/docs/Popup_PopupActions_PopupActionCut_PopupActionCut.js.html b/docs/Popup_PopupActions_PopupActionCut_PopupActionCut.js.html index 83ca287b..16913a75 100644 --- a/docs/Popup_PopupActions_PopupActionCut_PopupActionCut.js.html +++ b/docs/Popup_PopupActions_PopupActionCut_PopupActionCut.js.html @@ -67,7 +67,7 @@ diff --git a/docs/Popup_PopupActions_PopupActionDuplicate_PopupActionDuplicate.js.html b/docs/Popup_PopupActions_PopupActionDuplicate_PopupActionDuplicate.js.html index e0a5fe74..d11ed642 100644 --- a/docs/Popup_PopupActions_PopupActionDuplicate_PopupActionDuplicate.js.html +++ b/docs/Popup_PopupActions_PopupActionDuplicate_PopupActionDuplicate.js.html @@ -67,7 +67,7 @@ diff --git a/docs/Popup_PopupActions_PopupActionGoogleMaps_PopupActionGoogleMaps.js.html b/docs/Popup_PopupActions_PopupActionGoogleMaps_PopupActionGoogleMaps.js.html index acb908dc..5027ecb2 100644 --- a/docs/Popup_PopupActions_PopupActionGoogleMaps_PopupActionGoogleMaps.js.html +++ b/docs/Popup_PopupActions_PopupActionGoogleMaps_PopupActionGoogleMaps.js.html @@ -67,7 +67,7 @@ diff --git a/docs/Popup_PopupActions_PopupActionRemove_PopupActionRemove.js.html b/docs/Popup_PopupActions_PopupActionRemove_PopupActionRemove.js.html index 33494572..07269e8a 100644 --- a/docs/Popup_PopupActions_PopupActionRemove_PopupActionRemove.js.html +++ b/docs/Popup_PopupActions_PopupActionRemove_PopupActionRemove.js.html @@ -67,7 +67,7 @@ diff --git a/docs/Popup_PopupActions_PopupActionZoomToExtent_PopupActionZoomToExtent.js.html b/docs/Popup_PopupActions_PopupActionZoomToExtent_PopupActionZoomToExtent.js.html index 303df53d..a39f207a 100644 --- a/docs/Popup_PopupActions_PopupActionZoomToExtent_PopupActionZoomToExtent.js.html +++ b/docs/Popup_PopupActions_PopupActionZoomToExtent_PopupActionZoomToExtent.js.html @@ -67,7 +67,7 @@ diff --git a/docs/Popup_PopupBase.js.html b/docs/Popup_PopupBase.js.html index 00908583..5ad6649d 100644 --- a/docs/Popup_PopupBase.js.html +++ b/docs/Popup_PopupBase.js.html @@ -67,7 +67,7 @@ diff --git a/docs/Popup_PopupInsert_PopupActionGroup_index.js.html b/docs/Popup_PopupInsert_PopupActionGroup_index.js.html index 1d9ac7e7..ae19fde5 100644 --- a/docs/Popup_PopupInsert_PopupActionGroup_index.js.html +++ b/docs/Popup_PopupInsert_PopupActionGroup_index.js.html @@ -67,7 +67,7 @@ diff --git a/docs/Popup_PopupInsert_PopupActionItem_index.js.html b/docs/Popup_PopupInsert_PopupActionItem_index.js.html index cf1802a0..eaa8f455 100644 --- a/docs/Popup_PopupInsert_PopupActionItem_index.js.html +++ b/docs/Popup_PopupInsert_PopupActionItem_index.js.html @@ -67,7 +67,7 @@ diff --git a/docs/Popup_PopupInsert_PopupActionLink_index.js.html b/docs/Popup_PopupInsert_PopupActionLink_index.js.html index 5058641d..aaba04e4 100644 --- a/docs/Popup_PopupInsert_PopupActionLink_index.js.html +++ b/docs/Popup_PopupInsert_PopupActionLink_index.js.html @@ -67,7 +67,7 @@ diff --git a/docs/Popup_PopupInsert_PopupDataList_index.js.html b/docs/Popup_PopupInsert_PopupDataList_index.js.html index e72ee1ae..d9f9a95e 100644 --- a/docs/Popup_PopupInsert_PopupDataList_index.js.html +++ b/docs/Popup_PopupInsert_PopupDataList_index.js.html @@ -67,7 +67,7 @@ diff --git a/docs/Popup_PopupInsert_PopupDefaultInsert.js.html b/docs/Popup_PopupInsert_PopupDefaultInsert.js.html index d540da14..b8566c98 100644 --- a/docs/Popup_PopupInsert_PopupDefaultInsert.js.html +++ b/docs/Popup_PopupInsert_PopupDefaultInsert.js.html @@ -67,7 +67,7 @@ @@ -91,6 +91,7 @@ import { PopupActionRemove } from 'Popup/PopupActions/PopupActionRemove' import { PopupActionDuplicate } from 'Popup/PopupActions/PopupActionDuplicate' import { PopupActionCut } from 'Popup/PopupActions/PopupActionCut' +import { PopupActionEdit } from 'Popup/PopupActions/PopupActionEdit' import { PopupActionZoomToExtent } from 'Popup/PopupActions/PopupActionZoomToExtent' import PopupDefaultPage from './PopupDefaultPage' import PopupPageLayout from './PopupPageLayout' @@ -114,7 +115,7 @@ super(props) this.state = { - selectedIdx: 0 + selectedIdx: 0, } } @@ -168,7 +169,7 @@ } render () { - const { actions, features, loading, onClose, onSettingsClick, propertiesFilter, translations } = this.props + const { actions, features, loading, onClose, onSettingsClick, propertiesFilter, translations, onEdit } = this.props const { selectedIdx } = this.state const getChildren = feature => { @@ -178,6 +179,7 @@ <PopupActionDuplicate key='dupe' />, <PopupActionRemove key='remove' />, <PopupActionGoogleMaps key='nav' />, + <PopupActionEdit key='edit' onEdit={onEdit} />, <PopupActionZoomToExtent key='zoom' />] if (!pointGeom) defaultActions = [...defaultActions, <PopupActionCut key='cut' />] @@ -220,7 +222,8 @@ onClose: () => {}, onSelectFeature: () => {}, propertiesFilter: sanitizeProperties, - translations: en + translations: en, + onEdit: () => false, } PopupDefaultInsert.propTypes = { @@ -250,7 +253,8 @@ '_ol_kit.PopupDefaultPage.details': PropTypes.string, '_ol_kit.PopupDefaultPage.actions': PropTypes.string, '_ol_kit.PopupDefaultPage.customize': PropTypes.string - }).isRequired + }).isRequired, + onEdit: PropTypes.func, } export default connectToContext(PopupDefaultInsert) diff --git a/docs/Popup_PopupInsert_PopupDefaultPage_index.js.html b/docs/Popup_PopupInsert_PopupDefaultPage_index.js.html index 056b1457..57742f29 100644 --- a/docs/Popup_PopupInsert_PopupDefaultPage_index.js.html +++ b/docs/Popup_PopupInsert_PopupDefaultPage_index.js.html @@ -67,7 +67,7 @@ diff --git a/docs/Popup_PopupInsert_PopupPageLayoutChild_index.js.html b/docs/Popup_PopupInsert_PopupPageLayoutChild_index.js.html index 6e103c51..951d65fa 100644 --- a/docs/Popup_PopupInsert_PopupPageLayoutChild_index.js.html +++ b/docs/Popup_PopupInsert_PopupPageLayoutChild_index.js.html @@ -67,7 +67,7 @@ diff --git a/docs/Popup_PopupInsert_PopupPageLayout_index.js.html b/docs/Popup_PopupInsert_PopupPageLayout_index.js.html index ac296874..a8c70b8d 100644 --- a/docs/Popup_PopupInsert_PopupPageLayout_index.js.html +++ b/docs/Popup_PopupInsert_PopupPageLayout_index.js.html @@ -67,7 +67,7 @@ diff --git a/docs/Popup_PopupInsert_PopupTabs_index.js.html b/docs/Popup_PopupInsert_PopupTabs_index.js.html index df13ff19..f26ad031 100644 --- a/docs/Popup_PopupInsert_PopupTabs_index.js.html +++ b/docs/Popup_PopupInsert_PopupTabs_index.js.html @@ -67,7 +67,7 @@ diff --git a/docs/Popup_PopupInsert__LoadingSpinner_index.js.html b/docs/Popup_PopupInsert__LoadingSpinner_index.js.html index 4a6a17ea..4efe097f 100644 --- a/docs/Popup_PopupInsert__LoadingSpinner_index.js.html +++ b/docs/Popup_PopupInsert__LoadingSpinner_index.js.html @@ -67,7 +67,7 @@ diff --git a/docs/Popup_utils.js.html b/docs/Popup_utils.js.html index de97c6bc..3d0aad4e 100644 --- a/docs/Popup_utils.js.html +++ b/docs/Popup_utils.js.html @@ -67,7 +67,7 @@ diff --git a/docs/ProjectMenu.html b/docs/ProjectMenu.html index 471cc7dd..9328444c 100644 --- a/docs/ProjectMenu.html +++ b/docs/ProjectMenu.html @@ -65,7 +65,7 @@ diff --git a/docs/Project_Project.js.html b/docs/Project_Project.js.html index c41b76e6..c7e484b4 100644 --- a/docs/Project_Project.js.html +++ b/docs/Project_Project.js.html @@ -67,7 +67,7 @@ diff --git a/docs/Project_utils.js.html b/docs/Project_utils.js.html index 81d32a34..cd96a34c 100644 --- a/docs/Project_utils.js.html +++ b/docs/Project_utils.js.html @@ -67,7 +67,7 @@ diff --git a/docs/Provider.html b/docs/Provider.html index b5e0ab71..22360445 100644 --- a/docs/Provider.html +++ b/docs/Provider.html @@ -65,7 +65,7 @@ @@ -105,7 +105,7 @@

- <Provider children contextProps map maps translations /> + <Provider children contextProps map maps translations editFeature />

@@ -192,6 +192,17 @@
PropTypes:
+ + editFeature + + object + + No + An Ol feature object that is being edited + + + + @@ -271,13 +282,13 @@
Example
-
+
diff --git a/docs/Provider_Provider.js.html b/docs/Provider_Provider.js.html index b9401aa4..05bcb748 100644 --- a/docs/Provider_Provider.js.html +++ b/docs/Provider_Provider.js.html @@ -67,7 +67,7 @@ @@ -117,6 +117,10 @@ this.setState({ mapContext }) } + addEditFeatureToContext = editFeature => { + this.setState({ editFeature }) + } + getContextValue = () => { const { contextProps, map: mapProp, maps: mapsProp, translations } = this.props const { mapContext } = this.state @@ -134,6 +138,8 @@ addMapToContext: this.addMapToContext, map, maps, + editFeature: this.state.editFeature, + addEditFeatureToContext: this.addEditFeatureToContext, persistedState: this.state, persistState: this.persistState, translations, @@ -170,7 +176,9 @@ /** Array of Openlayers map objects for components that support multi-map (this will override anything passed to map prop) */ maps: PropTypes.array, /** Object with key/value pairs for component translation strings */ - translations: PropTypes.object + translations: PropTypes.object, + /** An Ol feature object that is being edited */ + editFeature: PropTypes.object } export default Provider diff --git a/docs/Provider_utils.js.html b/docs/Provider_utils.js.html index 82d6df93..afc2f359 100644 --- a/docs/Provider_utils.js.html +++ b/docs/Provider_utils.js.html @@ -67,7 +67,7 @@ diff --git a/docs/TabbedPanel.html b/docs/TabbedPanel.html index 43a39556..d3e395f1 100644 --- a/docs/TabbedPanel.html +++ b/docs/TabbedPanel.html @@ -65,7 +65,7 @@ diff --git a/docs/TabbedPanel_TabbedPanel.js.html b/docs/TabbedPanel_TabbedPanel.js.html index 3098eca0..1fd06455 100644 --- a/docs/TabbedPanel_TabbedPanel.js.html +++ b/docs/TabbedPanel_TabbedPanel.js.html @@ -67,7 +67,7 @@ diff --git a/docs/TimeSlider.html b/docs/TimeSlider.html index 3a40a2c7..d3f19cce 100644 --- a/docs/TimeSlider.html +++ b/docs/TimeSlider.html @@ -65,7 +65,7 @@ diff --git a/docs/TimeSliderBase.html b/docs/TimeSliderBase.html index 7caa24e5..6418a816 100644 --- a/docs/TimeSliderBase.html +++ b/docs/TimeSliderBase.html @@ -65,7 +65,7 @@ diff --git a/docs/TimeSlider_TimeSlider.js.html b/docs/TimeSlider_TimeSlider.js.html index 0478275d..de263ba2 100644 --- a/docs/TimeSlider_TimeSlider.js.html +++ b/docs/TimeSlider_TimeSlider.js.html @@ -67,7 +67,7 @@ diff --git a/docs/TimeSlider_TimeSliderBase.js.html b/docs/TimeSlider_TimeSliderBase.js.html index 0df11517..d9c69454 100644 --- a/docs/TimeSlider_TimeSliderBase.js.html +++ b/docs/TimeSlider_TimeSliderBase.js.html @@ -67,7 +67,7 @@ diff --git a/docs/Toolbar_Toolbar.js.html b/docs/Toolbar_Toolbar.js.html index fb9c4adf..570ce941 100644 --- a/docs/Toolbar_Toolbar.js.html +++ b/docs/Toolbar_Toolbar.js.html @@ -67,7 +67,7 @@ diff --git a/docs/ZoomControls.html b/docs/ZoomControls.html index 7057602b..714ad3d1 100644 --- a/docs/ZoomControls.html +++ b/docs/ZoomControls.html @@ -65,7 +65,7 @@ diff --git a/docs/ZoomIn.html b/docs/ZoomIn.html index bc2daf13..09ef19fb 100644 --- a/docs/ZoomIn.html +++ b/docs/ZoomIn.html @@ -65,7 +65,7 @@ diff --git a/docs/ZoomOut.html b/docs/ZoomOut.html index 24effe31..c94fcfdd 100644 --- a/docs/ZoomOut.html +++ b/docs/ZoomOut.html @@ -65,7 +65,7 @@ diff --git a/docs/classes_VectorLayer.js.html b/docs/classes_VectorLayer.js.html index 682b1074..57b94390 100644 --- a/docs/classes_VectorLayer.js.html +++ b/docs/classes_VectorLayer.js.html @@ -67,7 +67,7 @@ diff --git a/docs/classes_VectorTileLayer.js.html b/docs/classes_VectorTileLayer.js.html index c6452ef3..928c1f10 100644 --- a/docs/classes_VectorTileLayer.js.html +++ b/docs/classes_VectorTileLayer.js.html @@ -67,7 +67,7 @@ diff --git a/docs/docs.html b/docs/docs.html index d559451b..3a522cf9 100644 --- a/docs/docs.html +++ b/docs/docs.html @@ -65,7 +65,7 @@ diff --git a/docs/entry.js b/docs/entry.js index 8cc2eb90..260cc986 100644 --- a/docs/entry.js +++ b/docs/entry.js @@ -97,140 +97,143 @@ reactComponents['DrawPoint'] = Component23; import Component24 from '../src/Draw/Polygon.js'; reactComponents['DrawPolygon'] = Component24; -import Component25 from '../src/GoogleDirections/GoogleDirections.js'; -reactComponents['GoogleDirections'] = Component25; +import Component25 from '../src/FeatureEditor/FeatureEditor.js'; +reactComponents['FeatureEditor'] = Component25; -import Component26 from '../src/GooglePlacesSearch/GooglePlacesSearch.js'; -reactComponents['GooglePlacesSearch'] = Component26; +import Component26 from '../src/GoogleDirections/GoogleDirections.js'; +reactComponents['GoogleDirections'] = Component26; -import Component27 from '../src/Heatmap/HeatmapControls.js'; -reactComponents['HeatmapControls'] = Component27; +import Component27 from '../src/GooglePlacesSearch/GooglePlacesSearch.js'; +reactComponents['GooglePlacesSearch'] = Component27; -import Component28 from '../src/LayerPanel/LayerPanel.js'; -reactComponents['LayerPanel'] = Component28; +import Component28 from '../src/Heatmap/HeatmapControls.js'; +reactComponents['HeatmapControls'] = Component28; -import Component29 from '../src/LayerPanel/LayerPanelActionDuplicate/index.js'; -reactComponents['LayerPanelActionDuplicate'] = Component29; +import Component29 from '../src/LayerPanel/LayerPanel.js'; +reactComponents['LayerPanel'] = Component29; -import Component30 from '../src/LayerPanel/LayerPanelActionExport/index.js'; -reactComponents['LayerPanelActionExport'] = Component30; +import Component30 from '../src/LayerPanel/LayerPanelActionDuplicate/index.js'; +reactComponents['LayerPanelActionDuplicate'] = Component30; -import Component31 from '../src/LayerPanel/LayerPanelActionExtent/index.js'; -reactComponents['LayerPanelActionExtent'] = Component31; +import Component31 from '../src/LayerPanel/LayerPanelActionExport/index.js'; +reactComponents['LayerPanelActionExport'] = Component31; -import Component32 from '../src/LayerPanel/LayerPanelActionHeatmap/index.js'; -reactComponents['LayerPanelActionHeatmap'] = Component32; +import Component32 from '../src/LayerPanel/LayerPanelActionExtent/index.js'; +reactComponents['LayerPanelActionExtent'] = Component32; -import Component33 from '../src/LayerPanel/LayerPanelActionImport/index.js'; -reactComponents['LayerPanelActionImport'] = Component33; +import Component33 from '../src/LayerPanel/LayerPanelActionHeatmap/index.js'; +reactComponents['LayerPanelActionHeatmap'] = Component33; -import Component34 from '../src/LayerPanel/LayerPanelActionMerge/index.js'; -reactComponents['LayerPanelActionMerge'] = Component34; +import Component34 from '../src/LayerPanel/LayerPanelActionImport/index.js'; +reactComponents['LayerPanelActionImport'] = Component34; -import Component35 from '../src/LayerPanel/LayerPanelActionOpacity/index.js'; -reactComponents['LayerPanelActionOpacity'] = Component35; +import Component35 from '../src/LayerPanel/LayerPanelActionMerge/index.js'; +reactComponents['LayerPanelActionMerge'] = Component35; -import Component36 from '../src/LayerPanel/LayerPanelActionRemove/index.js'; -reactComponents['LayerPanelActionRemove'] = Component36; +import Component36 from '../src/LayerPanel/LayerPanelActionOpacity/index.js'; +reactComponents['LayerPanelActionOpacity'] = Component36; -import Component37 from '../src/LayerPanel/LayerPanelActions/index.js'; -reactComponents['LayerPanelActions'] = Component37; +import Component37 from '../src/LayerPanel/LayerPanelActionRemove/index.js'; +reactComponents['LayerPanelActionRemove'] = Component37; -import Component38 from '../src/LayerPanel/LayerPanelBase/index.js'; -reactComponents['LayerPanelBase'] = Component38; +import Component38 from '../src/LayerPanel/LayerPanelActions/index.js'; +reactComponents['LayerPanelActions'] = Component38; -import Component39 from '../src/LayerPanel/LayerPanelCheckbox/index.js'; -reactComponents['LayerPanelCheckbox'] = Component39; +import Component39 from '../src/LayerPanel/LayerPanelBase/index.js'; +reactComponents['LayerPanelBase'] = Component39; -import Component40 from '../src/LayerPanel/LayerPanelContent/index.js'; -reactComponents['LayerPanelContent'] = Component40; +import Component40 from '../src/LayerPanel/LayerPanelCheckbox/index.js'; +reactComponents['LayerPanelCheckbox'] = Component40; -import Component41 from '../src/LayerPanel/LayerPanelHeader/index.js'; -reactComponents['LayerPanelHeader'] = Component41; +import Component41 from '../src/LayerPanel/LayerPanelContent/index.js'; +reactComponents['LayerPanelContent'] = Component41; -import Component42 from '../src/LayerPanel/LayerPanelLayersPage/index.js'; -reactComponents['LayerPanelLayersPage'] = Component42; +import Component42 from '../src/LayerPanel/LayerPanelHeader/index.js'; +reactComponents['LayerPanelHeader'] = Component42; -import Component43 from '../src/LayerPanel/LayerPanelList/index.js'; -reactComponents['LayerPanelList'] = Component43; +import Component43 from '../src/LayerPanel/LayerPanelLayersPage/index.js'; +reactComponents['LayerPanelLayersPage'] = Component43; -import Component44 from '../src/LayerPanel/LayerPanelListItem/index.js'; -reactComponents['LayerPanelListItem'] = Component44; +import Component44 from '../src/LayerPanel/LayerPanelList/index.js'; +reactComponents['LayerPanelList'] = Component44; -import Component45 from '../src/LayerPanel/LayerPanelMenu/index.js'; -reactComponents['LayerPanelMenu'] = Component45; +import Component45 from '../src/LayerPanel/LayerPanelListItem/index.js'; +reactComponents['LayerPanelListItem'] = Component45; -import Component46 from '../src/LayerPanel/LayerPanelPage/index.js'; -reactComponents['LayerPanelPage'] = Component46; +import Component46 from '../src/LayerPanel/LayerPanelMenu/index.js'; +reactComponents['LayerPanelMenu'] = Component46; -import Component47 from '../src/LayerStyler/LayerStyler.js'; -reactComponents['LayerStyler'] = Component47; +import Component47 from '../src/LayerPanel/LayerPanelPage/index.js'; +reactComponents['LayerPanelPage'] = Component47; -import Component48 from '../src/Map/Map.js'; -reactComponents['Map'] = Component48; +import Component48 from '../src/LayerStyler/LayerStyler.js'; +reactComponents['LayerStyler'] = Component48; -import Component49 from '../src/MultiMapManager/MultiMapManager.js'; -reactComponents['MultiMapManager'] = Component49; +import Component49 from '../src/Map/Map.js'; +reactComponents['Map'] = Component49; -import Component50 from '../src/Popup/Popup.js'; -reactComponents['Popup'] = Component50; +import Component50 from '../src/MultiMapManager/MultiMapManager.js'; +reactComponents['MultiMapManager'] = Component50; -import Component51 from '../src/Popup/PopupActions/PopupActionCopyWkt/PopupActionCopyWkt.js'; -reactComponents['PopupActionCopyWkt'] = Component51; +import Component51 from '../src/Popup/Popup.js'; +reactComponents['Popup'] = Component51; -import Component52 from '../src/Popup/PopupActions/PopupActionGoogleMaps/PopupActionGoogleMaps.js'; -reactComponents['PopupActionGoogleMaps'] = Component52; +import Component52 from '../src/Popup/PopupActions/PopupActionCopyWkt/PopupActionCopyWkt.js'; +reactComponents['PopupActionCopyWkt'] = Component52; -import Component53 from '../src/Popup/PopupInsert/PopupActionGroup/index.js'; -reactComponents['PopupActionGroup'] = Component53; +import Component53 from '../src/Popup/PopupActions/PopupActionGoogleMaps/PopupActionGoogleMaps.js'; +reactComponents['PopupActionGoogleMaps'] = Component53; -import Component54 from '../src/Popup/PopupInsert/PopupActionItem/index.js'; -reactComponents['PopupActionItem'] = Component54; +import Component54 from '../src/Popup/PopupInsert/PopupActionGroup/index.js'; +reactComponents['PopupActionGroup'] = Component54; -import Component55 from '../src/Popup/PopupInsert/PopupActionLink/index.js'; -reactComponents['PopupActionLink'] = Component55; +import Component55 from '../src/Popup/PopupInsert/PopupActionItem/index.js'; +reactComponents['PopupActionItem'] = Component55; -import Component56 from '../src/Popup/PopupBase.js'; -reactComponents['PopupBase'] = Component56; +import Component56 from '../src/Popup/PopupInsert/PopupActionLink/index.js'; +reactComponents['PopupActionLink'] = Component56; -import Component57 from '../src/Popup/PopupInsert/PopupDataList/index.js'; -reactComponents['PopupDataList'] = Component57; +import Component57 from '../src/Popup/PopupBase.js'; +reactComponents['PopupBase'] = Component57; -import Component58 from '../src/Popup/PopupInsert/PopupDefaultInsert.js'; -reactComponents['PopupDefaultInsert'] = Component58; +import Component58 from '../src/Popup/PopupInsert/PopupDataList/index.js'; +reactComponents['PopupDataList'] = Component58; -import Component59 from '../src/Popup/PopupInsert/PopupDefaultPage/index.js'; -reactComponents['PopupDefaultPage'] = Component59; +import Component59 from '../src/Popup/PopupInsert/PopupDefaultInsert.js'; +reactComponents['PopupDefaultInsert'] = Component59; -import Component60 from '../src/Popup/PopupInsert/PopupPageLayout/index.js'; -reactComponents['PopupPageLayout'] = Component60; +import Component60 from '../src/Popup/PopupInsert/PopupDefaultPage/index.js'; +reactComponents['PopupDefaultPage'] = Component60; -import Component61 from '../src/Popup/PopupInsert/PopupPageLayoutChild/index.js'; -reactComponents['PopupPageLayoutChild'] = Component61; +import Component61 from '../src/Popup/PopupInsert/PopupPageLayout/index.js'; +reactComponents['PopupPageLayout'] = Component61; -import Component62 from '../src/Popup/PopupInsert/PopupTabs/index.js'; -reactComponents['PopupTabs'] = Component62; +import Component62 from '../src/Popup/PopupInsert/PopupPageLayoutChild/index.js'; +reactComponents['PopupPageLayoutChild'] = Component62; -import Component63 from '../src/Project/Project.js'; -reactComponents['ProjectMenu'] = Component63; +import Component63 from '../src/Popup/PopupInsert/PopupTabs/index.js'; +reactComponents['PopupTabs'] = Component63; -import Component64 from '../src/Provider/Provider.js'; -reactComponents['Provider'] = Component64; +import Component64 from '../src/Project/Project.js'; +reactComponents['ProjectMenu'] = Component64; -import Component65 from '../src/TabbedPanel/TabbedPanel.js'; -reactComponents['TabbedPanel'] = Component65; +import Component65 from '../src/Provider/Provider.js'; +reactComponents['Provider'] = Component65; -import Component66 from '../src/TimeSlider/TimeSlider.js'; -reactComponents['TimeSlider'] = Component66; +import Component66 from '../src/TabbedPanel/TabbedPanel.js'; +reactComponents['TabbedPanel'] = Component66; -import Component67 from '../src/TimeSlider/TimeSliderBase.js'; -reactComponents['TimeSliderBase'] = Component67; +import Component67 from '../src/TimeSlider/TimeSlider.js'; +reactComponents['TimeSlider'] = Component67; -import Component68 from '../src/Controls/ZoomControls.js'; -reactComponents['ZoomControls'] = Component68; +import Component68 from '../src/TimeSlider/TimeSliderBase.js'; +reactComponents['TimeSliderBase'] = Component68; -import Component69 from '../src/Controls/ZoomIn.js'; -reactComponents['ZoomIn'] = Component69; +import Component69 from '../src/Controls/ZoomControls.js'; +reactComponents['ZoomControls'] = Component69; -import Component70 from '../src/Controls/ZoomOut.js'; -reactComponents['ZoomOut'] = Component70; \ No newline at end of file +import Component70 from '../src/Controls/ZoomIn.js'; +reactComponents['ZoomIn'] = Component70; + +import Component71 from '../src/Controls/ZoomOut.js'; +reactComponents['ZoomOut'] = Component71; \ No newline at end of file diff --git a/docs/global.html b/docs/global.html index 68f36798..c6912f3a 100644 --- a/docs/global.html +++ b/docs/global.html @@ -65,7 +65,7 @@ @@ -1954,7 +1954,7 @@
Parameters:

View Source - LayerPanel/LayerPanelActionImport/utils.js, line 48 + LayerPanel/LayerPanelActionImport/utils.js, line 47

@@ -2118,9 +2118,6 @@
Parameters:
-
Since:
-
  • 6.5.0
- @@ -5180,6 +5177,228 @@
Parameters:
+ + + +
+ + + +

+ # + + + + immediateEditStyle(opts, feature, resolution) → {object} + + +

+ + + + +
+ Style an ol/Feature with orange circle vertices, a blue outline, an area label, and a perimeter length label. Can be used with individual features as a style function or call it directly to get a style object for use with `vectorContext.drawFeature` +
+ + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
opts + + +object + + + + The config object
feature + + +ol/Feature + + + + The feature you want to style
resolution + + +number + + + + the resolution of the map
+ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ View Source + + FeatureEditor/styles.js, line 223 + +

+ +
+ + + + + + + + + + + + + + + + + + +
+
+
+ + + +
+ +
The style object for the passed feature
+ + +
+ + +object + + +
+ +
+ + +
+
+ + +
@@ -6825,6 +7044,228 @@
Parameters:
+
+
+
+ + + +
+ +
The style object for the passed feature
+ + +
+ + +object + + +
+ +
+ + +
+
+ + + + + + +
+ + + +

+ # + + + + styleText(opts, feature, resolution) → {object} + + +

+ + + + +
+ Style ol/Features +
+ + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
opts + + +object + + + + The config object
feature + + +ol/Feature + + + + The feature you want to style
resolution + + +number + + + + the resolution of the map
+ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ View Source + + FeatureEditor/styles.js, line 32 + +

+ +
+ + + + + + + + + + + + + + + + + +
diff --git a/docs/tutorial-Getting Started.html b/docs/tutorial-Getting Started.html index dcd4f4ca..0cd73264 100644 --- a/docs/tutorial-Getting Started.html +++ b/docs/tutorial-Getting Started.html @@ -65,7 +65,7 @@ diff --git a/docs/tutorial-LayerPanel_.html b/docs/tutorial-LayerPanel_.html index 9d726a36..0eca536a 100644 --- a/docs/tutorial-LayerPanel_.html +++ b/docs/tutorial-LayerPanel_.html @@ -65,7 +65,7 @@ diff --git a/docs/tutorial-Popup_.html b/docs/tutorial-Popup_.html index b91285ad..9cec5198 100644 --- a/docs/tutorial-Popup_.html +++ b/docs/tutorial-Popup_.html @@ -65,7 +65,7 @@ diff --git a/docs/tutorial-TimeSlider_.html b/docs/tutorial-TimeSlider_.html index 0fce7738..a4a09ffc 100644 --- a/docs/tutorial-TimeSlider_.html +++ b/docs/tutorial-TimeSlider_.html @@ -65,7 +65,7 @@ diff --git a/docs/tutorial-connectToContext.html b/docs/tutorial-connectToContext.html index 4651740d..5eefceab 100644 --- a/docs/tutorial-connectToContext.html +++ b/docs/tutorial-connectToContext.html @@ -65,7 +65,7 @@ diff --git a/docs/tutorial-contextProps.html b/docs/tutorial-contextProps.html index 78c8916d..7411c3ba 100644 --- a/docs/tutorial-contextProps.html +++ b/docs/tutorial-contextProps.html @@ -65,7 +65,7 @@ diff --git a/docs/tutorial-i18n Support.html b/docs/tutorial-i18n Support.html index 6f3f601e..c7e43f27 100644 --- a/docs/tutorial-i18n Support.html +++ b/docs/tutorial-i18n Support.html @@ -65,7 +65,7 @@ diff --git a/src/FeatureEditor/FeatureEditor.js b/src/FeatureEditor/FeatureEditor.js index 7690c946..b47d038c 100644 --- a/src/FeatureEditor/FeatureEditor.js +++ b/src/FeatureEditor/FeatureEditor.js @@ -51,6 +51,12 @@ const RightCard = withStyles(() => ({ } }))(Card) +/** + * A component to edit geometries + * @component + * @category FeatureEditor + * @since 1.16.0 + */ class FeatureEditor extends Component { constructor (props) { super(props) From 224aa53e48fa95840174143768ed2e9de238972b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jake=20St=C3=A4zrad?= Date: Wed, 27 Oct 2021 14:21:17 -0500 Subject: [PATCH 13/13] update snapshot to 1.16.0 --- src/Draw/__snapshots__/DrawContainer.test.js.snap | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Draw/__snapshots__/DrawContainer.test.js.snap b/src/Draw/__snapshots__/DrawContainer.test.js.snap index 23cc662f..b2e4181d 100644 --- a/src/Draw/__snapshots__/DrawContainer.test.js.snap +++ b/src/Draw/__snapshots__/DrawContainer.test.js.snap @@ -18,7 +18,7 @@ exports[` should render a basic prebuilt DrawContainer componen class=\\"sc-gtssRu fBppYk\\" href=\\"https://ol-kit.com/\\" target=\\"_blank\\" - title=\\"Powered by ol-kit v1.15.0\\" + title=\\"Powered by ol-kit v1.16.0\\" >