Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Visualize all uploaded submission features/geometries on submission instance page #1931

Merged
merged 13 commits into from
Dec 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {};
Expand Down Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -467,14 +467,9 @@ const SubmissionsTable = ({ toggleView }) => {
const taskUid = taskList?.find((task) => task?.id == row?.task_id)?.id;
return (
<div className="fmtm-w-[5rem] fmtm-overflow-hidden fmtm-truncate fmtm-text-center">
<AssetModules.VisibilityOutlinedIcon
className="fmtm-text-[#545454] hover:fmtm-text-primaryRed"
onClick={() => {
navigate(
`/project-submissions/${projectId}/tasks/${taskUid}/submission/${row?.meta?.instanceID}`,
);
}}
/>{' '}
<Link to={`/project-submissions/${projectId}/tasks/${taskUid}/submission/${row?.meta?.instanceID}`}>
<AssetModules.VisibilityOutlinedIcon className="fmtm-text-[#545454] hover:fmtm-text-primaryRed" />
</Link>
<span className="fmtm-text-primaryRed fmtm-border-[1px] fmtm-border-primaryRed fmtm-mx-1"></span>{' '}
<AssetModules.CheckOutlinedIcon
className="fmtm-text-[#545454] hover:fmtm-text-primaryRed"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,32 @@ import LayerSwitcherControl from '@/components/MapComponent/OpenLayersComponent/
import { VectorLayer } from '@/components/MapComponent/OpenLayersComponent/Layers';
import { defaultStyles } from '@/components/MapComponent/OpenLayersComponent/helpers/styleUtils';
import LayerSwitchMenu from '../MapComponent/OpenLayersComponent/LayerSwitcher/LayerSwitchMenu';
import { ClusterLayer } from '@/components/MapComponent/OpenLayersComponent/Layers';
import { Style, Circle } from 'ol/style';
import { Stroke } from 'ol/style';
import { hexToRgba } from '@/components/MapComponent/OpenLayersComponent/helpers/styleUtils';
import { Fill } from 'ol/style';
import { geojsonType } from '@/store/types/ISubmissions';

type submissionInstanceMapPropType = {
featureGeojson: Record<string, any>;
featureGeojson: { vectorLayerGeojson: geojsonType; clusterLayerGeojson: geojsonType };
};

const getIndividualStyle = (featureProperty) => {
const style = new Style({
spwoodcock marked this conversation as resolved.
Show resolved Hide resolved
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) => {
Expand Down Expand Up @@ -39,9 +62,9 @@ const SubmissionInstanceMap = ({ featureGeojson }: submissionInstanceMapPropType
<LayerSwitchMenu map={map} />
</div>
<LayerSwitcherControl visible={'osm'} />
{featureGeojson?.type && (
{featureGeojson?.vectorLayerGeojson?.type && (
<VectorLayer
geojson={featureGeojson}
geojson={featureGeojson?.vectorLayerGeojson}
viewProperties={{
size: map?.getSize(),
padding: [50, 50, 50, 50],
Expand All @@ -54,6 +77,22 @@ const SubmissionInstanceMap = ({ featureGeojson }: submissionInstanceMapPropType
style={{ ...defaultStyles, lineColor: '#D73F37', lineThickness: 2, circleRadius: 10, fillColor: '#D73F37' }}
/>
)}
{featureGeojson?.clusterLayerGeojson?.type && (
<ClusterLayer
map={map}
source={featureGeojson?.clusterLayerGeojson}
zIndex={100}
visibleOnMap={true}
style={{
...defaultStyles,
background_color: '#D73F37',
color: '#eb9f9f',
opacity: 90,
}}
mapOnClick={() => {}}
getIndividualStyle={getIndividualStyle}
/>
)}
</MapComponent>
</div>
);
Expand Down
14 changes: 14 additions & 0 deletions src/frontend/src/store/types/ISubmissions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,17 @@ type mappedVsValidatedTaskType = {
validated: number;
label: string;
};

export type featureType = {
type: 'Feature';
geometry: Partial<{
type: string;
coordinates: any[];
}>;
properties: Record<string, any>;
};

export type geojsonType = {
type: 'FeatureCollection';
features: featureType[];
};
106 changes: 106 additions & 0 deletions src/frontend/src/utilfunctions/extractGeojsonFromObject.ts
Original file line number Diff line number Diff line change
@@ -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<string, any>) {
/*{ 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<string, any>) {
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 };
}
58 changes: 2 additions & 56 deletions src/frontend/src/views/SubmissionDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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') {
Expand Down Expand Up @@ -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<string, any> = {
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 (
<>
<UpdateReviewStatusModal />
Expand Down Expand Up @@ -256,19 +214,7 @@ const SubmissionDetails = () => {
</div>
<div className="fmtm-flex fmtm-flex-grow fmtm-justify-center fmtm-mt-10 md:fmtm-mt-0">
<div className="fmtm-w-full fmtm-h-[20rem] md:fmtm-h-full fmtm-rounded-lg fmtm-overflow-hidden">
<SubmissionInstanceMap
featureGeojson={
submissionDetailsLoading
? {}
: restSubmissionDetails?.new_feature
? newFeaturePoint
: coordinatesArray
? geojsonFeature
: restSubmissionDetails?.point
? pointFeature
: {}
}
/>
<SubmissionInstanceMap featureGeojson={extractGeojsonFromObject(restSubmissionDetails)} />
</div>
</div>
</div>
Expand Down
Loading