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

Feat/frontend req tokha #32

Merged
merged 6 commits into from
Mar 4, 2025
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 @@ -19,7 +19,9 @@ const FeatureSelectionPopup = ({ featureProperties, taskId }: FeatureSelectionPo
const taskModalStatus = useAppSelector((state) => state.project.taskModalStatus);
const entityOsmMap = useAppSelector((state) => state.project.entityOsmMap);
const projectId = params.id || '';
const entity = entityOsmMap.find((x) => x.osm_id === featureProperties?.osm_id);
const entity = entityOsmMap.find(
(x) => x.osm_id === featureProperties?.osm_id || x.id === featureProperties?.entity_id,
);
const submissionIds = entity?.submission_ids ? entity?.submission_ids?.split(',') : [];

return (
Expand All @@ -36,7 +38,9 @@ const FeatureSelectionPopup = ({ featureProperties, taskId }: FeatureSelectionPo
} fmtm-rounded-t-2xl md:fmtm-rounded-tr-none md:fmtm-rounded-l-2xl`}
>
<div className="fmtm-flex fmtm-justify-between fmtm-items-center fmtm-gap-2 fmtm-px-3 sm:fmtm-px-5 fmtm-py-2">
<h4 className="fmtm-text-lg fmtm-font-bold">Feature: {featureProperties?.osm_id}</h4>
<h4 className="fmtm-text-lg fmtm-font-bold">
{entity?.osm_id ? `Feature: ${entity.osm_id}` : `Entity: ${entity?.id}`}
</h4>
<div title="Close">
<AssetModules.CloseIcon
style={{ width: '20px' }}
Expand All @@ -45,26 +49,28 @@ const FeatureSelectionPopup = ({ featureProperties, taskId }: FeatureSelectionPo
/>
</div>
</div>
<div className="fmtm-h-fit fmtm-px-2 sm:fmtm-px-5 fmtm-py-2 fmtm-border-t">
<div className="fmtm-flex fmtm-flex-col fmtm-gap-1 fmtm-mt-1">
<p>
<span>Tags: </span>
<span className="fmtm-overflow-hidden fmtm-line-clamp-2">{featureProperties?.tags}</span>
</p>
<p>
<span>Timestamp: </span>
<span>{featureProperties?.timestamp}</span>
</p>
<p>
<span>Changeset: </span>
<span>{featureProperties?.changeset}</span>
</p>
<p>
<span>Version: </span>
<span>{featureProperties?.version}</span>
</p>
{featureProperties?.changeset && (
<div className="fmtm-h-fit fmtm-px-2 sm:fmtm-px-5 fmtm-py-2 fmtm-border-t">
<div className="fmtm-flex fmtm-flex-col fmtm-gap-1 fmtm-mt-1">
<p>
<span>Tags: </span>
<span className="fmtm-overflow-hidden fmtm-line-clamp-2">{featureProperties?.tags}</span>
</p>
<p>
<span>Timestamp: </span>
<span>{featureProperties?.timestamp}</span>
</p>
<p>
<span>Changeset: </span>
<span>{featureProperties?.changeset}</span>
</p>
<p>
<span>Version: </span>
<span>{featureProperties?.version}</span>
</p>
</div>
</div>
</div>
)}
{!submissionIds ||
(submissionIds?.length !== 0 && (
<div className="fmtm-px-2 sm:fmtm-px-5 fmtm-py-3 fmtm-border-t fmtm-flex fmtm-flex-col fmtm-gap-3">
Expand Down
11 changes: 6 additions & 5 deletions src/frontend/src/store/types/ITask.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,10 @@ type downloadSubmissionLoadingTypes = {
};

export type TaskFeatureSelectionProperties = {
osm_id: number;
tags: string;
timestamp: string;
version: number;
changeset: number;
osm_id?: number;
tags?: string;
timestamp?: string;
version?: number;
changeset?: number;
entity_id?: string;
};
13 changes: 6 additions & 7 deletions src/frontend/src/utilfunctions/getTaskStatusStyle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ import { Fill, Icon, Stroke, Style } from 'ol/style';
import { getCenter } from 'ol/extent';
import { Point } from 'ol/geom';
import AssetModules from '@/shared/AssetModules';
import { entity_state } from '@/types/enums';
import { EntityOsmMap } from '@/store/types/IProject';
import { Text } from 'ol/style';

function createPolygonStyle(fillColor: string, strokeColor: string) {
return new Style({
Expand Down Expand Up @@ -106,11 +105,7 @@ const getTaskStatusStyle = (feature: Record<string, any>, mapTheme: Record<strin
return geojsonStyles[status];
};

export const getFeatureStatusStyle = (osmId: string, mapTheme: Record<string, any>, entityOsmMap: EntityOsmMap[]) => {
const entity = entityOsmMap?.find((entity) => entity?.osm_id === osmId) as EntityOsmMap;

let status = entity_state[entity?.status];

export const getFeatureStatusStyle = (mapTheme: Record<string, any>, status: string, osm_id: number) => {
const borderStrokeColor = 'rgb(0,0,0,0.5)';

const strokeStyle = new Stroke({
Expand All @@ -125,6 +120,10 @@ export const getFeatureStatusStyle = (osmId: string, mapTheme: Record<string, an
fill: new Fill({
color: mapTheme.palette.mapFeatureColors.ready_rgb,
}),
text: new Text({
text: osm_id?.toString(),
font: '10px Arial',
}),
}),
OPENED_IN_ODK: new Style({
stroke: strokeStyle,
Expand Down
29 changes: 28 additions & 1 deletion src/frontend/src/views/ProjectDetailsV2.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ import Instructions from '@/components/ProjectDetailsV2/Instructions';
import useDocumentTitle from '@/utilfunctions/useDocumentTitle';
import { Style, Stroke } from 'ol/style';
import MapStyles from '@/hooks/MapStyles';
import { EntityOsmMap } from '@/models/project/projectModel';
import { entity_state } from '@/types/enums';

const ProjectDetailsV2 = () => {
useDocumentTitle('Project Details');
Expand Down Expand Up @@ -71,6 +73,7 @@ const ProjectDetailsV2 = () => {
const entityOsmMap = useAppSelector((state) => state?.project?.entityOsmMap);
const entityOsmMapLoading = useAppSelector((state) => state?.project?.entityOsmMapLoading);
const badGeomFeatureCollection = useAppSelector((state) => state?.project?.badGeomFeatureCollection);
const newGeomFeatureCollection = useAppSelector((state) => state?.project?.newGeomFeatureCollection);
const getGeomLogLoading = useAppSelector((state) => state?.project?.getGeomLogLoading);
const syncTaskStateLoading = useAppSelector((state) => state?.project?.syncTaskStateLoading);

Expand Down Expand Up @@ -512,12 +515,36 @@ const ProjectDetailsV2 = () => {
zIndex={5}
style=""
/>
<VectorLayer
geojson={newGeomFeatureCollection}
viewProperties={{
size: map?.getSize(),
padding: [50, 50, 50, 50],
constrainResolution: true,
duration: 2000,
}}
layerProperties={{ name: 'new-entities' }}
zIndex={5}
style=""
mapOnClick={projectClickOnTaskFeature}
getTaskStatusStyle={(feature) => {
const entity = entityOsmMap?.find(
(entity) => entity?.id === feature?.getProperties()?.entity_id,
) as EntityOsmMap;
const status = entity_state[entity?.status];
return getFeatureStatusStyle(mapTheme, status, entity?.osm_id);
}}
/>
{dataExtractUrl && isValidUrl(dataExtractUrl) && dataExtractExtent && selectedTask && (
<VectorLayer
fgbUrl={dataExtractUrl}
fgbExtent={dataExtractExtent}
getTaskStatusStyle={(feature) => {
return getFeatureStatusStyle(feature?.getProperties()?.osm_id, mapTheme, entityOsmMap);
const osmId = feature?.getProperties()?.osm_id;
const entity = entityOsmMap?.find((entity) => entity?.osm_id === osmId) as EntityOsmMap;

const status = entity_state[entity?.status];
return getFeatureStatusStyle(mapTheme, status, entity?.osm_id);
}}
viewProperties={{
size: map?.getSize(),
Expand Down
126 changes: 105 additions & 21 deletions src/mapper/src/lib/components/map/main.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,9 @@
const clickedTaskFeature = map?.queryRenderedFeatures(e.point, {
layers: ['task-fill-layer'],
});
const clickedNewEntityFeature = map?.queryRenderedFeatures(e.point, {
layers: ['new-entity-polygon-layer'],
});
// if clicked point contains entity then set it's osm id else set null to store
if (clickedEntityFeature && clickedEntityFeature?.length > 0) {
const entityCentroid = centroid(clickedEntityFeature[0].geometry);
Expand All @@ -164,6 +167,14 @@
entityId: clickedEntityId,
coordinate: entityCentroid?.geometry?.coordinates,
});
} else if (clickedNewEntityFeature && clickedNewEntityFeature?.length > 0) {
const entityCentroid = centroid(clickedNewEntityFeature[0].geometry);
const clickedEntityId = clickedNewEntityFeature[0]?.properties?.entity_id;
entitiesStore.setSelectedEntity(clickedEntityId);
entitiesStore.setSelectedEntityCoordinate({
entityId: clickedEntityId,
coordinate: entityCentroid?.geometry?.coordinates,
});
} else {
entitiesStore.setSelectedEntity(null);
entitiesStore.setSelectedEntityCoordinate(null);
Expand All @@ -172,15 +183,18 @@
// if clicked point contains task layer
if (clickedTaskFeature && clickedTaskFeature?.length > 0) {
taskAreaClicked = true;
const clickedTaskId = clickedTaskFeature[0]?.properties?.fid;
taskStore.setSelectedTaskId(clickedTaskId, clickedTaskFeature[0]?.properties?.task_index);
const clickedTaskId = clickedTaskFeature?.[0]?.properties?.fid;
taskStore.setSelectedTaskId(clickedTaskId, clickedTaskFeature?.[0]?.properties?.task_index);
if (+(projectSetupStepStore.projectSetupStep || 0) === projectSetupStepEnum['task_selection']) {
localStorage.setItem(`project-${projectId}-setup`, projectSetupStepEnum['complete_setup']);
projectSetupStepStore.setProjectSetupStep(projectSetupStepEnum['complete_setup']);
}
}

if (clickedEntityFeature && clickedEntityFeature?.length > 0) {
if (
(clickedEntityFeature && clickedEntityFeature?.length > 0) ||
(clickedNewEntityFeature && clickedNewEntityFeature?.length > 0)
) {
toggleActionModal('entity-modal');
} else if (clickedTaskFeature && clickedTaskFeature?.length > 0) {
toggleActionModal('task-modal');
Expand Down Expand Up @@ -267,21 +281,42 @@
}
});

function addStatusToGeojsonProperty(geojsonData: FeatureCollection): FeatureCollection {
return {
...geojsonData,
features: geojsonData.features.map((feature) => {
const entity = entitiesStore.entitiesStatusList.find((entity) => entity.osmid === feature?.properties?.osm_id);
return {
...feature,
properties: {
...feature.properties,
status: entity?.status,
entity_id: entity?.entity_id,
},
};
}),
};
function addStatusToGeojsonProperty(geojsonData: FeatureCollection, entityType: '' | 'new'): FeatureCollection {
if (entityType === 'new') {
return {
...geojsonData,
features: geojsonData.features.map((feature) => {
const entity = entitiesStore.entitiesStatusList.find(
(entity) => entity.entity_id === feature?.properties?.entity_id,
);
return {
...feature,
properties: {
...feature.properties,
status: entity?.status,
entity_id: entity?.entity_id,
},
};
}),
};
} else {
return {
...geojsonData,
features: geojsonData.features.map((feature) => {
const entity = entitiesStore.entitiesStatusList.find(
(entity) => entity.osmid === feature?.properties?.osm_id,
);
return {
...feature,
properties: {
...feature.properties,
status: entity?.status,
entity_id: entity?.entity_id,
},
};
}),
};
}
}

function zoomToProject() {
Expand Down Expand Up @@ -487,7 +522,7 @@
extent={taskStore.selectedTaskGeom}
extractGeomCols={true}
promoteId="id"
processGeojson={(geojsonData) => addStatusToGeojsonProperty(geojsonData)}
processGeojson={(geojsonData) => addStatusToGeojsonProperty(geojsonData, '')}
geojsonUpdateDependency={entitiesStore.entitiesStatusList}
>
<SymbolLayer
Expand Down Expand Up @@ -573,7 +608,56 @@
/>
</GeoJSON>
{/if}

{#if showEntityLayer}
<GeoJSON id="new-geoms" data={addStatusToGeojsonProperty(entitiesStore.newGeomList, 'new')}>
<FillLayer
id="new-entity-polygon-layer"
paint={{
'fill-opacity': ['match', ['get', 'status'], 'MARKED_BAD', 0, 0.6],
'fill-color': [
'match',
['get', 'status'],
'READY',
'#9c9a9a',
'OPENED_IN_ODK',
'#fae15f',
'SURVEY_SUBMITTED',
'#71bf86',
'VALIDATED',
'#71bf86',
'MARKED_BAD',
'#fa1100',
'#c5fbf5',
],
'fill-outline-color': [
'match',
['get', 'status'],
'READY',
'#000000',
'OPENED_IN_ODK',
'#ffd603',
'SURVEY_SUBMITTED',
'#32a852',
'MARKED_BAD',
'#fa1100',
'#c5fbf5',
],
}}
beforeLayerType="symbol"
manageHoverState
/>
<LineLayer
layout={{ 'line-cap': 'round', 'line-join': 'round' }}
paint={{
'line-color': '#fa1100',
'line-width': ['case', ['==', ['get', 'osm_id'], entitiesStore.selectedEntity || ''], 1, 0],
'line-opacity': ['case', ['==', ['get', 'osm_id'], entitiesStore.selectedEntity || ''], 1, 0.35],
}}
beforeLayerType="symbol"
manageHoverState
/>
</GeoJSON>
{/if}
<!-- pulse effect layer representing rejected entities -->

<!-- Offline pmtiles, if present (alternative approach, not baselayer) -->
Expand Down Expand Up @@ -624,7 +708,7 @@
<LayerSwitcher
{map}
styles={allBaseLayers}
sourcesIdToReAdd={['tasks', 'entities', 'geolocation', 'maplibre-gl-directions', 'bad-geoms']}
sourcesIdToReAdd={['tasks', 'entities', 'geolocation', 'maplibre-gl-directions', 'bad-geoms', 'new-geoms']}
selectedStyleName={selectedBaselayer}
{selectedStyleUrl}
setSelectedStyleUrl={(style) => (selectedStyleUrl = style)}
Expand Down
8 changes: 3 additions & 5 deletions src/mapper/src/lib/odk/collect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,16 @@ import { geojsonGeomToJavarosa } from '$lib/odk/javarosa';

const alertStore = getAlertStore();

export function openOdkCollectNewFeature(xFormId: string, geom: GeoJSONGeometry, task_id: number | null) {
if (!xFormId || !geom) {
export function openOdkCollectNewFeature(xFormId: string, entityId: string) {
if (!xFormId || !entityId) {
return;
}

const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);

const javarosaGeom = geojsonGeomToJavarosa(geom);

if (isMobile) {
// TODO we need to update the form to support task_id=${}&
document.location.href = `odkcollect://form/${xFormId}?new_feature=${javarosaGeom}&task_id=${task_id}`;
document.location.href = `odkcollect://form/${xFormId}?feature=${entityId}`;
} else {
alertStore.setAlert({
variant: 'warning',
Expand Down
Loading