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 = () => {