diff --git a/src/frontend/src/components/MapComponent/OpenLayersComponent/Layers/ClusterLayer.js b/src/frontend/src/components/MapComponent/OpenLayersComponent/Layers/ClusterLayer.js index 7ebaa41501..15d4b470d8 100644 --- a/src/frontend/src/components/MapComponent/OpenLayersComponent/Layers/ClusterLayer.js +++ b/src/frontend/src/components/MapComponent/OpenLayersComponent/Layers/ClusterLayer.js @@ -6,11 +6,10 @@ import Stroke from 'ol/style/Stroke'; import Style from 'ol/style/Style'; import Fill from 'ol/style/Fill'; import { Cluster, OSM as OSMSource } from 'ol/source'; -import { Text, Circle, Icon } from 'ol/style'; +import { Text, Circle } from 'ol/style'; import VectorSource from 'ol/source/Vector'; import SelectCluster from 'ol-ext/interaction/SelectCluster'; import { hexToRgba } from '@/components/MapComponent/OpenLayersComponent/helpers/styleUtils'; -import MarkerIcon from '@/assets/images/red_marker.png'; function setAsyncStyle(style, feature, getIndividualStyle) { const styleCache = {}; @@ -186,23 +185,7 @@ const ClusterLayer = ({ if (isExpandedFeature) { const feature = clusterMember.getProperties().features[0]; const featureProperty = feature?.getProperties(); - console.log(featureProperty, 'featureProperty'); - console.log(feature, 'isExpandedFeature'); - const style = new Style({ - image: new Icon({ - src: MarkerIcon, - scale: 0.06, - }), - text: new Text({ - text: featureProperty?.project_id, - fill: new Fill({ - color: 'black', - }), - offsetY: 25, - font: '15px Times New Roman', - }), - }); - fillColor = '#96bfff'; + const style = getIndividualStyle(featureProperty); return style; } else { return; diff --git a/src/frontend/src/components/ProjectSubmissions/SubmissionsTable.tsx b/src/frontend/src/components/ProjectSubmissions/SubmissionsTable.tsx index b638dc4022..48b43bce87 100644 --- a/src/frontend/src/components/ProjectSubmissions/SubmissionsTable.tsx +++ b/src/frontend/src/components/ProjectSubmissions/SubmissionsTable.tsx @@ -15,7 +15,7 @@ import Button from '@/components/common/Button'; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@/components/common/Dropdown'; import { ConvertXMLToJOSM, getDownloadProjectSubmission } from '@/api/task'; import { Modal } from '@/components/common/Modal'; -import { useNavigate, useSearchParams } from 'react-router-dom'; +import { Link, useNavigate, useSearchParams } from 'react-router-dom'; import filterParams from '@/utilfunctions/filterParams'; import UpdateReviewStatusModal from '@/components/ProjectSubmissions/UpdateReviewStatusModal'; import { useAppSelector } from '@/types/reduxTypes'; @@ -467,14 +467,9 @@ const SubmissionsTable = ({ toggleView }) => { const taskUid = taskList?.find((task) => task?.id == row?.task_id)?.id; return (
- { - navigate( - `/project-submissions/${projectId}/tasks/${taskUid}/submission/${row?.meta?.instanceID}`, - ); - }} - />{' '} + + + {' '} ; + featureGeojson: { vectorLayerGeojson: geojsonType; clusterLayerGeojson: geojsonType }; +}; + +const getIndividualStyle = (featureProperty) => { + const style = new Style({ + image: new Circle({ + radius: 10, + stroke: new Stroke({ + color: hexToRgba('#D73F37'), + width: 2, + }), + fill: new Fill({ + color: hexToRgba('#eb9f9f'), + width: 40, + }), + }), + }); + return style; }; const SubmissionInstanceMap = ({ featureGeojson }: submissionInstanceMapPropType) => { @@ -39,9 +62,9 @@ const SubmissionInstanceMap = ({ featureGeojson }: submissionInstanceMapPropType
- {featureGeojson?.type && ( + {featureGeojson?.vectorLayerGeojson?.type && ( )} + {featureGeojson?.clusterLayerGeojson?.type && ( + {}} + getIndividualStyle={getIndividualStyle} + /> + )} ); diff --git a/src/frontend/src/store/types/ISubmissions.ts b/src/frontend/src/store/types/ISubmissions.ts index c37db5de8d..0baecd7a12 100644 --- a/src/frontend/src/store/types/ISubmissions.ts +++ b/src/frontend/src/store/types/ISubmissions.ts @@ -44,3 +44,17 @@ type mappedVsValidatedTaskType = { validated: number; label: string; }; + +export type featureType = { + type: 'Feature'; + geometry: Partial<{ + type: string; + coordinates: any[]; + }>; + properties: Record; +}; + +export type geojsonType = { + type: 'FeatureCollection'; + features: featureType[]; +}; diff --git a/src/frontend/src/utilfunctions/extractGeojsonFromObject.ts b/src/frontend/src/utilfunctions/extractGeojsonFromObject.ts new file mode 100644 index 0000000000..79883d720a --- /dev/null +++ b/src/frontend/src/utilfunctions/extractGeojsonFromObject.ts @@ -0,0 +1,106 @@ +import { featureType, geojsonType } from '@/store/types/ISubmissions'; + +// convert JavaRosa string to a GeoJson +const convertCoordinateStringToFeature = (coordinateString: string) => { + let feature: featureType = { + type: 'Feature', + geometry: {}, + properties: {}, + }; + + // if feature is LineString in JavaRosa format it contains string of array separated by ';' + if (coordinateString?.includes(';')) { + let coordinates = coordinateString?.split(';')?.map((coord) => { + let coordinate = coord + .trim() + .split(' ') + .slice(0, 2) + .map((value: string) => { + return parseFloat(value); + }); + return [coordinate[1], coordinate[0]]; + }); + feature = { ...feature, geometry: { type: 'LineString', coordinates: coordinates } }; + } else { + // if feature is Point in JavaRosa format it contains string of array + const splittedCoord = coordinateString?.split(' '); + let coordinates = [+splittedCoord[1], +splittedCoord[0]]; + feature = { ...feature, geometry: { type: 'Point', coordinates: coordinates } }; + } + return feature; +}; + +export function extractGeojsonFromObject(data: Record) { + /*{ all feature of geometry points are stored in clusterLayerGeojson + reason: more than one gps point can be on the exact same coordinate, so clustering and point expand on cluster click is important for visualization + }*/ + let clusterLayerGeojson: geojsonType = { + type: 'FeatureCollection', + features: [], + }; + + // feature other than of geometry type points are stored in vectorLayerGeojson + let vectorLayerGeojson: any = { + type: 'FeatureCollection', + features: [], + }; + + // function called recursively until the object is traversed + function traverse(data: Record) { + typeof data === 'object' && + Object.entries(data)?.map(([key, value]) => { + if (value && typeof value === 'object' && Object.keys(value).includes('coordinates')) { + const feature = value as featureType['geometry']; + if (feature?.type === 'Point') { + clusterLayerGeojson = { + ...clusterLayerGeojson, + features: [...clusterLayerGeojson.features, { type: 'Feature', geometry: feature, properties: {} }], + }; + } else { + vectorLayerGeojson = { + ...vectorLayerGeojson, + features: [...vectorLayerGeojson.features, { type: 'Feature', geometry: value, properties: {} }], + }; + } + } + // if value is object then traverse + else if (value && typeof value === 'object') { + traverse(value); + } + // check if value is JavaRosa LineString + // the JavaRosa LineString is separated by ';' + else if ( + value && + typeof value === 'string' && + value?.includes(';') && + value + ?.split(';')?.[0] + ?.split(' ') + ?.every((item) => !isNaN(+item)) + ) { + const convertedFeature = convertCoordinateStringToFeature(value); + vectorLayerGeojson = { + ...vectorLayerGeojson, + features: [...vectorLayerGeojson.features, convertedFeature], + }; + } + // check if value is JavaRosa Point + // point contains 4 floating point data + else if ( + value && + typeof value === 'string' && + value?.split(' ')?.every((item) => !isNaN(+item)) && + value?.split(' ')?.length === 4 + ) { + const convertedFeature = convertCoordinateStringToFeature(value); + clusterLayerGeojson = { + ...clusterLayerGeojson, + features: [...clusterLayerGeojson.features, convertedFeature], + }; + } + }); + } + + traverse(data); + return { clusterLayerGeojson, vectorLayerGeojson }; +} diff --git a/src/frontend/src/views/SubmissionDetails.tsx b/src/frontend/src/views/SubmissionDetails.tsx index 9582301348..d4ebb214af 100644 --- a/src/frontend/src/views/SubmissionDetails.tsx +++ b/src/frontend/src/views/SubmissionDetails.tsx @@ -13,6 +13,7 @@ import Accordion from '@/components/common/Accordion'; import { GetProjectComments } from '@/api/Project'; import SubmissionComments from '@/components/SubmissionInstance/SubmissionComments'; import ImageSlider from '@/components/common/ImageSlider'; +import { extractGeojsonFromObject } from '@/utilfunctions/extractGeojsonFromObject'; const renderValue = (value: any, key: string = '') => { if (key === 'start' || key === 'end') { @@ -130,49 +131,6 @@ const SubmissionDetails = () => { const filteredData = restSubmissionDetails ? removeNullValues(restSubmissionDetails) : {}; - const coordinatesArray: [number, number][] = restSubmissionDetails?.xlocation?.split(';').map(function ( - coord: string, - ) { - let coordinate = coord - .trim() - .split(' ') - .slice(0, 2) - .map((value: string) => { - return parseFloat(value); - }); - return [coordinate[1], coordinate[0]]; - }); - - const geojsonFeature: Record = { - type: 'FeatureCollection', - features: [ - { - type: 'Feature', - geometry: { - type: 'LineString', - coordinates: coordinatesArray, - }, - properties: {}, - }, - ], - }; - - const pointFeature = { - type: 'Feature', - geometry: { - ...restSubmissionDetails?.point, - }, - properties: {}, - }; - - const newFeaturePoint = { - type: 'Feature', - geometry: { - ...restSubmissionDetails?.new_feature, - }, - properties: {}, - }; - return ( <> @@ -256,19 +214,7 @@ const SubmissionDetails = () => {
- +