From 21367f4e75a884c082987f72f8ea279b5adb3fcd Mon Sep 17 00:00:00 2001 From: Sujit Date: Sun, 28 Jul 2024 21:28:31 +0545 Subject: [PATCH 01/10] feat: create `VectorLayerWithCluster` component --- .../MapSection/VectorLayerWithCluster.tsx | 108 ++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 src/frontend/src/components/Projects/MapSection/VectorLayerWithCluster.tsx diff --git a/src/frontend/src/components/Projects/MapSection/VectorLayerWithCluster.tsx b/src/frontend/src/components/Projects/MapSection/VectorLayerWithCluster.tsx new file mode 100644 index 00000000..4361491e --- /dev/null +++ b/src/frontend/src/components/Projects/MapSection/VectorLayerWithCluster.tsx @@ -0,0 +1,108 @@ +import { useEffect } from 'react'; + +export default function VectorLayerWithCluster({ + map, + visibleOnMap, + mapLoaded, + sourceId, + geojson, +}: any) { + useEffect(() => { + if (!map || !mapLoaded || !visibleOnMap || !sourceId) return; + + !map.getSource(sourceId) && + map.addSource(sourceId, { + type: 'geojson', + data: geojson, + cluster: true, + clusterMaxZoom: 14, + clusterRadius: 40, + }); + + !map.getLayer('clusters') && + map.addLayer({ + id: 'clusters', + type: 'circle', + source: sourceId, + filter: ['has', 'point_count'], + paint: { + 'circle-color': '#D73F3F', + 'circle-radius': 15, + }, + }); + + map.setGlyphs( + 'https://demotiles.maplibre.org/font/{fontstack}/{range}.pbf', + ); + + !map.getLayer('cluster-count') && + map.addLayer({ + id: 'cluster-count', + type: 'symbol', + source: sourceId, + filter: ['has', 'point_count'], + layout: { + 'text-field': '{point_count_abbreviated}', + 'text-size': 12, + }, + paint: { + 'text-color': '#fff', + }, + }); + + map.addLayer({ + id: 'unclustered-point', + type: 'circle', + source: sourceId, + filter: ['!', ['has', 'point_count']], + paint: { + 'circle-color': '#11b4da', + 'circle-radius': 6, + 'circle-stroke-width': 1, + 'circle-stroke-color': '#fff', + }, + layout: {}, + }); + + // inspect a cluster on click + map.on('click', 'clusters', (e: any) => { + const features = map.queryRenderedFeatures(e.point, { + layers: ['clusters'], + }); + const clusterId = features[0].properties.cluster_id; + map + .getSource(sourceId) + .getClusterExpansionZoom(clusterId, (err: any, zoom: any) => { + if (err) return; + map.easeTo({ + center: features[0].geometry.coordinates, + zoom, + }); + }); + }); + + map.on('mouseenter', 'clusters', () => { + map.getCanvas().style.cursor = 'pointer'; + }); + map.on('mouseleave', 'clusters', () => { + map.getCanvas().style.cursor = ''; + }); + + map.on('mouseenter', 'unclustered-point', () => { + map.getCanvas().style.cursor = 'pointer'; + }); + map.on('mouseleave', 'unclustered-point', () => { + map.getCanvas().style.cursor = ''; + }); + + return () => { + if (sourceId) { + if (map.getLayer(sourceId)) { + map.removeLayer(sourceId); + } + } + }; + }, [geojson, map, mapLoaded, sourceId, visibleOnMap]); + + return null; +} From d978369158a86e955f9d3d1e5c1bbe388dd4e810 Mon Sep 17 00:00:00 2001 From: Sujit Date: Sun, 28 Jul 2024 21:31:25 +0545 Subject: [PATCH 02/10] feat(Buttons): add hover effect(underline text) on button hover --- src/frontend/src/components/RadixComponents/Button.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/frontend/src/components/RadixComponents/Button.tsx b/src/frontend/src/components/RadixComponents/Button.tsx index c7515bed..390c9eb2 100644 --- a/src/frontend/src/components/RadixComponents/Button.tsx +++ b/src/frontend/src/components/RadixComponents/Button.tsx @@ -84,7 +84,7 @@ function Button({ return ( {leftIcon && ( )} - {children} +
{children}
{rightIcon && ( Date: Sun, 28 Jul 2024 21:39:47 +0545 Subject: [PATCH 03/10] fix(draw-tool): cursor style --- .../common/MapLibreComponents/useDrawTool/index.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/frontend/src/components/common/MapLibreComponents/useDrawTool/index.ts b/src/frontend/src/components/common/MapLibreComponents/useDrawTool/index.ts index 9c262a77..6fd8df8f 100644 --- a/src/frontend/src/components/common/MapLibreComponents/useDrawTool/index.ts +++ b/src/frontend/src/components/common/MapLibreComponents/useDrawTool/index.ts @@ -210,14 +210,16 @@ export default function useDrawTool({ if (!map || !drawMode?.includes('draw') || isDrawLayerAdded) return () => {}; const handleMouseMove = (e: any) => { - map.getCanvas().style.cursor = 'crosshair'; + // map.getCanvas().style.cursor = 'crosshair'; + map.getCanvas().style.cursor = ''; const description = 'Click to start drawing shape'; popup.setLngLat(e.lngLat).setHTML(description).addTo(map); }; map.on('mousemove', handleMouseMove); return () => { map.off('mousemove', handleMouseMove); - map.getCanvas().style.cursor = ''; + // map.getCanvas().style.cursor = ''; + map.getCanvas().style.cursor = 'crosshair'; popup.remove(); }; }, [map, drawMode, isDrawLayerAdded]); From 16d28776c1e4c6ba82978ee6eb76d9354c816443 Mon Sep 17 00:00:00 2001 From: Sujit Date: Sun, 28 Jul 2024 21:46:47 +0545 Subject: [PATCH 04/10] feat(projectDashboard): cluster projects on map view --- .../components/Projects/MapSection/index.tsx | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/src/frontend/src/components/Projects/MapSection/index.tsx b/src/frontend/src/components/Projects/MapSection/index.tsx index b2e5a832..b8ddd07d 100644 --- a/src/frontend/src/components/Projects/MapSection/index.tsx +++ b/src/frontend/src/components/Projects/MapSection/index.tsx @@ -1,6 +1,9 @@ import { useMapLibreGLMap } from '@Components/common/MapLibreComponents'; import MapContainer from '@Components/common/MapLibreComponents/MapContainer'; import BaseLayerSwitcher from '@Components/common/MapLibreComponents/BaseLayerSwitcher'; +import { useGetProjectsListQuery } from '@Api/projects'; +import centroid from '@turf/centroid'; +import VectorLayerWithCluster from './VectorLayerWithCluster'; export default function ProjectsMapSection() { const { map, isMapLoaded } = useMapLibreGLMap({ @@ -12,6 +15,31 @@ export default function ProjectsMapSection() { }, disableRotation: true, }); + const { data: projectsList, isLoading } = useGetProjectsListQuery({ + select: (data: any) => { + // find all polygons centroid and set to geojson save to single geojson + const combinedGeojson = data?.data?.reduce( + (acc: Record, current: Record) => { + return { + ...acc, + features: [...acc.features, centroid(current.outline_geojson)], + }; + }, + { + type: 'FeatureCollection', + features: [], + }, + ); + return combinedGeojson; + }, + }); + + // useEffect(() => { + // if (!projectsList) return; + // const bbox = getBbox(projectsList as FeatureCollection); + // map?.fitBounds(bbox as LngLatBoundsLike, { padding: 30 }); + // }, [projectsList, map]); + return ( + + + {/* */} + ); From b279f96fb97444eb754c0098893ede4bf38d2a3e Mon Sep 17 00:00:00 2001 From: Sujit Date: Mon, 29 Jul 2024 15:25:25 +0545 Subject: [PATCH 05/10] feat: refactor `UploadArea` component to set validation from outside the component --- src/frontend/src/components/common/UploadArea/index.tsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/frontend/src/components/common/UploadArea/index.tsx b/src/frontend/src/components/common/UploadArea/index.tsx index 5caa0af2..0e1f4523 100644 --- a/src/frontend/src/components/common/UploadArea/index.tsx +++ b/src/frontend/src/components/common/UploadArea/index.tsx @@ -29,6 +29,9 @@ interface IFileUploadProps extends UseFormPropsType { data?: []; placeholder?: string; onChange?: any; + isValid?: + | ((value: any) => boolean | undefined) + | ((value: any) => Promise); } export default function FileUpload({ @@ -40,6 +43,7 @@ export default function FileUpload({ data, placeholder, onChange, + isValid = () => true, }: IFileUploadProps) { const [inputRef, onFileUpload] = useCustomUpload(); const [uploadedFiles, setUploadedFiles] = useState([]); @@ -66,7 +70,7 @@ export default function FileUpload({ setValue(name, []); }, [register, name, setValue]); - const handleFileUpload = (event: FileEvent) => { + const handleFileUpload = async (event: FileEvent) => { const { files } = event.target; const uploaded = Array.from(files).map(file => ({ id: uuidv4(), @@ -76,6 +80,9 @@ export default function FileUpload({ const uploadedFilesState = multiple ? [...uploadedFiles, ...uploaded] : uploaded; + + const valid = await isValid?.(uploadedFilesState); + if (!valid) return; // @ts-ignore setUploadedFiles(uploadedFilesState); setValue(name, uploadedFilesState, { shouldDirty: true }); From a11f77e8267a69aa352fd6f6eda71e3f4bec047e Mon Sep 17 00:00:00 2001 From: Sujit Date: Mon, 29 Jul 2024 15:28:08 +0545 Subject: [PATCH 06/10] feat(project-creation): Area validation on file upload as project area and no fly zone --- .../FormContents/DefineAOI/index.tsx | 59 +++++++++++++++---- 1 file changed, 46 insertions(+), 13 deletions(-) diff --git a/src/frontend/src/components/CreateProject/FormContents/DefineAOI/index.tsx b/src/frontend/src/components/CreateProject/FormContents/DefineAOI/index.tsx index f16da752..07146795 100644 --- a/src/frontend/src/components/CreateProject/FormContents/DefineAOI/index.tsx +++ b/src/frontend/src/components/CreateProject/FormContents/DefineAOI/index.tsx @@ -63,7 +63,7 @@ export default function DefineAOI({ } const drawnArea = drawnProjectArea && area(drawnProjectArea as FeatureCollection); - if (drawnArea && drawnArea > 1000000) { + if (drawnArea && drawnArea > 100000000) { toast.error('Drawn Area should not exceed 100km²'); dispatch( setCreateProjectState({ @@ -124,9 +124,10 @@ export default function DefineAOI({ const handleProjectAreaFileChange = (file: Record[]) => { if (!file) return; - const geojson = validateGeoJSON(file[0]?.file); + const geojson: any = validateGeoJSON(file[0]?.file); + try { - geojson.then(z => { + geojson.then((z: any) => { if (typeof z === 'object' && !Array.isArray(z) && z !== null) { const convertedGeojson = flatten(z); dispatch(setCreateProjectState({ projectArea: convertedGeojson })); @@ -139,6 +140,33 @@ export default function DefineAOI({ } }; + // @ts-ignore + const validateAreaOfFileUpload = async (file: any) => { + try { + if (!file) return; + const geojson: any = await validateGeoJSON(file[0]?.file); + if ( + typeof geojson === 'object' && + !Array.isArray(geojson) && + geojson !== null + ) { + const convertedGeojson = flatten(geojson); + const uploadedArea: any = + convertedGeojson && area(convertedGeojson as FeatureCollection); + if (uploadedArea && uploadedArea > 100000000) { + toast.error('Drawn Area should not exceed 100km²'); + return false; + } + return true; + } + return false; + } catch (err: any) { + // eslint-disable-next-line no-console + console.log(err); + return false; + } + }; + const handleNoFlyZoneFileChange = (file: Record[]) => { if (!file) return; const geojson = validateGeoJSON(file[0]?.file); @@ -206,16 +234,20 @@ export default function DefineAOI({ rules={{ required: 'Project Area is Required', }} - render={({ field: { value } }) => ( - - )} + render={({ field: { value } }) => { + // console.log(value, 'value12'); + return ( + + ); + }} /> Date: Mon, 29 Jul 2024 15:44:19 +0545 Subject: [PATCH 07/10] feat(useDrawTool): hide tooltip on cursor --- .../components/common/MapLibreComponents/useDrawTool/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/frontend/src/components/common/MapLibreComponents/useDrawTool/index.ts b/src/frontend/src/components/common/MapLibreComponents/useDrawTool/index.ts index 6fd8df8f..77bda231 100644 --- a/src/frontend/src/components/common/MapLibreComponents/useDrawTool/index.ts +++ b/src/frontend/src/components/common/MapLibreComponents/useDrawTool/index.ts @@ -212,8 +212,8 @@ export default function useDrawTool({ const handleMouseMove = (e: any) => { // map.getCanvas().style.cursor = 'crosshair'; map.getCanvas().style.cursor = ''; - const description = 'Click to start drawing shape'; - popup.setLngLat(e.lngLat).setHTML(description).addTo(map); + // const description = 'Click to start drawing shape'; + // popup.setLngLat(e.lngLat).setHTML(description).addTo(map); }; map.on('mousemove', handleMouseMove); return () => { From 5212ff17ccf6639889143d1de075059a343836f5 Mon Sep 17 00:00:00 2001 From: Sujit Date: Mon, 29 Jul 2024 17:49:26 +0545 Subject: [PATCH 08/10] fix: disable consistent return --- .../components/CreateProject/FormContents/DefineAOI/index.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/frontend/src/components/CreateProject/FormContents/DefineAOI/index.tsx b/src/frontend/src/components/CreateProject/FormContents/DefineAOI/index.tsx index 07146795..8e5eacac 100644 --- a/src/frontend/src/components/CreateProject/FormContents/DefineAOI/index.tsx +++ b/src/frontend/src/components/CreateProject/FormContents/DefineAOI/index.tsx @@ -1,3 +1,4 @@ +/* eslint-disable consistent-return */ import { useCallback, useState } from 'react'; import { useTypedDispatch, useTypedSelector } from '@Store/hooks'; import { Controller } from 'react-hook-form'; From e0ed40560c562ada11335b96a4572c8bd70bd574 Mon Sep 17 00:00:00 2001 From: Sujit Date: Tue, 30 Jul 2024 11:51:22 +0545 Subject: [PATCH 09/10] feat(create-project): draw multipolygon no fly zone on Define AOI step --- .../DefineAOI/MapSection/index.tsx | 72 ++++++++++++++++++- .../FormContents/DefineAOI/index.tsx | 9 ++- 2 files changed, 77 insertions(+), 4 deletions(-) diff --git a/src/frontend/src/components/CreateProject/FormContents/DefineAOI/MapSection/index.tsx b/src/frontend/src/components/CreateProject/FormContents/DefineAOI/MapSection/index.tsx index 3b7db7d9..68ce3b48 100644 --- a/src/frontend/src/components/CreateProject/FormContents/DefineAOI/MapSection/index.tsx +++ b/src/frontend/src/components/CreateProject/FormContents/DefineAOI/MapSection/index.tsx @@ -15,8 +15,10 @@ import { setCreateProjectState } from '@Store/actions/createproject'; export default function MapSection({ onResetButtonClick, + handleDrawProjectAreaClick, }: { onResetButtonClick: (reset: any) => void; + handleDrawProjectAreaClick: any; }) { const dispatch = useTypedDispatch(); @@ -35,12 +37,30 @@ export default function MapSection({ const drawNoFlyZoneEnable = useTypedSelector( state => state.createproject.drawNoFlyZoneEnable, ); + const drawnNoFlyZone = useTypedSelector( + state => state.createproject.drawnNoFlyZone, + ); + const noFlyZone = useTypedSelector(state => state.createproject.noFlyZone); const handleDrawEnd = (geojson: GeojsonType | null) => { + if (!geojson) return; if (drawProjectAreaEnable) { dispatch(setCreateProjectState({ drawnProjectArea: geojson })); + } else { + const collectiveGeojson: any = drawnNoFlyZone + ? { + // @ts-ignore + ...drawnNoFlyZone, + features: [ + // @ts-ignore + ...(drawnNoFlyZone?.features || []), + // @ts-ignore + ...(geojson?.features || []), + ], + } + : geojson; + dispatch(setCreateProjectState({ drawnNoFlyZone: collectiveGeojson })); } - dispatch(setCreateProjectState({ drawnNoFlyZone: geojson })); }; const { resetDraw } = useDrawTool({ @@ -58,7 +78,6 @@ export default function MapSection({ const projectArea = useTypedSelector( state => state.createproject.projectArea, ); - const noFlyZone = useTypedSelector(state => state.createproject.noFlyZone); useEffect(() => { if (!projectArea) return; @@ -66,6 +85,14 @@ export default function MapSection({ map?.fitBounds(bbox as LngLatBoundsLike, { padding: 25 }); }, [map, projectArea]); + const drawSaveFromMap = () => { + if (drawProjectAreaEnable) { + handleDrawProjectAreaClick(); + } else { + resetDraw(); + } + }; + return ( + {(drawNoFlyZoneEnable || drawProjectAreaEnable) && ( +
+
+ drawSaveFromMap()} + > + save + + { + dispatch(setCreateProjectState({ drawnNoFlyZone: noFlyZone })); + resetDraw(); + }} + > + restart_alt + +
+
+ )} + + +
); diff --git a/src/frontend/src/components/CreateProject/FormContents/DefineAOI/index.tsx b/src/frontend/src/components/CreateProject/FormContents/DefineAOI/index.tsx index 8e5eacac..c443ccff 100644 --- a/src/frontend/src/components/CreateProject/FormContents/DefineAOI/index.tsx +++ b/src/frontend/src/components/CreateProject/FormContents/DefineAOI/index.tsx @@ -93,6 +93,7 @@ export default function DefineAOI({ dispatch(setCreateProjectState({ drawNoFlyZoneEnable: true })); return; } + if (!drawnNoFlyZone) return; const drawnNoFlyZoneArea = drawnProjectArea && area(drawnNoFlyZone as FeatureCollection); if (drawnNoFlyZoneArea && drawnNoFlyZoneArea > 100000000) { @@ -158,7 +159,6 @@ export default function DefineAOI({ toast.error('Drawn Area should not exceed 100km²'); return false; } - return true; } return false; } catch (err: any) { @@ -297,6 +297,8 @@ export default function DefineAOI({ dispatch( setCreateProjectState({ noFlyZone: null, + drawnNoFlyZone: null, + drawNoFlyZoneEnable: false, }), ) } @@ -373,7 +375,10 @@ export default function DefineAOI({ )}
- +
From 273ad54e7af51dc3286a77ff1f5b0e25dcb8fc4e Mon Sep 17 00:00:00 2001 From: Sujit Date: Tue, 30 Jul 2024 12:07:55 +0545 Subject: [PATCH 10/10] fix(create-project): validate area before file upload --- .../components/CreateProject/FormContents/DefineAOI/index.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/frontend/src/components/CreateProject/FormContents/DefineAOI/index.tsx b/src/frontend/src/components/CreateProject/FormContents/DefineAOI/index.tsx index c443ccff..7e8498c7 100644 --- a/src/frontend/src/components/CreateProject/FormContents/DefineAOI/index.tsx +++ b/src/frontend/src/components/CreateProject/FormContents/DefineAOI/index.tsx @@ -159,6 +159,7 @@ export default function DefineAOI({ toast.error('Drawn Area should not exceed 100km²'); return false; } + return true; } return false; } catch (err: any) {