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

Fixes wrong WKT conversion at LOI creation time + Moved code into lib #2057

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
4 changes: 2 additions & 2 deletions functions/src/common/datastore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
*/
type pseudoGeoJsonGeometry = {
type: string;
coordinates: any;

Check warning on line 32 in functions/src/common/datastore.ts

View workflow job for this annotation

GitHub Actions / Check

Unexpected any. Specify a different type
};

/**
Expand Down Expand Up @@ -179,13 +179,13 @@
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) {

Check warning on line 188 in functions/src/common/datastore.ts

View workflow job for this annotation

GitHub Actions / Check

Unexpected any. Specify a different type
return Object.fromEntries(
Object.entries(geometry).map(([key, value]) => [
key,
Expand All @@ -194,7 +194,7 @@
);
}

static toFirestoreValue(value: any): any {

Check warning on line 197 in functions/src/common/datastore.ts

View workflow job for this annotation

GitHub Actions / Check

Unexpected any. Specify a different type

Check warning on line 197 in functions/src/common/datastore.ts

View workflow job for this annotation

GitHub Actions / Check

Unexpected any. Specify a different type
if (value === null) {
return null;
}
Expand Down Expand Up @@ -222,7 +222,7 @@
*
* @returns GeoJSON geometry object (with geometry as list of lists)
*/
static fromFirestoreMap(geoJsonGeometry: any): any {

Check warning on line 225 in functions/src/common/datastore.ts

View workflow job for this annotation

GitHub Actions / Check

Unexpected any. Specify a different type

Check warning on line 225 in functions/src/common/datastore.ts

View workflow job for this annotation

GitHub Actions / Check

Unexpected any. Specify a different type
const geometryObject = geoJsonGeometry as pseudoGeoJsonGeometry;
if (!geometryObject) {
throw new Error(
Expand All @@ -237,7 +237,7 @@
return geometryObject;
}

static fromFirestoreValue(coordinates: any) {

Check warning on line 240 in functions/src/common/datastore.ts

View workflow job for this annotation

GitHub Actions / Check

Unexpected any. Specify a different type
if (coordinates instanceof GeoPoint) {
// Note: GeoJSON coordinates are in lng-lat order.
return [coordinates.longitude, coordinates.latitude];
Expand All @@ -246,7 +246,7 @@
if (typeof coordinates !== 'object') {
return coordinates;
}
const result = new Array<any>(coordinates.length);

Check warning on line 249 in functions/src/common/datastore.ts

View workflow job for this annotation

GitHub Actions / Check

Unexpected any. Specify a different type

Object.entries(coordinates).map(([i, nestedValue]) => {
const index = Number.parseInt(i);
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';
Loading