LayerServer: fix some bugs in build_db-script, decode MVT-tilesource for more features

This commit is contained in:
Pieter Vander Vennet 2024-02-02 20:04:39 +01:00
parent ee3e000cd1
commit 5b318236bf
8 changed files with 327 additions and 103 deletions

View file

@ -1,6 +1,6 @@
import { BBox } from "./BBox"
import * as turf from "@turf/turf"
import { AllGeoJSON, booleanWithin, Coord } from "@turf/turf"
import { AllGeoJSON, booleanWithin, Coord, Lines } from "@turf/turf"
import {
Feature,
FeatureCollection,
@ -156,7 +156,7 @@ export class GeoOperations {
const intersection = GeoOperations.calculateIntersection(
feature,
otherFeature,
featureBBox
featureBBox,
)
if (intersection === null) {
continue
@ -195,7 +195,7 @@ export class GeoOperations {
console.error(
"Could not correctly calculate the overlap of ",
feature,
": unsupported type"
": unsupported type",
)
return result
}
@ -224,7 +224,7 @@ export class GeoOperations {
*/
public static inside(
pointCoordinate: [number, number] | Feature<Point>,
feature: Feature
feature: Feature,
): boolean {
// ray-casting algorithm based on
// http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html
@ -302,7 +302,7 @@ export class GeoOperations {
*/
public static nearestPoint(
way: Feature<LineString>,
point: [number, number]
point: [number, number],
): Feature<
Point,
{
@ -324,11 +324,11 @@ export class GeoOperations {
public static forceLineString(way: Feature<LineString | Polygon>): Feature<LineString>
public static forceLineString(
way: Feature<MultiLineString | MultiPolygon>
way: Feature<MultiLineString | MultiPolygon>,
): Feature<MultiLineString>
public static forceLineString(
way: Feature<LineString | MultiLineString | Polygon | MultiPolygon>
way: Feature<LineString | MultiLineString | Polygon | MultiPolygon>,
): Feature<LineString | MultiLineString> {
if (way.geometry.type === "Polygon") {
way = { ...way }
@ -448,7 +448,7 @@ export class GeoOperations {
*/
public static LineIntersections(
feature: Feature<LineString | MultiLineString | Polygon | MultiPolygon>,
otherFeature: Feature<LineString | MultiLineString | Polygon | MultiPolygon>
otherFeature: Feature<LineString | MultiLineString | Polygon | MultiPolygon>,
): [number, number][] {
return turf
.lineIntersect(feature, otherFeature)
@ -485,7 +485,7 @@ export class GeoOperations {
locations:
| Feature<LineString>
| Feature<Point, { date?: string; altitude?: number | string }>[],
title?: string
title?: string,
) {
title = title?.trim()
if (title === undefined || title === "") {
@ -506,7 +506,7 @@ export class GeoOperations {
type: "Point",
coordinates: p,
},
}
},
)
}
for (const l of locationsWithMeta) {
@ -521,7 +521,7 @@ export class GeoOperations {
trackPoints.push(trkpt)
}
const header =
'<gpx version="1.1" creator="mapcomplete.org" xmlns="http://www.topografix.com/GPX/1/1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd">'
"<gpx version=\"1.1\" creator=\"mapcomplete.org\" xmlns=\"http://www.topografix.com/GPX/1/1\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd\">"
return (
header +
"\n<name>" +
@ -539,7 +539,7 @@ export class GeoOperations {
*/
public static toGpxPoints(
locations: Feature<Point, { date?: string; altitude?: number | string }>[],
title?: string
title?: string,
) {
title = title?.trim()
if (title === undefined || title === "") {
@ -560,7 +560,7 @@ export class GeoOperations {
trackPoints.push(trkpt)
}
const header =
'<gpx version="1.1" creator="mapcomplete.org" xmlns="http://www.topografix.com/GPX/1/1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd">'
"<gpx version=\"1.1\" creator=\"mapcomplete.org\" xmlns=\"http://www.topografix.com/GPX/1/1\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd\">"
return (
header +
"\n<name>" +
@ -648,7 +648,7 @@ export class GeoOperations {
},
},
distanceMeter,
{ units: "meters" }
{ units: "meters" },
).geometry.coordinates
}
@ -683,7 +683,7 @@ export class GeoOperations {
*/
static completelyWithin(
feature: Feature<Geometry, any>,
possiblyEnclosingFeature: Feature<Polygon | MultiPolygon, any>
possiblyEnclosingFeature: Feature<Polygon | MultiPolygon, any>,
): boolean {
return booleanWithin(feature, possiblyEnclosingFeature)
}
@ -714,6 +714,23 @@ export class GeoOperations {
}
return kept
}
if (toSplit.geometry.type === "MultiLineString") {
const lines: Feature<LineString>[][] = toSplit.geometry.coordinates.map(coordinates =>
turf.lineSplit(<LineString> {type: "LineString", coordinates}, boundary).features )
const splitted: Feature<LineString>[] = [].concat(...lines)
const kept: Feature<LineString>[] = []
for (const f of splitted) {
console.log("Checking", f)
if (!GeoOperations.inside(GeoOperations.centerpointCoordinates(f), boundary)) {
continue
}
f.properties = { ...toSplit.properties }
kept.push(f)
}
console.log(">>>", {lines, splitted, kept})
return kept
}
if (toSplit.geometry.type === "Polygon" || toSplit.geometry.type == "MultiPolygon") {
const splitup = turf.intersect(<Feature<Polygon>>toSplit, boundary)
splitup.properties = { ...toSplit.properties }
@ -739,7 +756,7 @@ export class GeoOperations {
*/
public static featureToCoordinateWithRenderingType(
feature: Feature,
location: "point" | "centroid" | "start" | "end" | "projected_centerpoint" | string
location: "point" | "centroid" | "start" | "end" | "projected_centerpoint" | string,
): [number, number] | undefined {
switch (location) {
case "point":
@ -760,7 +777,7 @@ export class GeoOperations {
const centerpoint = GeoOperations.centerpointCoordinates(feature)
const projected = GeoOperations.nearestPoint(
<Feature<LineString>>feature,
centerpoint
centerpoint,
)
return <[number, number]>projected.geometry.coordinates
}
@ -937,7 +954,7 @@ export class GeoOperations {
* GeoOperations.bearingToHuman(46) // => "NE"
*/
public static bearingToHuman(
bearing: number
bearing: number,
): "N" | "NE" | "E" | "SE" | "S" | "SW" | "W" | "NW" {
while (bearing < 0) {
bearing += 360
@ -956,7 +973,7 @@ export class GeoOperations {
* GeoOperations.bearingToHuman(46) // => "NE"
*/
public static bearingToHumanRelative(
bearing: number
bearing: number,
):
| "straight"
| "slight_right"
@ -975,18 +992,73 @@ export class GeoOperations {
return GeoOperations.directionsRelative[segment]
}
/**
* const coors = [[[3.217198532946432,51.218067],[3.216807134449482,51.21849812105347],[3.2164304037883706,51.2189272]],[[3.2176208,51.21760169669458],[3.217198560167068,51.218067]]]
* const f = <any> {geometry: {coordinates: coors}}
* const merged = GeoOperations.attemptLinearize(f)
* merged.geometry.coordinates // => [[3.2176208,51.21760169669458],[3.217198532946432,51.218067], [3.216807134449482,51.21849812105347],[3.2164304037883706,51.2189272]]
*/
static attemptLinearize(multiLineStringFeature: Feature<MultiLineString>): Feature<LineString | MultiLineString> {
const coors = multiLineStringFeature.geometry.coordinates
if(coors.length === 0) {
console.error(multiLineStringFeature.geometry)
throw "Error: got degenerate multilinestring"
}
outer: for (let i = coors.length - 1; i >= 0; i--) {
// We try to match the first element of 'i' with another, earlier list `j`
// If a match is found with `j`, j is extended and `i` is scrapped
const iFirst = coors[i][0]
for (let j = 0; j < coors.length; j++) {
if (i == j) {
continue
}
const jLast = coors[j].at(-1)
if (!(Math.abs(iFirst[0] - jLast[0]) < 0.000001 && Math.abs(iFirst[1] - jLast[1]) < 0.0000001)) {
continue
}
coors[j].splice(coors.length - 1, 1)
coors[j].push(...coors[i])
coors.splice(i, 1)
continue outer
}
}
if(coors.length === 0) {
throw "No more coordinates found"
}
if (coors.length === 1) {
return {
type: "Feature",
properties: multiLineStringFeature.properties,
geometry: {
type: "LineString",
coordinates: coors[0],
},
}
}
return {
type: "Feature",
properties: multiLineStringFeature.properties,
geometry: {
type: "MultiLineString",
coordinates: coors,
},
}
}
/**
* Helper function which does the heavy lifting for 'inside'
*/
private static pointInPolygonCoordinates(
x: number,
y: number,
coordinates: [number, number][][]
coordinates: [number, number][][],
): boolean {
const inside = GeoOperations.pointWithinRing(
x,
y,
/*This is the outer ring of the polygon */ coordinates[0]
/*This is the outer ring of the polygon */ coordinates[0],
)
if (!inside) {
return false
@ -995,7 +1067,7 @@ export class GeoOperations {
const inHole = GeoOperations.pointWithinRing(
x,
y,
coordinates[i] /* These are inner rings, aka holes*/
coordinates[i], /* These are inner rings, aka holes*/
)
if (inHole) {
return false
@ -1033,7 +1105,7 @@ export class GeoOperations {
feature,
otherFeature,
featureBBox: BBox,
otherFeatureBBox?: BBox
otherFeatureBBox?: BBox,
): number {
if (feature.geometry.type === "LineString") {
otherFeatureBBox = otherFeatureBBox ?? BBox.get(otherFeature)
@ -1082,7 +1154,7 @@ export class GeoOperations {
let intersection = turf.lineSlice(
turf.point(intersectionPointsArray[0]),
turf.point(intersectionPointsArray[1]),
feature
feature,
)
if (intersection == null) {
@ -1103,7 +1175,7 @@ export class GeoOperations {
otherFeature,
feature,
otherFeatureBBox,
featureBBox
featureBBox,
)
}
@ -1123,7 +1195,7 @@ export class GeoOperations {
console.log("Applying fallback intersection...")
const intersection = turf.intersect(
turf.truncate(feature),
turf.truncate(otherFeature)
turf.truncate(otherFeature),
)
if (intersection == null) {
return null