From ba37afd97aa36d8d2eaf5de2e277113acd806a67 Mon Sep 17 00:00:00 2001 From: stdavis Date: Wed, 20 Nov 2024 15:00:32 -0700 Subject: [PATCH] feat: add buffered point and polyline options to shape filter Closes #697 --- .../search-wizard/filters/Buffer.jsx | 47 -------- .../search-wizard/filters/Buffer.tsx | 64 +++++++++++ .../search-wizard/filters/Shape.jsx | 73 ------------ .../search-wizard/filters/Shape.tsx | 106 ++++++++++++++++++ .../search-wizard/filters/StreetAddress.jsx | 2 +- src/index.css | 8 ++ 6 files changed, 179 insertions(+), 121 deletions(-) delete mode 100644 src/components/search-wizard/filters/Buffer.jsx create mode 100644 src/components/search-wizard/filters/Buffer.tsx delete mode 100644 src/components/search-wizard/filters/Shape.jsx create mode 100644 src/components/search-wizard/filters/Shape.tsx diff --git a/src/components/search-wizard/filters/Buffer.jsx b/src/components/search-wizard/filters/Buffer.jsx deleted file mode 100644 index 0483c7da..00000000 --- a/src/components/search-wizard/filters/Buffer.jsx +++ /dev/null @@ -1,47 +0,0 @@ -import { buffer } from '@arcgis/core/geometry/geometryEngine'; -import { isLoaded, load, project } from '@arcgis/core/geometry/projection'; -import PropTypes from 'prop-types'; -import { useEffect, useState } from 'react'; -import Input from '../../../utah-design-system/Input'; - -export default function Buffer({ className, onChange, inputGeometry }) { - const [bufferMiles, setBufferMiles] = useState(0.1); - - useEffect(() => { - const giddyUp = async () => { - if (inputGeometry) { - let geometry = inputGeometry.clone(); - if (geometry.spatialReference.wkid === 4326) { - if (!isLoaded()) { - await load(); - } - geometry = await project(geometry, { wkid: 26912 }); - } - onChange(buffer(geometry, bufferMiles, 'miles')); - } else { - onChange(null); - } - }; - giddyUp(); - }, [bufferMiles, inputGeometry, onChange]); - - const invalidBuffer = bufferMiles < 0.1; - - return ( - - ); -} - -Buffer.propTypes = { - className: PropTypes.string, - onChange: PropTypes.func.isRequired, - inputGeometry: PropTypes.object, -}; diff --git a/src/components/search-wizard/filters/Buffer.tsx b/src/components/search-wizard/filters/Buffer.tsx new file mode 100644 index 00000000..6511002a --- /dev/null +++ b/src/components/search-wizard/filters/Buffer.tsx @@ -0,0 +1,64 @@ +import { buffer } from '@arcgis/core/geometry/geometryEngine'; +import { isLoaded, load, project } from '@arcgis/core/geometry/projection'; +import PropTypes from 'prop-types'; +import { useEffect, useState } from 'react'; +import Input from '../../../utah-design-system/Input'; +import Geometry from '@arcgis/core/geometry/Geometry'; + +type BufferProps = { + className?: string; + // eslint-disable-next-line no-unused-vars + onChange: (value: Geometry) => void; + inputGeometry: Geometry | null; + allowZero?: boolean; +}; +export default function Buffer({ + className, + onChange, + inputGeometry, + allowZero, +}: BufferProps) { + const [bufferMiles, setBufferMiles] = useState(allowZero ? 0 : 0.1); + + useEffect(() => { + const giddyUp = async () => { + if (inputGeometry) { + let geometry = inputGeometry.clone(); + if (allowZero && bufferMiles === 0) { + onChange(geometry); + } else { + if (geometry.spatialReference.wkid === 4326) { + if (!isLoaded()) { + await load(); + } + geometry = (await project(geometry, { wkid: 26912 })) as Geometry; + } + onChange(buffer(geometry, bufferMiles, 'miles') as Geometry); + } + } else { + onChange(null); + } + }; + giddyUp(); + }, [allowZero, bufferMiles, inputGeometry, onChange]); + + const invalidBuffer = allowZero ? false : bufferMiles < 0.1; + + return ( + + ); +} + +Buffer.propTypes = { + className: PropTypes.string, + onChange: PropTypes.func.isRequired, + inputGeometry: PropTypes.object, +}; diff --git a/src/components/search-wizard/filters/Shape.jsx b/src/components/search-wizard/filters/Shape.jsx deleted file mode 100644 index 2b9e7456..00000000 --- a/src/components/search-wizard/filters/Shape.jsx +++ /dev/null @@ -1,73 +0,0 @@ -import GraphicsLayer from '@arcgis/core/layers/GraphicsLayer'; -import Sketch from '@arcgis/core/widgets/Sketch'; -import PropTypes from 'prop-types'; -import { useEffect } from 'react'; -import useMap from '../../../contexts/useMap'; - -export default function Shape({ send }) { - const { mapView } = useMap(); - - useEffect(() => { - if (!mapView) return; - - const sketch = new Sketch({ - availableCreateTools: ['polygon', 'rectangle', 'circle'], - creationMode: 'single', - layer: new GraphicsLayer(), - layout: 'vertical', - view: mapView, - visibleElements: { - selectionTools: { - 'rectangle-selection': false, - 'lasso-selection': false, - }, - settingsMenu: false, - }, - }); - - sketch.create('polygon'); - - sketch.on('create', (event) => { - if (event.state === 'complete') { - send({ - type: 'SET_FILTER', - filter: { - geometry: event.graphic.geometry, - name: 'User-drawn Shape', - }, - }); - } - }); - - mapView.ui.add(sketch, 'top-right'); - - send({ - type: 'SET_FILTER', - filter: { - geometry: null, - name: 'Shape', - }, - }); - - return () => { - if (sketch) { - mapView.ui.remove(sketch); - sketch.destroy(); - } - }; - }, [mapView, send]); - - return ( - <> -

Use the toolbar on the map to draw a shape.

-

Click once to create a new vertex.

-

Double-click to finish the shape.

- {/* buffer to make sure user can scroll far enough to see the entire select filter type dropdown */} -
- - ); -} - -Shape.propTypes = { - send: PropTypes.func.isRequired, -}; diff --git a/src/components/search-wizard/filters/Shape.tsx b/src/components/search-wizard/filters/Shape.tsx new file mode 100644 index 00000000..67b9753c --- /dev/null +++ b/src/components/search-wizard/filters/Shape.tsx @@ -0,0 +1,106 @@ +import GraphicsLayer from '@arcgis/core/layers/GraphicsLayer'; +import Sketch from '@arcgis/core/widgets/Sketch'; +import PropTypes from 'prop-types'; +import { useEffect, useState } from 'react'; +import useMap from '../../../contexts/useMap'; +import Buffer from './Buffer'; +import Geometry from '@arcgis/core/geometry/Geometry'; + +type ShapeProps = { + // eslint-disable-next-line no-unused-vars + send: (value: { + type: string; + filter?: { + geometry: Geometry | null; + name: string; + }; + }) => void; +}; + +export default function Shape({ send }: ShapeProps) { + const { mapView } = useMap(); + const [bufferGeometry, setBufferGeometry] = useState(null); + const [sketchGeometry, setSketchGeometry] = useState(null); + + useEffect(() => { + if (!mapView) return; + + const layer = new GraphicsLayer(); + mapView.map.add(layer); + const sketch = new Sketch({ + availableCreateTools: [ + 'polygon', + 'rectangle', + 'circle', + 'point', + 'polyline', + ], + creationMode: 'single', + layer, + layout: 'vertical', + view: mapView, + visibleElements: { + selectionTools: { + 'rectangle-selection': false, + 'lasso-selection': false, + }, + settingsMenu: false, + }, + }); + + sketch.create('polygon'); + + sketch.on('create', (event) => { + if (event.state === 'start') { + layer.removeAll(); + } else if (event.state === 'complete') { + setSketchGeometry(event.graphic.geometry); + } + }); + + mapView.ui.add(sketch, 'top-right'); + + return () => { + if (sketch) { + sketch.destroy(); + } + if (sketch && mapView?.ui) { + mapView.ui.remove(sketch); + } + if (layer && mapView.map) { + mapView.map.remove(layer); + } + }; + }, [mapView, send]); + + useEffect(() => { + send({ + type: 'SET_FILTER', + filter: { + geometry: bufferGeometry, + name: 'User-drawn Shape', + }, + }); + }, [bufferGeometry, send]); + + const bufferGeometryTypes = ['point', 'polyline']; + + return ( + <> +

Use the toolbar on the map to draw a shape.

+

Click once to create a new vertex.

+

Double-click to finish the shape.

+ + {/* buffer to make sure user can scroll far enough to see the entire select filter type dropdown */} +
+ + ); +} + +Shape.propTypes = { + send: PropTypes.func.isRequired, +}; diff --git a/src/components/search-wizard/filters/StreetAddress.jsx b/src/components/search-wizard/filters/StreetAddress.jsx index 7bf7c910..81988987 100644 --- a/src/components/search-wizard/filters/StreetAddress.jsx +++ b/src/components/search-wizard/filters/StreetAddress.jsx @@ -4,7 +4,7 @@ import appConfig from '../../../app-config.js'; import Sherlock, { LocatorSuggestProvider, } from '../../../utah-design-system/Sherlock.jsx'; -import Buffer from './Buffer.jsx'; +import Buffer from './Buffer'; export default function StreetAddress({ send }) { const [sherlockConfig, setSherlockConfig] = useState(null); diff --git a/src/index.css b/src/index.css index 4db4691d..3b8ba2f6 100644 --- a/src/index.css +++ b/src/index.css @@ -47,3 +47,11 @@ body { .esri-view .esri-view-surface--inset-outline:focus::after { @apply outline-none; } + +/* make sketch toolbar look nicer */ +.esri-sketch__panel .esri-sketch__section { + margin-left: 0; + margin-right: 0; + padding-top: 0; + padding-bottom: 0; +}