Skip to content

Commit

Permalink
Fixes wrong WKT conversion at LOI creation time + Moved code into lib (
Browse files Browse the repository at this point in the history
  • Loading branch information
rfontanarosa committed Sep 30, 2024
1 parent af4a78d commit e1b4ea6
Show file tree
Hide file tree
Showing 5 changed files with 95 additions and 67 deletions.
4 changes: 2 additions & 2 deletions functions/src/common/datastore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,10 +179,10 @@ export class Datastore {
async updateLoiProperties(
surveyId: string,
loiId: string,
properties: {[key: string]: string}
loiDoc: DocumentData
) {
const loiRef = this.db_.doc(loi(surveyId, loiId));
await loiRef.update({properties});
await loiRef.update({[l.properties]: loiDoc[l.properties]});
}

static toFirestoreMap(geometry: any) {
Expand Down
60 changes: 3 additions & 57 deletions functions/src/import-geojson.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ import JSONStream from 'jsonstream-ts';
import {canImport} from './common/auth';
import {DecodedIdToken} from 'firebase-admin/auth';
import {GroundProtos} from '@ground/proto';
import {toDocumentData} from '@ground/lib';
import {Feature, GeoJsonProperties, Geometry, Position} from 'geojson';
import {toDocumentData, toGeometryPb} from '@ground/lib';
import {Feature, GeoJsonProperties} from 'geojson';
import {ErrorHandler} from './handlers';

import Pb = GroundProtos.ground.v1beta1;
Expand Down Expand Up @@ -169,7 +169,7 @@ function toLoiPb(
});
}

function toLoiPbProperties(properties: GeoJsonProperties): {
export function toLoiPbProperties(properties: GeoJsonProperties): {
[k: string]: Pb.LocationOfInterest.Property;
} {
return Object.fromEntries(
Expand All @@ -184,57 +184,3 @@ function toLoiPbProperty(value: any): Pb.LocationOfInterest.Property {
: {stringValue: value?.toString() || ''}
);
}

function toGeometryPb(geometry: Geometry): Pb.Geometry {
switch (geometry.type) {
case 'Point':
return toPointGeometry(geometry.coordinates);
case 'Polygon':
return toPolygonGeometry(geometry.coordinates);
case 'MultiPolygon':
return toMultiPolygonGeometry(geometry.coordinates);
default:
throw new Error(`Unsupported GeoJSON type '${geometry.type}'`);
}
}

function toPointGeometry(position: Position): Pb.Geometry {
const coordinates = toCoordinatesPb(position);
const point = new Pb.Point({coordinates});
return new Pb.Geometry({point});
}

function toCoordinatesPb(position: Position): Pb.Coordinates {
const [longitude, latitude] = position;
if (longitude === undefined || latitude === undefined)
throw new Error('Missing coordinate(s)');
return new Pb.Coordinates({longitude, latitude});
}

function toPolygonPb(positions: Position[][]): Pb.Polygon {
const [shellCoords, ...holeCoords] = positions;
// Ignore if shell is missing.
if (!shellCoords)
throw new Error('Missing required polygon shell coordinates');
const shell = toLinearRingPb(shellCoords);
const holes = holeCoords?.map(h => toLinearRingPb(h));
return new Pb.Polygon({shell, holes});
}

function toPolygonGeometry(positions: Position[][]): Pb.Geometry {
const polygon = toPolygonPb(positions);
return new Pb.Geometry({polygon});
}

function toLinearRingPb(positions: Position[]): Pb.LinearRing {
const coordinates = positions.map(p => toCoordinatesPb(p));
return new Pb.LinearRing({coordinates});
}

function toMultiPolygonGeometry(positions: Position[][][]): Pb.Geometry {
// Skip invalid polygons.
const polygons = positions.map(p => toPolygonPb(p));
if (polygons.length === 0) throw new Error('Empty multi-polygon');
const multiPolygon = new Pb.MultiPolygon({polygons});
return new Pb.Geometry({multiPolygon});
}
42 changes: 35 additions & 7 deletions functions/src/on-create-loi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,14 @@ import {QueryDocumentSnapshot} from 'firebase-functions/v1/firestore';
import {getDatastore} from './common/context';
import {Datastore} from './common/datastore';
import {broadcastSurveyUpdate} from './common/broadcast-survey-update';
import {GroundProtos} from '@ground/proto';
import {toDocumentData, toGeoJsonGeometry, toMessage} from '@ground/lib';
import {geojsonToWKT} from '@terraformer/wkt';
import {toLoiPbProperties} from './import-geojson';

import Pb = GroundProtos.ground.v1beta1;

type Properties = {[key: string]: string | number};

type PropertyGenerator = {
name: string;
Expand All @@ -33,17 +40,21 @@ export async function onCreateLoiHandler(
) {
const surveyId = context.params.surveyId;
const loiId = context.params.loiId;
const loi = snapshot.data();
const data = snapshot.data();

if (!loiId || !loi) return;
if (!loiId || !data) return;

const loiPb = toMessage(data, Pb.LocationOfInterest) as Pb.LocationOfInterest;

const geometry = toGeoJsonGeometry(loiPb.geometry!);

const db = getDatastore();

let properties = loi.properties || {};
let properties = propertiesPbToObject(loiPb.properties) || {};

const propertyGenerators = await db.fetchPropertyGenerators();

const wkt = geojsonToWKT(Datastore.fromFirestoreMap(loi.geometry));
const wkt = geojsonToWKT(Datastore.fromFirestoreMap(geometry));

for (const propertyGeneratorDoc of propertyGenerators.docs) {
properties = await updateProperties(
Expand All @@ -57,7 +68,13 @@ export async function onCreateLoiHandler(
.forEach(key => (properties[key] = JSON.stringify(properties[key])));
}

await db.updateLoiProperties(surveyId, loiId, properties);
await db.updateLoiProperties(
surveyId,
loiId,
toDocumentData(
new Pb.LocationOfInterest({properties: toLoiPbProperties(properties)})
)
);

await broadcastSurveyUpdate(context.params.surveyId);
}
Expand All @@ -79,8 +96,6 @@ async function updateProperties(
};
}

type Properties = {[key: string]: string | number};

async function fetchProperties(url: string, wkt: string): Promise<Properties> {
const response = await fetch(url, {
method: 'POST',
Expand Down Expand Up @@ -117,3 +132,16 @@ function removePrefixedKeys(obj: Properties, prefix: string): Properties {
});
return obj;
}

function propertiesPbToObject(pb: {
[k: string]: Pb.LocationOfInterest.IProperty;
}): Properties {
const properties: {[k: string]: string | number} = {};
for (const k of Object.keys(pb)) {
const v = pb[k].stringValue || pb[k].numericValue;
if (v !== null && v !== undefined) {
properties[k] = v;
}
}
return properties;
}
54 changes: 54 additions & 0 deletions lib/src/geo-json.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,57 @@ function toGeoJsonMultiPolygon(multiPolygon: Pb.IMultiPolygon): MultiPolygon {
coordinates: multiPolygon.polygons.map(p => toGeoJsonPolygonCoordinates(p)),
};
}

export function toGeometryPb(geometry: Geometry): Pb.Geometry {
switch (geometry.type) {
case 'Point':
return toPointGeometryPb(geometry.coordinates);
case 'Polygon':
return toPolygonGeometryPb(geometry.coordinates);
case 'MultiPolygon':
return toMultiPolygonGeometryPb(geometry.coordinates);
default:
throw new Error(`Unsupported GeoJSON type '${geometry.type}'`);
}
}

function toPointGeometryPb(position: Position): Pb.Geometry {
const coordinates = toCoordinatesPb(position);
const point = new Pb.Point({coordinates});
return new Pb.Geometry({point});
}

function toCoordinatesPb(position: Position): Pb.Coordinates {
const [longitude, latitude] = position;
if (longitude === undefined || latitude === undefined)
throw new Error('Missing coordinate(s)');
return new Pb.Coordinates({longitude, latitude});
}

function toPolygonPb(positions: Position[][]): Pb.Polygon {
const [shellCoords, ...holeCoords] = positions;
// Ignore if shell is missing.
if (!shellCoords)
throw new Error('Missing required polygon shell coordinates');
const shell = toLinearRingPb(shellCoords);
const holes = holeCoords?.map(h => toLinearRingPb(h));
return new Pb.Polygon({shell, holes});
}

function toPolygonGeometryPb(positions: Position[][]): Pb.Geometry {
const polygon = toPolygonPb(positions);
return new Pb.Geometry({polygon});
}

function toLinearRingPb(positions: Position[]): Pb.LinearRing {
const coordinates = positions.map(p => toCoordinatesPb(p));
return new Pb.LinearRing({coordinates});
}

function toMultiPolygonGeometryPb(positions: Position[][][]): Pb.Geometry {
// Skip invalid polygons.
const polygons = positions.map(p => toPolygonPb(p));
if (polygons.length === 0) throw new Error('Empty multi-polygon');
const multiPolygon = new Pb.MultiPolygon({polygons});
return new Pb.Geometry({multiPolygon});
}
2 changes: 1 addition & 1 deletion lib/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,5 @@
export {toDocumentData} from './proto-to-firestore';
export {toMessage} from './firestore-to-proto';
export {deleteEmpty, isEmpty} from './obj-util';
export {toGeoJsonGeometry} from './geo-json';
export {toGeoJsonGeometry, toGeometryPb} from './geo-json';
export {registry} from './message-registry';

0 comments on commit e1b4ea6

Please sign in to comment.