Skip to content

Commit

Permalink
feat: capability to draw new polygon and linestring geoms (#2082)
Browse files Browse the repository at this point in the history
* feat(frontend): hardcode new_geom_type='POLYGON' during project creation

* feat(backend): use new_geom_type field when generating project xlsform

* build(mapper): update maplibre terradraw component version

* build: update mapper frontend version to match app version

* feat(mapper): allow drawing of new polygon & linestring geoms
  • Loading branch information
spwoodcock authored Jan 13, 2025
1 parent e6e1a66 commit 402dd8f
Show file tree
Hide file tree
Showing 14 changed files with 67 additions and 20 deletions.
6 changes: 5 additions & 1 deletion src/backend/app/central/central_crud.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@

from app.central import central_deps, central_schemas
from app.config import settings
from app.db.enums import EntityState, HTTPStatus
from app.db.enums import DbGeomType, EntityState, HTTPStatus
from app.db.models import DbXLSForm
from app.db.postgis_utils import (
geojson_to_javarosa_geom,
Expand Down Expand Up @@ -323,6 +323,7 @@ async def append_fields_to_user_xlsform(
form_category: str = "buildings",
additional_entities: list[str] = None,
existing_id: str = None,
new_geom_type: DbGeomType = DbGeomType.POINT,
) -> tuple[str, BytesIO]:
"""Helper to return the intermediate XLSForm prior to convert."""
log.debug("Appending mandatory FMTM fields to XLSForm")
Expand All @@ -331,6 +332,7 @@ async def append_fields_to_user_xlsform(
form_category=form_category,
additional_entities=additional_entities,
existing_id=existing_id,
new_geom_type=new_geom_type,
)


Expand All @@ -339,13 +341,15 @@ async def validate_and_update_user_xlsform(
form_category: str = "buildings",
additional_entities: list[str] = None,
existing_id: str = None,
new_geom_type: DbGeomType = DbGeomType.POINT,
) -> BytesIO:
"""Wrapper to append mandatory fields and validate user uploaded XLSForm."""
xform_id, updated_file_bytes = await append_fields_to_user_xlsform(
xlsform,
form_category=form_category,
additional_entities=additional_entities,
existing_id=existing_id,
new_geom_type=new_geom_type,
)

# Validate and return the form
Expand Down
9 changes: 9 additions & 0 deletions src/backend/app/projects/project_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -561,6 +561,12 @@ async def validate_form(
If the `debug` param is used, the form is returned for inspection.
NOTE that this debug form has additional fields appended and should
not be used for FMTM project creation.
NOTE this provides a basic sanity check, some fields are omitted
so the form is not usable in production:
- form_category
- additional_entities
- new_geom_type
"""
if debug:
xform_id, updated_form = await central_crud.append_fields_to_user_xlsform(
Expand Down Expand Up @@ -915,6 +921,7 @@ async def generate_files(
project = project_user_dict.get("project")
project_id = project.id
form_category = project.xform_category
new_geom_type = project.new_geom_type

log.debug(f"Generating additional files for project: {project.id}")

Expand All @@ -926,6 +933,7 @@ async def generate_files(
xlsform=xlsform_upload,
form_category=form_category,
additional_entities=additional_entities,
new_geom_type=new_geom_type,
)
xlsform = xlsform_upload

Expand All @@ -941,6 +949,7 @@ async def generate_files(
xlsform=xlsform,
form_category=form_category,
additional_entities=additional_entities,
new_geom_type=new_geom_type,
)
# Write XLS form content to db
xlsform_bytes = project_xlsform.getvalue()
Expand Down
1 change: 1 addition & 0 deletions src/backend/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ version_files = [
"pyproject.toml:version",
"app/__version__.py",
"../frontend/package.json:version",
"../mapper/package.json:version",
"../../chart/Chart.yaml:appVersion",
]
changelog_file = "../../CHANGELOG.md"
Expand Down
4 changes: 4 additions & 0 deletions src/frontend/src/components/createnewproject/SplitTasks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,10 @@ const SplitTasks = ({ flag, setGeojsonFile, customDataExtractUpload, additionalF
// Create a file object from the Blob
const taskAreaGeojsonFile = new File([taskAreaBlob], 'data.json', { type: 'application/json' });

// FIXME for now hardcoded as Polygon projects (add project creation UI for user selection)
// projectData = { ...projectData, new_geom_type: projectDetails.new_geom_type };
projectData = { ...projectData, new_geom_type: 'POLYGON' };

dispatch(
CreateProjectService(
`${import.meta.env.VITE_API_URL}/projects?org_id=${projectDetails.organisation_id}`,
Expand Down
3 changes: 3 additions & 0 deletions src/frontend/src/models/project/projectModel.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { NewGeomTypes } from '@/types/enums';

export type osmTag = {
string: string;
};
Expand Down Expand Up @@ -60,6 +62,7 @@ export type projectInfoType = {
bbox: [number, number, number, number];
last_active: string;
num_contributors: number | null;
new_geom_type: NewGeomTypes;
};

export type taskType = {
Expand Down
6 changes: 5 additions & 1 deletion src/frontend/src/store/types/ICreateProject.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { task_split_type } from '@/types/enums';
import { task_split_type, NewGeomTypes } from '@/types/enums';

export type CreateProjectStateTypes = {
editProjectDetails: ProjectDetailsTypes;
Expand Down Expand Up @@ -36,6 +36,9 @@ export type CreateProjectStateTypes = {
customFileValidity: boolean;
additionalFeatureGeojson: GeoJSONFeatureTypes | null;
descriptionToFocus: string | null;
task_num_buildings: number | null;
task_split_dimension: number | null;
new_geom_type: NewGeomTypes;
};
export type ValidateCustomFormResponse = {
detail: { message: string; possible_reason: string };
Expand Down Expand Up @@ -108,6 +111,7 @@ export type ProjectDetailsTypes = {
hasCustomTMS: boolean;
customFormUpload: any;
hasAdditionalFeature: boolean;
new_geom_type: NewGeomTypes;
};

export type FormCategoryListTypes = {
Expand Down
6 changes: 6 additions & 0 deletions src/frontend/src/types/enums.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,9 @@ export enum user_roles {
MAPPER = 'MAPPER',
ADMIN = 'ADMIN',
}

export type NewGeomTypes = {
POINT: 'POINT';
POLYGON: 'POLYGON';
LINESTRING: 'LINESTRING';
};
4 changes: 2 additions & 2 deletions src/mapper/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "fmtm-mapper",
"version": "0.0.1",
"version": "2024.5.0",
"type": "module",
"private": true,
"scripts": {
Expand Down Expand Up @@ -53,7 +53,7 @@
"@turf/buffer": "^7.1.0",
"@turf/centroid": "^7.1.0",
"@turf/helpers": "^7.1.0",
"@watergis/maplibre-gl-terradraw": "^0.5.1",
"@watergis/maplibre-gl-terradraw": "^0.8.1",
"drizzle-orm": "^0.35.3",
"flatgeobuf": "^3.36.0",
"maplibre-gl": "^4.7.1",
Expand Down
18 changes: 11 additions & 7 deletions src/mapper/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions src/mapper/src/constants/enums.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,9 @@ export enum projectSetupStep {
'task_selection' = 2,
'complete_setup' = 3,
}

export enum NewGeomTypes {
POINT = 'POINT',
POLYGON = 'POLYGON',
LINESTRING = 'LINESTRING',
}
19 changes: 11 additions & 8 deletions src/mapper/src/lib/components/map/main.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,12 @@
ControlButton,
} from 'svelte-maplibre';
import maplibre from 'maplibre-gl';
import MaplibreTerradrawControl from '@watergis/maplibre-gl-terradraw';
import { MaplibreTerradrawControl } from '@watergis/maplibre-gl-terradraw';
import { Protocol } from 'pmtiles';
import { polygon } from '@turf/helpers';
import { buffer } from '@turf/buffer';
import { bbox } from '@turf/bbox';
import { centroid } from '@turf/centroid';
import type { Position, Geometry as GeoJSONGeometry, FeatureCollection } from 'geojson';
import LocationArcImg from '$assets/images/locationArc.png';
Expand All @@ -40,10 +41,10 @@
// import { entityFeatcolStore, selectedEntityId } from '$store/entities';
import { readFileFromOPFS } from '$lib/fs/opfs.ts';
import { loadOfflinePmtiles } from '$lib/utils/basemaps.ts';
import { projectSetupStep as projectSetupStepEnum } from '$constants/enums.ts';
import { projectSetupStep as projectSetupStepEnum, NewGeomTypes } from '$constants/enums.ts';
import { baseLayers, osmStyle, pmtilesStyle } from '$constants/baseLayers.ts';
import { getEntitiesStatusStore } from '$store/entities.svelte.ts';
import { centroid } from '@turf/centroid';
type bboxType = [number, number, number, number];
Expand All @@ -54,6 +55,7 @@
projectId: number;
setMapRef: (map: maplibregl.Map | undefined) => void;
draw?: boolean;
drawGeomType: NewGeomTypes | undefined;
handleDrawnGeom?: ((geojson: GeoJSONGeometry) => void) | null;
}
Expand All @@ -64,6 +66,7 @@
projectId,
setMapRef,
draw = false,
drawGeomType,
handleDrawnGeom,
}: Props = $props();
Expand Down Expand Up @@ -118,12 +121,12 @@
// }
// })
let displayDrawHelpText: boolean = $state(false);
type DrawModeOptions = 'point' | 'linestring' | 'delete-selection' | 'polygon';
const currentDrawMode: DrawModeOptions = drawGeomType ? drawGeomType.toLowerCase() as DrawModeOptions : 'point';
const drawControl = new MaplibreTerradrawControl({
modes: [
'point',
// 'polygon',
// 'linestring',
// 'delete',
currentDrawMode,
// 'delete-selection'
],
// Note We do not open the toolbar options, allowing the user
// to simply click with a pre-defined mode active
Expand Down Expand Up @@ -220,7 +223,7 @@
const drawInstance = drawControl.getTerraDrawInstance();
if (drawInstance && handleDrawnGeom) {
drawInstance.start();
drawInstance.setMode('point');
drawInstance.setMode(currentDrawMode);
drawInstance.on('finish', (id: string, _context: any) => {
// Save the drawn geometry location, then delete all geoms from store
Expand Down
2 changes: 1 addition & 1 deletion src/mapper/src/lib/odk/javarosa.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,5 @@ export function geojsonGeomToJavarosa(geometry: GeoJSONGeometry) {
.join(';');

// Must append a final ; to finish the geom
return javarosaGeometry;
return `${javarosaGeometry};`;
}
2 changes: 2 additions & 0 deletions src/mapper/src/lib/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { UUID } from 'crypto';
import type { Polygon } from 'geojson';
import type { NewGeomTypes } from '$constants/enums.ts';

export type ProjectTask = {
id: number;
Expand Down Expand Up @@ -40,6 +41,7 @@ export interface ProjectData {
status: number;
hashtags: string[];
tasks: ProjectTask[];
new_geom_type: NewGeomTypes;
}

export interface ZoomToTaskEventDetail {
Expand Down
1 change: 1 addition & 0 deletions src/mapper/src/routes/[projectId]/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@
projectId={data.projectId}
entitiesUrl={data.project.data_extract_url}
draw={isDrawEnabled}
drawGeomType={data.project.new_geom_type}
handleDrawnGeom={(geom) => {
isDrawEnabled = false;
openOdkCollectNewFeature(data?.project?.odk_form_id, geom);
Expand Down

0 comments on commit 402dd8f

Please sign in to comment.