diff --git a/app/javascript/components/MapEditor/index.js b/app/javascript/components/MapEditor/index.js index d98fbdc13..7d3921fc7 100644 --- a/app/javascript/components/MapEditor/index.js +++ b/app/javascript/components/MapEditor/index.js @@ -1,69 +1,102 @@ -import React, { useState, useRef, useEffect } from 'react'; +import React, { useState, useCallback, useRef } from 'react'; import PropTypes from 'prop-types'; import mapboxgl from 'mapbox-gl'; import ReactMapboxGl, { GeoJSONLayer, ZoomControl } from 'react-mapbox-gl'; import DrawControl from 'react-mapbox-gl-draw'; -import SwitchMapStyle from './SwitchMapStyle'; -import SearchInput from './SearchInput'; -import { getJSON, ajax } from '@utils'; import { gpx, kml } from '@tmcw/togeojson/dist/togeojson.es.js'; -import ortho from '../MapStyles/ortho.json'; -import orthoCadastre from '../MapStyles/orthoCadastre.json'; -import vector from '../MapStyles/vector.json'; -import vectorCadastre from '../MapStyles/vectorCadastre.json'; -import { polygonCadastresFill, polygonCadastresLine } from './utils'; import '@mapbox/mapbox-gl-draw/dist/mapbox-gl-draw.css'; +import { getJSON, ajax, fire } from '@utils'; + +import SwitchMapStyle from './SwitchMapStyle'; +import { getMapStyle } from '../MapStyles'; + +import SearchInput from './SearchInput'; +import { polygonCadastresFill, polygonCadastresLine } from './utils'; +import { + noop, + filterFeatureCollection, + fitBounds, + generateId, + useEvent, + findFeature +} from '../shared/map'; + const Map = ReactMapboxGl({}); -function filterFeatureCollection(featureCollection, source) { - return { - type: 'FeatureCollection', - features: featureCollection.features.filter( - (feature) => feature.properties.source === source - ) - }; -} - -function noop() {} - function MapEditor({ featureCollection, url, preview, hasCadastres }) { const drawControl = useRef(null); + const [currentMap, setCurrentMap] = useState(null); + const [style, setStyle] = useState('ortho'); const [coords, setCoords] = useState([1.7, 46.9]); const [zoom, setZoom] = useState([5]); - const [currentMap, setCurrentMap] = useState({}); const [bbox, setBbox] = useState(featureCollection.bbox); const [importInputs, setImportInputs] = useState([]); - let mapStyle = style === 'ortho' ? ortho : vector; + const [cadastresFeatureCollection, setCadastresFeatureCollection] = useState( + filterFeatureCollection(featureCollection, 'cadastre') + ); + const mapStyle = getMapStyle(style, hasCadastres); - if (hasCadastres) { - mapStyle = style === 'ortho' ? orthoCadastre : vectorCadastre; - } - - const cadastresFeatureCollection = filterFeatureCollection( - featureCollection, - 'cadastre' + const onFeatureFocus = useCallback( + ({ detail }) => { + const { id } = detail; + const featureCollection = drawControl.current.draw.getAll(); + const feature = findFeature(featureCollection, id); + if (feature) { + fitBounds(currentMap, feature); + } + }, + [currentMap, drawControl.current] ); - function updateFeaturesList(features) { - const cadastres = features.find( - ({ geometry }) => geometry.type === 'Polygon' + const onFeatureUpdate = useCallback( + async ({ detail }) => { + const { id, properties } = detail; + const featureCollection = drawControl.current.draw.getAll(); + const feature = findFeature(featureCollection, id); + + if (feature) { + getJSON(`${url}/${id}`, { feature: { properties } }, 'patch'); + } + }, + [url, drawControl.current] + ); + + const updateFeaturesList = useCallback( + async (features) => { + const cadastres = features.find( + ({ geometry }) => geometry.type === 'Polygon' + ); + await ajax({ + url, + type: 'get', + data: cadastres ? 'cadastres=update' : '' + }); + fire(document, 'ds:page:update'); + }, + [url] + ); + + const onCadastresUpdate = useCallback(({ detail }) => { + setCadastresFeatureCollection( + filterFeatureCollection(detail.featureCollection, 'cadastre') ); - ajax({ url, type: 'get', data: cadastres ? 'cadastres=update' : '' }); - } + }, []); + + useEvent('map:feature:focus', onFeatureFocus); + useEvent('map:feature:update', onFeatureUpdate); + useEvent('cadastres:update', onCadastresUpdate); function setFeatureId(lid, feature) { const draw = drawControl.current.draw; draw.setFeatureProperty(lid, 'id', feature.properties.id); } - const generateId = () => Math.random().toString(20).substr(2, 6); - - const updateImportInputs = (inputs, inputId) => { + function updateImportInputs(inputs, inputId) { const updatedInputs = inputs.filter((input) => input.id !== inputId); setImportInputs(updatedInputs); - }; + } async function onDrawCreate({ features }) { for (const feature of features) { @@ -92,23 +125,13 @@ function MapEditor({ featureCollection, url, preview, hasCadastres }) { updateFeaturesList(features); } - const onMapLoad = (map) => { + function onMapLoad(map) { setCurrentMap(map); drawControl.current.draw.set( filterFeatureCollection(featureCollection, 'selection_utilisateur') ); - }; - - const onCadastresUpdate = (evt) => { - if (currentMap) { - currentMap - .getSource('cadastres-layer') - .setData( - filterFeatureCollection(evt.detail.featureCollection, 'cadastre') - ); - } - }; + } const onFileImport = (e, inputId) => { const isGpxFile = e.target.files[0].name.includes('.gpx'); @@ -190,11 +213,6 @@ function MapEditor({ featureCollection, url, preview, hasCadastres }) { updateImportInputs(inputs, inputId); }; - useEffect(() => { - addEventListener('cadastres:update', onCadastresUpdate); - return () => removeEventListener('cadastres:update', onCadastresUpdate); - }); - if (!mapboxgl.supported()) { return (
diff --git a/app/javascript/components/MapReader/index.js b/app/javascript/components/MapReader/index.js index c2d4e7d1c..145007b0a 100644 --- a/app/javascript/components/MapReader/index.js +++ b/app/javascript/components/MapReader/index.js @@ -1,25 +1,100 @@ -import React, { useState } from 'react'; +import React, { useState, useCallback, useMemo } from 'react'; import ReactMapboxGl, { ZoomControl, GeoJSONLayer } from 'react-mapbox-gl'; -import mapboxgl from 'mapbox-gl'; -import SwitchMapStyle from './SwitchMapStyle'; -import ortho from '../MapStyles/ortho.json'; -import orthoCadastre from '../MapStyles/orthoCadastre.json'; -import vector from '../MapStyles/vector.json'; -import vectorCadastre from '../MapStyles/vectorCadastre.json'; +import mapboxgl, { Popup } from 'mapbox-gl'; import PropTypes from 'prop-types'; +import SwitchMapStyle from './SwitchMapStyle'; +import { getMapStyle } from '../MapStyles'; + +import { + filterFeatureCollection, + filterFeatureCollectionByGeometryType, + useEvent, + findFeature, + fitBounds, + getCenter +} from '../shared/map'; + const Map = ReactMapboxGl({}); const MapReader = ({ featureCollection }) => { + const [currentMap, setCurrentMap] = useState(null); const [style, setStyle] = useState('ortho'); - const hasCadastres = featureCollection.features.find( - (feature) => feature.properties.source === 'cadastre' + const cadastresFeatureCollection = useMemo( + () => filterFeatureCollection(featureCollection, 'cadastre'), + [featureCollection] + ); + const selectionsUtilisateurFeatureCollection = useMemo( + () => filterFeatureCollection(featureCollection, 'selection_utilisateur'), + [featureCollection] + ); + const selectionsLineFeatureCollection = useMemo( + () => + filterFeatureCollectionByGeometryType( + selectionsUtilisateurFeatureCollection, + 'LineString' + ), + [selectionsUtilisateurFeatureCollection] + ); + const selectionsPolygonFeatureCollection = useMemo( + () => + filterFeatureCollectionByGeometryType( + selectionsUtilisateurFeatureCollection, + 'Polygon' + ), + [selectionsUtilisateurFeatureCollection] + ); + const selectionsPointFeatureCollection = useMemo( + () => + filterFeatureCollectionByGeometryType( + selectionsUtilisateurFeatureCollection, + 'Point' + ), + [selectionsUtilisateurFeatureCollection] + ); + const mapStyle = useMemo( + () => getMapStyle(style, cadastresFeatureCollection.length), + [style, cadastresFeatureCollection] + ); + const popup = useMemo( + () => + new Popup({ + closeButton: false, + closeOnClick: false + }) ); - let mapStyle = style === 'ortho' ? ortho : vector; - if (hasCadastres) { - mapStyle = style === 'ortho' ? orthoCadastre : vectorCadastre; - } + const onMouseEnter = useCallback( + (event) => { + const feature = event.features[0]; + if (feature.properties && feature.properties.description) { + const coordinates = getCenter(feature.geometry, event.lngLat); + const description = feature.properties.description; + currentMap.getCanvas().style.cursor = 'pointer'; + popup.setLngLat(coordinates).setHTML(description).addTo(currentMap); + } else { + popup.remove(); + } + }, + [currentMap, popup] + ); + + const onMouseLeave = useCallback(() => { + currentMap.getCanvas().style.cursor = ''; + popup.remove(); + }, [currentMap, popup]); + + const onFeatureFocus = useCallback( + ({ detail }) => { + const feature = findFeature(featureCollection, detail.id); + if (feature) { + fitBounds(currentMap, feature); + } + }, + [currentMap, featureCollection] + ); + + useEvent('map:feature:focus', onFeatureFocus); const [a1, a2, b1, b2] = featureCollection.bbox; const boundData = [ @@ -27,26 +102,6 @@ const MapReader = ({ featureCollection }) => { [b1, b2] ]; - const cadastresFeatureCollection = { - type: 'FeatureCollection', - features: [] - }; - - const selectionsLineFeatureCollection = { - type: 'FeatureCollection', - features: [] - }; - - const selectionsPolygonFeatureCollection = { - type: 'FeatureCollection', - features: [] - }; - - const selectionsPointFeatureCollection = { - type: 'FeatureCollection', - features: [] - }; - const polygonSelectionFill = { 'fill-color': '#EC3323', 'fill-opacity': 0.5 @@ -77,25 +132,8 @@ const MapReader = ({ featureCollection }) => { 'line-dasharray': [1, 1] }; - for (let feature of featureCollection.features) { - switch (feature.properties.source) { - case 'selection_utilisateur': - switch (feature.geometry.type) { - case 'LineString': - selectionsLineFeatureCollection.features.push(feature); - break; - case 'Polygon': - selectionsPolygonFeatureCollection.features.push(feature); - break; - case 'Point': - selectionsPointFeatureCollection.features.push(feature); - break; - } - break; - case 'cadastre': - cadastresFeatureCollection.features.push(feature); - break; - } + function onMapLoad(map) { + setCurrentMap(map); } if (!mapboxgl.supported()) { @@ -110,6 +148,7 @@ const MapReader = ({ featureCollection }) => { return (