Skip to content

Commit

Permalink
Merge pull request #2986 from bcgov/lines_and_points
Browse files Browse the repository at this point in the history
lines and points
  • Loading branch information
micheal-w-wells authored Nov 24, 2023
2 parents 0844e89 + 28c59d4 commit 6e676a5
Show file tree
Hide file tree
Showing 5 changed files with 138 additions and 57 deletions.
119 changes: 80 additions & 39 deletions api/src/paths/admin-defined-shapes.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
'use strict';

import {RequestHandler} from 'express';
import {Operation} from 'express-openapi';
import {getDBConnection} from '../database/db';
import {getLogger} from '../utils/logger';
import {SQLStatement} from 'sql-template-strings';
import { RequestHandler } from 'express';
import { Operation } from 'express-openapi';
import { getDBConnection } from '../database/db';
import { getLogger } from '../utils/logger';
import { SQLStatement } from 'sql-template-strings';
import {
deleteAdministrativelyDefinedShapesSQL,
getAdministrativelyDefinedShapesSQL
} from '../queries/admin-defined-shapes';
import {atob} from 'js-base64';
import {QueryResult} from 'pg';
import {FeatureCollection} from 'geojson';
import {GeoJSONFromKML, KMZToKML, sanitizeGeoJSON} from '../utils/kml-import';
import {InvasivesRequest} from '../utils/auth-utils';
import {ALL_ROLES, SECURITY_ON} from '../constants/misc';
import {simplifyGeojson} from '../utils/map-shaper-util';
import { atob } from 'js-base64';
import { QueryResult } from 'pg';
import { FeatureCollection } from 'geojson';
import { GeoJSONFromKML, KMZToKML, sanitizeGeoJSON } from '../utils/kml-import';
import { InvasivesRequest } from '../utils/auth-utils';
import { ALL_ROLES, SECURITY_ON } from '../constants/misc';
import { simplifyGeojson } from '../utils/map-shaper-util';

const defaultLog = getLogger('admin-defined-shapes');

Expand All @@ -27,10 +27,10 @@ GET.apiDoc = {
description: 'Fetches a GeoJSON object to display boundaries of administratively-defined shapes (KML uploads)',
security: SECURITY_ON
? [
{
Bearer: ALL_ROLES
}
]
{
Bearer: ALL_ROLES
}
]
: [],
responses: {
200: {
Expand Down Expand Up @@ -59,12 +59,12 @@ GET.apiDoc = {
POST.apiDoc = {
description: 'Creates new Administratively-defined shapes from KML/KMZ data',
security: SECURITY_ON
? [
{
Bearer: ALL_ROLES
}
]
: [],
? [
{
Bearer: ALL_ROLES
}
]
: [],
requestBody: {
description: 'Uploaded KML/KMZ file',
content: {
Expand Down Expand Up @@ -103,10 +103,10 @@ DELETE.apiDoc = {
description: 'deletes new Administratively-defined shapes from KML/KMZ data',
security: SECURITY_ON
? [
{
Bearer: ALL_ROLES
}
]
{
Bearer: ALL_ROLES
}
]
: [],
requestBody: {
description: 'Delete KML/KMZ file',
Expand Down Expand Up @@ -174,25 +174,66 @@ function getAdministrativelyDefinedShapes(): RequestHandler {
// parse the rows from the response
const rows: any[] = (response && response.rows) || [];

// terrible nesting, but returned KMLs should be a small set so should be fine for now
//edited, still could be much more efficient
for (const row of rows) {
let newFeatureArr = [];
for (const feature of row.geojson.features) {
if (feature !== null && feature.coordinates !== null) {
for (const multipolygon of feature.coordinates) {
let convertedFeature = {
const newFeatureArr = [];
row?.geojson?.features?.forEach((feature) => {
if (feature === null) return;

if (feature.type === 'GeometryCollection') {
if (feature?.geometries === null) return;
for (let geometry of feature.geometries) {
let shape = {
type: 'Feature',
properties: {},
geometry: {
type: 'Polygon',
coordinates: multipolygon
type: geometry.type,
coordinates: geometry.coordinates
}
};

newFeatureArr.push(convertedFeature);
newFeatureArr.push(shape);
}
} else {
if (feature?.coordinates === null) return;
for (let coords of feature.coordinates) {
let shape;
switch (feature?.type) {
case 'MultiPoint':
shape = {
type: 'Feature',
properties: {},
geometry: {
type: 'Point',
coordinates: coords
}
};
break;
case 'MultiLineString':
shape = {
type: 'Feature',
properties: {},
geometry: {
type: 'LineString',
coordinates: coords
}
};
break;
case 'MultiPolygon':
shape = {
type: 'Feature',
properties: {},
geometry: {
type: 'Polygon',
coordinates: coords
}
};
break;
}
newFeatureArr.push(shape);
}
}
}
});

row.geojson.features = newFeatureArr;
}

Expand Down Expand Up @@ -227,7 +268,7 @@ function getAdministrativelyDefinedShapes(): RequestHandler {
function uploadShape(): RequestHandler {
return async (req: InvasivesRequest, res) => {
const user_id = req.authContext.user.user_id;
const data = {...req.body};
const data = { ...req.body };
const title = data.title;
let geoJSON: FeatureCollection;

Expand Down Expand Up @@ -358,7 +399,7 @@ function deleteShape(): RequestHandler {
code: 200
});
} catch (error) {
defaultLog.debug({label: 'deleteAdministrativelyDefinedShapes', message: 'error', error});
defaultLog.debug({ label: 'deleteAdministrativelyDefinedShapes', message: 'error', error });
return res.status(500).json({
message: 'Failed to delete administratively defined shape',
request: req.body,
Expand Down
6 changes: 5 additions & 1 deletion api/src/utils/kml-import.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,11 @@ function sanitizeGeoJSON(data: FeatureCollection): FeatureCollection {

// filter out non-polygon features (V1)
const newFeatures = data.features.map((feature) => {
if (feature.geometry.type === 'Polygon') {
if (
feature.geometry.type === 'Polygon' ||
feature.geometry.type === 'LineString' ||
feature.geometry.type === 'Point'
) {
return {
type: feature.type,
geometry: feature.geometry,
Expand Down
43 changes: 28 additions & 15 deletions api/src/utils/map-shaper-util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,30 +29,43 @@ async function simplifyGeojson(data, percentage, returnCallback) {
{ 'in.json': data },
function (err, output) {
if (output) {
let json = output['out.json'];
let parsed = JSON.parse(json);
let parsedEdit = JSON.parse(JSON.stringify(parsed));

delete parsedEdit.features;
let newFeatures = parsed?.features?.map((feature) => {
if (typeof feature.properties === 'object' && feature.properties !== null) {
return feature;
} else {
return { ...feature, properties: {} };
}
let jsonArr = [];
jsonArr.push(output['out.json']);
if (!jsonArr[0]) {
jsonArr = [];
jsonArr.push(output['out1.json']);
jsonArr.push(output['out2.json']);
}

let totalEdit = {
type: 'FeatureCollection',
features: []
};

jsonArr.forEach((json) => {
let parsed = JSON.parse(json);

let newFeatures = parsed?.features?.map((feature) => {
if (typeof feature.properties === 'object' && feature.properties !== null) {
return feature;
} else {
return { ...feature, properties: {} };
}
});

totalEdit.features.push(...newFeatures);
});
parsedEdit.features = [...newFeatures];

returnCallback(JSON.stringify(parsedEdit));
returnCallback(JSON.stringify(totalEdit));
} else {
defaultLog.error({message: 'unspecified failure'});
defaultLog.error({ message: 'unspecified failure' });

return data;
}
}
);
} catch (e) {
defaultLog.error({error: e});
defaultLog.error({ error: e });
}
}

Expand Down
9 changes: 7 additions & 2 deletions appv2/src/UI/Map/LayerPickerBasic.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { LatLngBoundsExpression, LatLngExpression, rectangle } from 'leaflet';
import { circleMarker, LatLngBoundsExpression, LatLngExpression } from 'leaflet';
import React from 'react';
import {
GeoJSON,
Expand Down Expand Up @@ -234,7 +234,12 @@ const dispatch = useDispatch();
return (
<LayersControl.Overlay checked={true} name={'KML/KMZ: ' + boundary.title}>
<LayerGroup>
<GeoJSON data={boundary.geojson} />
<GeoJSON data={boundary.geojson}
pointToLayer={(feature, latlng) => {
return circleMarker(latlng, {
radius: 2
});
}}/>
</LayerGroup>
</LayersControl.Overlay>
);
Expand Down
18 changes: 18 additions & 0 deletions database/src/migrations/0077_geog_column_generic.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Knex } from 'knex';

export async function up(knex: Knex): Promise<void> {
await knex.raw(
`
set search_path=invasivesbc,public;
ALTER TABLE admin_defined_shapes ALTER COLUMN geog TYPE geography(Geometry,4326);
`
);
}

export async function down(knex: Knex): Promise<void> {
await knex.raw(`
set search_path=invasivesbc,public;
`);
}

0 comments on commit 6e676a5

Please sign in to comment.