forked from MapComplete/MapComplete
		
	LayerServer: fix some bugs in build_db-script, decode MVT-tilesource for more features
This commit is contained in:
		
							parent
							
								
									ee3e000cd1
								
							
						
					
					
						commit
						5b318236bf
					
				
					 8 changed files with 327 additions and 103 deletions
				
			
		| 
						 | 
					@ -7,6 +7,7 @@ import { Or } from "../../src/Logic/Tags/Or"
 | 
				
			||||||
import { RegexTag } from "../../src/Logic/Tags/RegexTag"
 | 
					import { RegexTag } from "../../src/Logic/Tags/RegexTag"
 | 
				
			||||||
import { ValidateThemeEnsemble } from "../../src/Models/ThemeConfig/Conversion/Validation"
 | 
					import { ValidateThemeEnsemble } from "../../src/Models/ThemeConfig/Conversion/Validation"
 | 
				
			||||||
import { AllKnownLayouts } from "../../src/Customizations/AllKnownLayouts"
 | 
					import { AllKnownLayouts } from "../../src/Customizations/AllKnownLayouts"
 | 
				
			||||||
 | 
					import { OsmObject } from "../../src/Logic/Osm/OsmObject"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class LuaSnippets {
 | 
					class LuaSnippets {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -20,6 +21,33 @@ class LuaSnippets {
 | 
				
			||||||
        "end",
 | 
					        "end",
 | 
				
			||||||
    ].join("\n")
 | 
					    ].join("\n")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static isPolygonFeature(): { blacklist: TagsFilter, whitelisted: TagsFilter } {
 | 
				
			||||||
 | 
					        const dict = OsmObject.polygonFeatures
 | 
				
			||||||
 | 
					        const or: TagsFilter[] = []
 | 
				
			||||||
 | 
					        const blacklisted : TagsFilter[] = []
 | 
				
			||||||
 | 
					        dict.forEach(({ values, blacklist }, k) => {
 | 
				
			||||||
 | 
					            if(blacklist){
 | 
				
			||||||
 | 
					                if(values === undefined){
 | 
				
			||||||
 | 
					                    blacklisted.push(new RegexTag(k, /.+/is))
 | 
				
			||||||
 | 
					                    return
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                values.forEach(v => {
 | 
				
			||||||
 | 
					                    blacklisted.push(new RegexTag(k, v))
 | 
				
			||||||
 | 
					                })
 | 
				
			||||||
 | 
					                return
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            if (values === undefined || values === null) {
 | 
				
			||||||
 | 
					                or.push(new RegexTag(k, /.+/is))
 | 
				
			||||||
 | 
					                return
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            values.forEach(v => {
 | 
				
			||||||
 | 
					                or.push(new RegexTag(k, v))
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					        console.log("Polygon features are:", or.map(t => t.asHumanString(false, false, {})))
 | 
				
			||||||
 | 
					        return { blacklist: new Or(blacklisted), whitelisted: new Or(or) }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public static toLuaFilter(tag: TagsFilter, useParens: boolean = false): string {
 | 
					    public static toLuaFilter(tag: TagsFilter, useParens: boolean = false): string {
 | 
				
			||||||
        if (tag instanceof Tag) {
 | 
					        if (tag instanceof Tag) {
 | 
				
			||||||
            return `object.tags["${tag.key}"] == "${tag.value}"`
 | 
					            return `object.tags["${tag.key}"] == "${tag.value}"`
 | 
				
			||||||
| 
						 | 
					@ -55,6 +83,10 @@ class LuaSnippets {
 | 
				
			||||||
            return `object.tags["${tag.key}"] ~= "${tag.value}"`
 | 
					            return `object.tags["${tag.key}"] ~= "${tag.value}"`
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (typeof tag.value === "string" && !tag.invert) {
 | 
				
			||||||
 | 
					            return `object.tags["${tag.key}"] == "${tag.value}"`
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const v = (<RegExp>tag.value).source.replace(/\\\//g, "/")
 | 
					        const v = (<RegExp>tag.value).source.replace(/\\\//g, "/")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if ("" + tag.value === "/.+/is" && !tag.invert) {
 | 
					        if ("" + tag.value === "/.+/is" && !tag.invert) {
 | 
				
			||||||
| 
						 | 
					@ -220,6 +252,7 @@ class GenerateBuildDbScript extends Script {
 | 
				
			||||||
            bodyPolygons.push(this.insertInto(tags, layerId, "polygons_").join("\n"))
 | 
					            bodyPolygons.push(this.insertInto(tags, layerId, "polygons_").join("\n"))
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const isPolygon = LuaSnippets.isPolygonFeature()
 | 
				
			||||||
        return [
 | 
					        return [
 | 
				
			||||||
            "function process_polygon(object, geom)",
 | 
					            "function process_polygon(object, geom)",
 | 
				
			||||||
            "  local matches_filter",
 | 
					            "  local matches_filter",
 | 
				
			||||||
| 
						 | 
					@ -232,7 +265,9 @@ class GenerateBuildDbScript extends Script {
 | 
				
			||||||
            "",
 | 
					            "",
 | 
				
			||||||
            "function osm2pgsql.process_way(object)",
 | 
					            "function osm2pgsql.process_way(object)",
 | 
				
			||||||
            this.earlyAbort(),
 | 
					            this.earlyAbort(),
 | 
				
			||||||
            "  if object.is_closed then",
 | 
					            "  local object_is_line = not object.is_closed or "+LuaSnippets.toLuaFilter(isPolygon.blacklist),
 | 
				
			||||||
 | 
					            `  local object_is_area = object.is_closed and (object.tags["area"] == "yes" or (not object_is_line and ${LuaSnippets.toLuaFilter(isPolygon.whitelisted, true)}))`,
 | 
				
			||||||
 | 
					            "  if object_is_area then",
 | 
				
			||||||
            "    process_polygon(object, object:as_polygon())",
 | 
					            "    process_polygon(object, object:as_polygon())",
 | 
				
			||||||
            "  else",
 | 
					            "  else",
 | 
				
			||||||
            "    process_linestring(object, object:as_linestring())",
 | 
					            "    process_linestring(object, object:as_linestring())",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -59,7 +59,7 @@ export default class LayoutSource extends FeatureSourceMerger {
 | 
				
			||||||
            zoom,
 | 
					            zoom,
 | 
				
			||||||
            featureSwitches
 | 
					            featureSwitches
 | 
				
			||||||
        )//*/
 | 
					        )//*/
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
        const osmApiSource = LayoutSource.setupOsmApiSource(
 | 
					        const osmApiSource = LayoutSource.setupOsmApiSource(
 | 
				
			||||||
            osmLayers,
 | 
					            osmLayers,
 | 
				
			||||||
            bounds,
 | 
					            bounds,
 | 
				
			||||||
| 
						 | 
					@ -67,14 +67,14 @@ export default class LayoutSource extends FeatureSourceMerger {
 | 
				
			||||||
            backend,
 | 
					            backend,
 | 
				
			||||||
            featureSwitches,
 | 
					            featureSwitches,
 | 
				
			||||||
            fullNodeDatabaseSource
 | 
					            fullNodeDatabaseSource
 | 
				
			||||||
        )
 | 
					        )*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const geojsonSources: FeatureSource[] = geojsonlayers.map((l) =>
 | 
					        const geojsonSources: FeatureSource[] = geojsonlayers.map((l) =>
 | 
				
			||||||
            LayoutSource.setupGeojsonSource(l, mapProperties, isDisplayed(l.id))
 | 
					            LayoutSource.setupGeojsonSource(l, mapProperties, isDisplayed(l.id))
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        super(osmApiSource, ...geojsonSources, ...fromCache, ...mvtSources)
 | 
					        super(...geojsonSources, ...fromCache, ...mvtSources)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const self = this
 | 
					        const self = this
 | 
				
			||||||
        function setIsLoading() {
 | 
					        function setIsLoading() {
 | 
				
			||||||
| 
						 | 
					@ -83,7 +83,7 @@ export default class LayoutSource extends FeatureSourceMerger {
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // overpassSource?.runningQuery?.addCallbackAndRun((_) => setIsLoading())
 | 
					        // overpassSource?.runningQuery?.addCallbackAndRun((_) => setIsLoading())
 | 
				
			||||||
        osmApiSource?.isRunning?.addCallbackAndRun((_) => setIsLoading())
 | 
					       // osmApiSource?.isRunning?.addCallbackAndRun((_) => setIsLoading())
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private static setupMvtSource(layer: LayerConfig, mapProperties:  { zoom: Store<number>; bounds: Store<BBox> },    isActive?: Store<boolean>): FeatureSource{
 | 
					    private static setupMvtSource(layer: LayerConfig, mapProperties:  { zoom: Store<number>; bounds: Store<BBox> },    isActive?: Store<boolean>): FeatureSource{
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,5 @@
 | 
				
			||||||
import { Store } from "../../UIEventSource"
 | 
					import { Store } from "../../UIEventSource"
 | 
				
			||||||
import DynamicTileSource, { PolygonSourceMerger } from "./DynamicTileSource"
 | 
					import DynamicTileSource from "./DynamicTileSource"
 | 
				
			||||||
import { Utils } from "../../../Utils"
 | 
					import { Utils } from "../../../Utils"
 | 
				
			||||||
import { BBox } from "../../BBox"
 | 
					import { BBox } from "../../BBox"
 | 
				
			||||||
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"
 | 
					import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"
 | 
				
			||||||
| 
						 | 
					@ -7,6 +7,8 @@ import MvtSource from "../Sources/MvtSource"
 | 
				
			||||||
import { Tiles } from "../../../Models/TileRange"
 | 
					import { Tiles } from "../../../Models/TileRange"
 | 
				
			||||||
import Constants from "../../../Models/Constants"
 | 
					import Constants from "../../../Models/Constants"
 | 
				
			||||||
import FeatureSourceMerger from "../Sources/FeatureSourceMerger"
 | 
					import FeatureSourceMerger from "../Sources/FeatureSourceMerger"
 | 
				
			||||||
 | 
					import { LineSourceMerger } from "./LineSourceMerger"
 | 
				
			||||||
 | 
					import { PolygonSourceMerger } from "./PolygonSourceMerger"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class PolygonMvtSource extends PolygonSourceMerger{
 | 
					class PolygonMvtSource extends PolygonSourceMerger{
 | 
				
			||||||
| 
						 | 
					@ -39,6 +41,36 @@ class PolygonMvtSource extends PolygonSourceMerger{
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class LineMvtSource extends LineSourceMerger{
 | 
				
			||||||
 | 
					    constructor( layer: LayerConfig,
 | 
				
			||||||
 | 
					                 mapProperties: {
 | 
				
			||||||
 | 
					                     zoom: Store<number>
 | 
				
			||||||
 | 
					                     bounds: Store<BBox>
 | 
				
			||||||
 | 
					                 },
 | 
				
			||||||
 | 
					                 options?: {
 | 
				
			||||||
 | 
					                     isActive?: Store<boolean>
 | 
				
			||||||
 | 
					                 }) {
 | 
				
			||||||
 | 
					        const roundedZoom = mapProperties.zoom.mapD(z => Math.min(Math.floor(z/2)*2, 14))
 | 
				
			||||||
 | 
					        super(
 | 
				
			||||||
 | 
					            roundedZoom,
 | 
				
			||||||
 | 
					            layer.minzoom,
 | 
				
			||||||
 | 
					            (zxy) => {
 | 
				
			||||||
 | 
					                const [z, x, y] = Tiles.tile_from_index(zxy)
 | 
				
			||||||
 | 
					                const url = Utils.SubstituteKeys(Constants.VectorTileServer,
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        z, x, y, layer: layer.id,
 | 
				
			||||||
 | 
					                        type: "lines",
 | 
				
			||||||
 | 
					                    })
 | 
				
			||||||
 | 
					                return new MvtSource(url, x, y, z)
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            mapProperties,
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                isActive: options?.isActive,
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class PointMvtSource extends DynamicTileSource {
 | 
					class PointMvtSource extends DynamicTileSource {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    constructor(
 | 
					    constructor(
 | 
				
			||||||
| 
						 | 
					@ -84,9 +116,9 @@ export default class DynamicMvtileSource extends FeatureSourceMerger {
 | 
				
			||||||
            isActive?: Store<boolean>
 | 
					            isActive?: Store<boolean>
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
    ) {
 | 
					    ) {
 | 
				
			||||||
        const roundedZoom = mapProperties.zoom.mapD(z => Math.floor(z))
 | 
					 | 
				
			||||||
        super(
 | 
					        super(
 | 
				
			||||||
            new PointMvtSource(layer, mapProperties, options),
 | 
					            new PointMvtSource(layer, mapProperties, options),
 | 
				
			||||||
 | 
					            new LineMvtSource(layer, mapProperties, options),
 | 
				
			||||||
            new PolygonMvtSource(layer, mapProperties, options)
 | 
					            new PolygonMvtSource(layer, mapProperties, options)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,11 +1,8 @@
 | 
				
			||||||
import { Store, Stores } from "../../UIEventSource"
 | 
					import { Store, Stores } from "../../UIEventSource"
 | 
				
			||||||
import { Tiles } from "../../../Models/TileRange"
 | 
					import { Tiles } from "../../../Models/TileRange"
 | 
				
			||||||
import { BBox } from "../../BBox"
 | 
					import { BBox } from "../../BBox"
 | 
				
			||||||
import { FeatureSource, FeatureSourceForTile } from "../FeatureSource"
 | 
					import { FeatureSource } from "../FeatureSource"
 | 
				
			||||||
import FeatureSourceMerger from "../Sources/FeatureSourceMerger"
 | 
					import FeatureSourceMerger from "../Sources/FeatureSourceMerger"
 | 
				
			||||||
import { Feature } from "geojson"
 | 
					 | 
				
			||||||
import { Utils } from "../../../Utils"
 | 
					 | 
				
			||||||
import { GeoOperations } from "../../GeoOperations"
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/***
 | 
					/***
 | 
				
			||||||
| 
						 | 
					@ -84,68 +81,3 @@ export default class DynamicTileSource<Src extends FeatureSource = FeatureSource
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * The PolygonSourceMerger receives various small pieces of bigger polygons and stitches them together.
 | 
					 | 
				
			||||||
 * This is used to reconstruct polygons of vector tiles
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export class PolygonSourceMerger extends DynamicTileSource<FeatureSourceForTile> {
 | 
					 | 
				
			||||||
    constructor(
 | 
					 | 
				
			||||||
        zoomlevel: Store<number>,
 | 
					 | 
				
			||||||
        minzoom: number,
 | 
					 | 
				
			||||||
        constructSource: (tileIndex: number) => FeatureSourceForTile,
 | 
					 | 
				
			||||||
        mapProperties: {
 | 
					 | 
				
			||||||
            bounds: Store<BBox>
 | 
					 | 
				
			||||||
            zoom: Store<number>
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        options?: {
 | 
					 | 
				
			||||||
            isActive?: Store<boolean>
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
    ) {
 | 
					 | 
				
			||||||
        super(zoomlevel, minzoom, constructSource, mapProperties, options)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    protected addDataFromSources(sources: FeatureSourceForTile[]) {
 | 
					 | 
				
			||||||
        sources = Utils.NoNull(sources)
 | 
					 | 
				
			||||||
        const all: Map<string, Feature> = new Map()
 | 
					 | 
				
			||||||
        const zooms: Map<string, number> = new Map()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        for (const source of sources) {
 | 
					 | 
				
			||||||
            let z = source.z
 | 
					 | 
				
			||||||
            for (const f of source.features.data) {
 | 
					 | 
				
			||||||
                const id = f.properties.id
 | 
					 | 
				
			||||||
                if(id.endsWith("146616907")){
 | 
					 | 
				
			||||||
                    console.log("Horeca totaal")
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                if (!all.has(id)) {
 | 
					 | 
				
			||||||
                    // No other parts of this polygon have been seen before, simply add it
 | 
					 | 
				
			||||||
                    all.set(id, f)
 | 
					 | 
				
			||||||
                    zooms.set(id, z)
 | 
					 | 
				
			||||||
                    continue
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                // A part of this object has been seen before, eventually from a different zoom level
 | 
					 | 
				
			||||||
                const oldV = all.get(id)
 | 
					 | 
				
			||||||
                const oldZ = zooms.get(id)
 | 
					 | 
				
			||||||
                if (oldZ > z) {
 | 
					 | 
				
			||||||
                    // The store contains more detailed information, so we ignore this part which has a lower accuraccy
 | 
					 | 
				
			||||||
                    continue
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                if (oldZ < z) {
 | 
					 | 
				
			||||||
                    // The old value has worse accuracy then what we receive now, we throw it away
 | 
					 | 
				
			||||||
                    all.set(id, f)
 | 
					 | 
				
			||||||
                    zooms.set(id, z)
 | 
					 | 
				
			||||||
                    continue
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                const merged = GeoOperations.union(f, oldV)
 | 
					 | 
				
			||||||
                merged.properties = oldV.properties
 | 
					 | 
				
			||||||
                all.set(id, merged)
 | 
					 | 
				
			||||||
                zooms.set(id, z)
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        const newList = Array.from(all.values())
 | 
					 | 
				
			||||||
        this.features.setData(newList)
 | 
					 | 
				
			||||||
        this._featuresById.setData(all)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,80 @@
 | 
				
			||||||
 | 
					import { FeatureSourceForTile } from "../FeatureSource"
 | 
				
			||||||
 | 
					import { Store } from "../../UIEventSource"
 | 
				
			||||||
 | 
					import { BBox } from "../../BBox"
 | 
				
			||||||
 | 
					import { Utils } from "../../../Utils"
 | 
				
			||||||
 | 
					import { Feature, LineString, MultiLineString, Position } from "geojson"
 | 
				
			||||||
 | 
					import { Tiles } from "../../../Models/TileRange"
 | 
				
			||||||
 | 
					import { GeoOperations } from "../../GeoOperations"
 | 
				
			||||||
 | 
					import DynamicTileSource from "./DynamicTileSource"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * The PolygonSourceMerger receives various small pieces of bigger polygons and stitches them together.
 | 
				
			||||||
 | 
					 * This is used to reconstruct polygons of vector tiles
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export class LineSourceMerger extends DynamicTileSource<FeatureSourceForTile> {
 | 
				
			||||||
 | 
					    private readonly _zoomlevel: Store<number>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    constructor(
 | 
				
			||||||
 | 
					        zoomlevel: Store<number>,
 | 
				
			||||||
 | 
					        minzoom: number,
 | 
				
			||||||
 | 
					        constructSource: (tileIndex: number) => FeatureSourceForTile,
 | 
				
			||||||
 | 
					        mapProperties: {
 | 
				
			||||||
 | 
					            bounds: Store<BBox>
 | 
				
			||||||
 | 
					            zoom: Store<number>
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        options?: {
 | 
				
			||||||
 | 
					            isActive?: Store<boolean>
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    ) {
 | 
				
			||||||
 | 
					        super(zoomlevel, minzoom, constructSource, mapProperties, options)
 | 
				
			||||||
 | 
					        this._zoomlevel = zoomlevel
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected addDataFromSources(sources: FeatureSourceForTile[]) {
 | 
				
			||||||
 | 
					        sources = Utils.NoNull(sources)
 | 
				
			||||||
 | 
					        const all: Map<string, Feature<MultiLineString>> = new Map()
 | 
				
			||||||
 | 
					        const currentZoom = this._zoomlevel?.data ?? 0
 | 
				
			||||||
 | 
					        for (const source of sources) {
 | 
				
			||||||
 | 
					            if(source.z != currentZoom){
 | 
				
			||||||
 | 
					                continue
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            const bboxCoors = Tiles.tile_bounds_lon_lat(source.z, source.x, source.y)
 | 
				
			||||||
 | 
					            const bboxGeo = new BBox(bboxCoors).asGeoJson({})
 | 
				
			||||||
 | 
					            for (const f of source.features.data) {
 | 
				
			||||||
 | 
					                const id = f.properties.id
 | 
				
			||||||
 | 
					                const coordinates : Position[][] = []
 | 
				
			||||||
 | 
					                if(f.geometry.type === "LineString"){
 | 
				
			||||||
 | 
					                    coordinates.push(f.geometry.coordinates)
 | 
				
			||||||
 | 
					                }else if(f.geometry.type === "MultiLineString"){
 | 
				
			||||||
 | 
					                    coordinates.push(...f.geometry.coordinates)
 | 
				
			||||||
 | 
					                }else {
 | 
				
			||||||
 | 
					                    console.error("Invalid geometry type:", f.geometry.type)
 | 
				
			||||||
 | 
					                    continue
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                const oldV = all.get(id)
 | 
				
			||||||
 | 
					                if(!oldV){
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                all.set(id, {
 | 
				
			||||||
 | 
					                    type: "Feature",
 | 
				
			||||||
 | 
					                    properties: f.properties,
 | 
				
			||||||
 | 
					                    geometry:{
 | 
				
			||||||
 | 
					                        type:"MultiLineString",
 | 
				
			||||||
 | 
					                        coordinates
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                })
 | 
				
			||||||
 | 
					                    continue
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                oldV.geometry.coordinates.push(...coordinates)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const keys = Array.from(all.keys())
 | 
				
			||||||
 | 
					        for (const key of keys) {
 | 
				
			||||||
 | 
					            all.set(key, <any> GeoOperations.attemptLinearize(<Feature<MultiLineString>>all.get(key)))
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        const newList = Array.from(all.values())
 | 
				
			||||||
 | 
					        this.features.setData(newList)
 | 
				
			||||||
 | 
					        this._featuresById.setData(all)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,73 @@
 | 
				
			||||||
 | 
					import { FeatureSourceForTile } from "../FeatureSource"
 | 
				
			||||||
 | 
					import { Store } from "../../UIEventSource"
 | 
				
			||||||
 | 
					import { BBox } from "../../BBox"
 | 
				
			||||||
 | 
					import { Utils } from "../../../Utils"
 | 
				
			||||||
 | 
					import { Feature } from "geojson"
 | 
				
			||||||
 | 
					import { GeoOperations } from "../../GeoOperations"
 | 
				
			||||||
 | 
					import DynamicTileSource from "./DynamicTileSource"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * The PolygonSourceMerger receives various small pieces of bigger polygons and stitches them together.
 | 
				
			||||||
 | 
					 * This is used to reconstruct polygons of vector tiles
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export class PolygonSourceMerger extends DynamicTileSource<FeatureSourceForTile> {
 | 
				
			||||||
 | 
					    constructor(
 | 
				
			||||||
 | 
					        zoomlevel: Store<number>,
 | 
				
			||||||
 | 
					        minzoom: number,
 | 
				
			||||||
 | 
					        constructSource: (tileIndex: number) => FeatureSourceForTile,
 | 
				
			||||||
 | 
					        mapProperties: {
 | 
				
			||||||
 | 
					            bounds: Store<BBox>
 | 
				
			||||||
 | 
					            zoom: Store<number>
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        options?: {
 | 
				
			||||||
 | 
					            isActive?: Store<boolean>
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    ) {
 | 
				
			||||||
 | 
					        super(zoomlevel, minzoom, constructSource, mapProperties, options)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected addDataFromSources(sources: FeatureSourceForTile[]) {
 | 
				
			||||||
 | 
					        sources = Utils.NoNull(sources)
 | 
				
			||||||
 | 
					        const all: Map<string, Feature> = new Map()
 | 
				
			||||||
 | 
					        const zooms: Map<string, number> = new Map()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for (const source of sources) {
 | 
				
			||||||
 | 
					            let z = source.z
 | 
				
			||||||
 | 
					            for (const f of source.features.data) {
 | 
				
			||||||
 | 
					                const id = f.properties.id
 | 
				
			||||||
 | 
					                if (id.endsWith("146616907")) {
 | 
				
			||||||
 | 
					                    console.log("Horeca totaal")
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                if (!all.has(id)) {
 | 
				
			||||||
 | 
					                    // No other parts of this polygon have been seen before, simply add it
 | 
				
			||||||
 | 
					                    all.set(id, f)
 | 
				
			||||||
 | 
					                    zooms.set(id, z)
 | 
				
			||||||
 | 
					                    continue
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // A part of this object has been seen before, eventually from a different zoom level
 | 
				
			||||||
 | 
					                const oldV = all.get(id)
 | 
				
			||||||
 | 
					                const oldZ = zooms.get(id)
 | 
				
			||||||
 | 
					                if (oldZ > z) {
 | 
				
			||||||
 | 
					                    // The store contains more detailed information, so we ignore this part which has a lower accuraccy
 | 
				
			||||||
 | 
					                    continue
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                if (oldZ < z) {
 | 
				
			||||||
 | 
					                    // The old value has worse accuracy then what we receive now, we throw it away
 | 
				
			||||||
 | 
					                    all.set(id, f)
 | 
				
			||||||
 | 
					                    zooms.set(id, z)
 | 
				
			||||||
 | 
					                    continue
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                const merged = GeoOperations.union(f, oldV)
 | 
				
			||||||
 | 
					                merged.properties = oldV.properties
 | 
				
			||||||
 | 
					                all.set(id, merged)
 | 
				
			||||||
 | 
					                zooms.set(id, z)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const newList = Array.from(all.values())
 | 
				
			||||||
 | 
					        this.features.setData(newList)
 | 
				
			||||||
 | 
					        this._featuresById.setData(all)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,6 @@
 | 
				
			||||||
import { BBox } from "./BBox"
 | 
					import { BBox } from "./BBox"
 | 
				
			||||||
import * as turf from "@turf/turf"
 | 
					import * as turf from "@turf/turf"
 | 
				
			||||||
import { AllGeoJSON, booleanWithin, Coord } from "@turf/turf"
 | 
					import { AllGeoJSON, booleanWithin, Coord, Lines } from "@turf/turf"
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
    Feature,
 | 
					    Feature,
 | 
				
			||||||
    FeatureCollection,
 | 
					    FeatureCollection,
 | 
				
			||||||
| 
						 | 
					@ -156,7 +156,7 @@ export class GeoOperations {
 | 
				
			||||||
                const intersection = GeoOperations.calculateIntersection(
 | 
					                const intersection = GeoOperations.calculateIntersection(
 | 
				
			||||||
                    feature,
 | 
					                    feature,
 | 
				
			||||||
                    otherFeature,
 | 
					                    otherFeature,
 | 
				
			||||||
                    featureBBox
 | 
					                    featureBBox,
 | 
				
			||||||
                )
 | 
					                )
 | 
				
			||||||
                if (intersection === null) {
 | 
					                if (intersection === null) {
 | 
				
			||||||
                    continue
 | 
					                    continue
 | 
				
			||||||
| 
						 | 
					@ -195,7 +195,7 @@ export class GeoOperations {
 | 
				
			||||||
        console.error(
 | 
					        console.error(
 | 
				
			||||||
            "Could not correctly calculate the overlap of ",
 | 
					            "Could not correctly calculate the overlap of ",
 | 
				
			||||||
            feature,
 | 
					            feature,
 | 
				
			||||||
            ": unsupported type"
 | 
					            ": unsupported type",
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        return result
 | 
					        return result
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					@ -224,7 +224,7 @@ export class GeoOperations {
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public static inside(
 | 
					    public static inside(
 | 
				
			||||||
        pointCoordinate: [number, number] | Feature<Point>,
 | 
					        pointCoordinate: [number, number] | Feature<Point>,
 | 
				
			||||||
        feature: Feature
 | 
					        feature: Feature,
 | 
				
			||||||
    ): boolean {
 | 
					    ): boolean {
 | 
				
			||||||
        // ray-casting algorithm based on
 | 
					        // ray-casting algorithm based on
 | 
				
			||||||
        // http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html
 | 
					        // http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html
 | 
				
			||||||
| 
						 | 
					@ -302,7 +302,7 @@ export class GeoOperations {
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public static nearestPoint(
 | 
					    public static nearestPoint(
 | 
				
			||||||
        way: Feature<LineString>,
 | 
					        way: Feature<LineString>,
 | 
				
			||||||
        point: [number, number]
 | 
					        point: [number, number],
 | 
				
			||||||
    ): Feature<
 | 
					    ): Feature<
 | 
				
			||||||
        Point,
 | 
					        Point,
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
| 
						 | 
					@ -324,11 +324,11 @@ export class GeoOperations {
 | 
				
			||||||
    public static forceLineString(way: Feature<LineString | Polygon>): Feature<LineString>
 | 
					    public static forceLineString(way: Feature<LineString | Polygon>): Feature<LineString>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public static forceLineString(
 | 
					    public static forceLineString(
 | 
				
			||||||
        way: Feature<MultiLineString | MultiPolygon>
 | 
					        way: Feature<MultiLineString | MultiPolygon>,
 | 
				
			||||||
    ): Feature<MultiLineString>
 | 
					    ): Feature<MultiLineString>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public static forceLineString(
 | 
					    public static forceLineString(
 | 
				
			||||||
        way: Feature<LineString | MultiLineString | Polygon | MultiPolygon>
 | 
					        way: Feature<LineString | MultiLineString | Polygon | MultiPolygon>,
 | 
				
			||||||
    ): Feature<LineString | MultiLineString> {
 | 
					    ): Feature<LineString | MultiLineString> {
 | 
				
			||||||
        if (way.geometry.type === "Polygon") {
 | 
					        if (way.geometry.type === "Polygon") {
 | 
				
			||||||
            way = { ...way }
 | 
					            way = { ...way }
 | 
				
			||||||
| 
						 | 
					@ -448,7 +448,7 @@ export class GeoOperations {
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public static LineIntersections(
 | 
					    public static LineIntersections(
 | 
				
			||||||
        feature: Feature<LineString | MultiLineString | Polygon | MultiPolygon>,
 | 
					        feature: Feature<LineString | MultiLineString | Polygon | MultiPolygon>,
 | 
				
			||||||
        otherFeature: Feature<LineString | MultiLineString | Polygon | MultiPolygon>
 | 
					        otherFeature: Feature<LineString | MultiLineString | Polygon | MultiPolygon>,
 | 
				
			||||||
    ): [number, number][] {
 | 
					    ): [number, number][] {
 | 
				
			||||||
        return turf
 | 
					        return turf
 | 
				
			||||||
            .lineIntersect(feature, otherFeature)
 | 
					            .lineIntersect(feature, otherFeature)
 | 
				
			||||||
| 
						 | 
					@ -485,7 +485,7 @@ export class GeoOperations {
 | 
				
			||||||
        locations:
 | 
					        locations:
 | 
				
			||||||
            | Feature<LineString>
 | 
					            | Feature<LineString>
 | 
				
			||||||
            | Feature<Point, { date?: string; altitude?: number | string }>[],
 | 
					            | Feature<Point, { date?: string; altitude?: number | string }>[],
 | 
				
			||||||
        title?: string
 | 
					        title?: string,
 | 
				
			||||||
    ) {
 | 
					    ) {
 | 
				
			||||||
        title = title?.trim()
 | 
					        title = title?.trim()
 | 
				
			||||||
        if (title === undefined || title === "") {
 | 
					        if (title === undefined || title === "") {
 | 
				
			||||||
| 
						 | 
					@ -506,7 +506,7 @@ export class GeoOperations {
 | 
				
			||||||
                            type: "Point",
 | 
					                            type: "Point",
 | 
				
			||||||
                            coordinates: p,
 | 
					                            coordinates: p,
 | 
				
			||||||
                        },
 | 
					                        },
 | 
				
			||||||
                    }
 | 
					                    },
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        for (const l of locationsWithMeta) {
 | 
					        for (const l of locationsWithMeta) {
 | 
				
			||||||
| 
						 | 
					@ -521,7 +521,7 @@ export class GeoOperations {
 | 
				
			||||||
            trackPoints.push(trkpt)
 | 
					            trackPoints.push(trkpt)
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        const header =
 | 
					        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 (
 | 
					        return (
 | 
				
			||||||
            header +
 | 
					            header +
 | 
				
			||||||
            "\n<name>" +
 | 
					            "\n<name>" +
 | 
				
			||||||
| 
						 | 
					@ -539,7 +539,7 @@ export class GeoOperations {
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public static toGpxPoints(
 | 
					    public static toGpxPoints(
 | 
				
			||||||
        locations: Feature<Point, { date?: string; altitude?: number | string }>[],
 | 
					        locations: Feature<Point, { date?: string; altitude?: number | string }>[],
 | 
				
			||||||
        title?: string
 | 
					        title?: string,
 | 
				
			||||||
    ) {
 | 
					    ) {
 | 
				
			||||||
        title = title?.trim()
 | 
					        title = title?.trim()
 | 
				
			||||||
        if (title === undefined || title === "") {
 | 
					        if (title === undefined || title === "") {
 | 
				
			||||||
| 
						 | 
					@ -560,7 +560,7 @@ export class GeoOperations {
 | 
				
			||||||
            trackPoints.push(trkpt)
 | 
					            trackPoints.push(trkpt)
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        const header =
 | 
					        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 (
 | 
					        return (
 | 
				
			||||||
            header +
 | 
					            header +
 | 
				
			||||||
            "\n<name>" +
 | 
					            "\n<name>" +
 | 
				
			||||||
| 
						 | 
					@ -648,7 +648,7 @@ export class GeoOperations {
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
            distanceMeter,
 | 
					            distanceMeter,
 | 
				
			||||||
            { units: "meters" }
 | 
					            { units: "meters" },
 | 
				
			||||||
        ).geometry.coordinates
 | 
					        ).geometry.coordinates
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -683,7 +683,7 @@ export class GeoOperations {
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    static completelyWithin(
 | 
					    static completelyWithin(
 | 
				
			||||||
        feature: Feature<Geometry, any>,
 | 
					        feature: Feature<Geometry, any>,
 | 
				
			||||||
        possiblyEnclosingFeature: Feature<Polygon | MultiPolygon, any>
 | 
					        possiblyEnclosingFeature: Feature<Polygon | MultiPolygon, any>,
 | 
				
			||||||
    ): boolean {
 | 
					    ): boolean {
 | 
				
			||||||
        return booleanWithin(feature, possiblyEnclosingFeature)
 | 
					        return booleanWithin(feature, possiblyEnclosingFeature)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					@ -714,6 +714,23 @@ export class GeoOperations {
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            return kept
 | 
					            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") {
 | 
					        if (toSplit.geometry.type === "Polygon" || toSplit.geometry.type == "MultiPolygon") {
 | 
				
			||||||
            const splitup = turf.intersect(<Feature<Polygon>>toSplit, boundary)
 | 
					            const splitup = turf.intersect(<Feature<Polygon>>toSplit, boundary)
 | 
				
			||||||
            splitup.properties = { ...toSplit.properties }
 | 
					            splitup.properties = { ...toSplit.properties }
 | 
				
			||||||
| 
						 | 
					@ -739,7 +756,7 @@ export class GeoOperations {
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public static featureToCoordinateWithRenderingType(
 | 
					    public static featureToCoordinateWithRenderingType(
 | 
				
			||||||
        feature: Feature,
 | 
					        feature: Feature,
 | 
				
			||||||
        location: "point" | "centroid" | "start" | "end" | "projected_centerpoint" | string
 | 
					        location: "point" | "centroid" | "start" | "end" | "projected_centerpoint" | string,
 | 
				
			||||||
    ): [number, number] | undefined {
 | 
					    ): [number, number] | undefined {
 | 
				
			||||||
        switch (location) {
 | 
					        switch (location) {
 | 
				
			||||||
            case "point":
 | 
					            case "point":
 | 
				
			||||||
| 
						 | 
					@ -760,7 +777,7 @@ export class GeoOperations {
 | 
				
			||||||
                    const centerpoint = GeoOperations.centerpointCoordinates(feature)
 | 
					                    const centerpoint = GeoOperations.centerpointCoordinates(feature)
 | 
				
			||||||
                    const projected = GeoOperations.nearestPoint(
 | 
					                    const projected = GeoOperations.nearestPoint(
 | 
				
			||||||
                        <Feature<LineString>>feature,
 | 
					                        <Feature<LineString>>feature,
 | 
				
			||||||
                        centerpoint
 | 
					                        centerpoint,
 | 
				
			||||||
                    )
 | 
					                    )
 | 
				
			||||||
                    return <[number, number]>projected.geometry.coordinates
 | 
					                    return <[number, number]>projected.geometry.coordinates
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
| 
						 | 
					@ -937,7 +954,7 @@ export class GeoOperations {
 | 
				
			||||||
     * GeoOperations.bearingToHuman(46) // => "NE"
 | 
					     * GeoOperations.bearingToHuman(46) // => "NE"
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public static bearingToHuman(
 | 
					    public static bearingToHuman(
 | 
				
			||||||
        bearing: number
 | 
					        bearing: number,
 | 
				
			||||||
    ): "N" | "NE" | "E" | "SE" | "S" | "SW" | "W" | "NW" {
 | 
					    ): "N" | "NE" | "E" | "SE" | "S" | "SW" | "W" | "NW" {
 | 
				
			||||||
        while (bearing < 0) {
 | 
					        while (bearing < 0) {
 | 
				
			||||||
            bearing += 360
 | 
					            bearing += 360
 | 
				
			||||||
| 
						 | 
					@ -956,7 +973,7 @@ export class GeoOperations {
 | 
				
			||||||
     * GeoOperations.bearingToHuman(46) // => "NE"
 | 
					     * GeoOperations.bearingToHuman(46) // => "NE"
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public static bearingToHumanRelative(
 | 
					    public static bearingToHumanRelative(
 | 
				
			||||||
        bearing: number
 | 
					        bearing: number,
 | 
				
			||||||
    ):
 | 
					    ):
 | 
				
			||||||
        | "straight"
 | 
					        | "straight"
 | 
				
			||||||
        | "slight_right"
 | 
					        | "slight_right"
 | 
				
			||||||
| 
						 | 
					@ -975,18 +992,73 @@ export class GeoOperations {
 | 
				
			||||||
        return GeoOperations.directionsRelative[segment]
 | 
					        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'
 | 
					     * Helper function which does the heavy lifting for 'inside'
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    private static pointInPolygonCoordinates(
 | 
					    private static pointInPolygonCoordinates(
 | 
				
			||||||
        x: number,
 | 
					        x: number,
 | 
				
			||||||
        y: number,
 | 
					        y: number,
 | 
				
			||||||
        coordinates: [number, number][][]
 | 
					        coordinates: [number, number][][],
 | 
				
			||||||
    ): boolean {
 | 
					    ): boolean {
 | 
				
			||||||
        const inside = GeoOperations.pointWithinRing(
 | 
					        const inside = GeoOperations.pointWithinRing(
 | 
				
			||||||
            x,
 | 
					            x,
 | 
				
			||||||
            y,
 | 
					            y,
 | 
				
			||||||
            /*This is the outer ring of the polygon */ coordinates[0]
 | 
					            /*This is the outer ring of the polygon */ coordinates[0],
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        if (!inside) {
 | 
					        if (!inside) {
 | 
				
			||||||
            return false
 | 
					            return false
 | 
				
			||||||
| 
						 | 
					@ -995,7 +1067,7 @@ export class GeoOperations {
 | 
				
			||||||
            const inHole = GeoOperations.pointWithinRing(
 | 
					            const inHole = GeoOperations.pointWithinRing(
 | 
				
			||||||
                x,
 | 
					                x,
 | 
				
			||||||
                y,
 | 
					                y,
 | 
				
			||||||
                coordinates[i] /* These are inner rings, aka holes*/
 | 
					                coordinates[i], /* These are inner rings, aka holes*/
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
            if (inHole) {
 | 
					            if (inHole) {
 | 
				
			||||||
                return false
 | 
					                return false
 | 
				
			||||||
| 
						 | 
					@ -1033,7 +1105,7 @@ export class GeoOperations {
 | 
				
			||||||
        feature,
 | 
					        feature,
 | 
				
			||||||
        otherFeature,
 | 
					        otherFeature,
 | 
				
			||||||
        featureBBox: BBox,
 | 
					        featureBBox: BBox,
 | 
				
			||||||
        otherFeatureBBox?: BBox
 | 
					        otherFeatureBBox?: BBox,
 | 
				
			||||||
    ): number {
 | 
					    ): number {
 | 
				
			||||||
        if (feature.geometry.type === "LineString") {
 | 
					        if (feature.geometry.type === "LineString") {
 | 
				
			||||||
            otherFeatureBBox = otherFeatureBBox ?? BBox.get(otherFeature)
 | 
					            otherFeatureBBox = otherFeatureBBox ?? BBox.get(otherFeature)
 | 
				
			||||||
| 
						 | 
					@ -1082,7 +1154,7 @@ export class GeoOperations {
 | 
				
			||||||
            let intersection = turf.lineSlice(
 | 
					            let intersection = turf.lineSlice(
 | 
				
			||||||
                turf.point(intersectionPointsArray[0]),
 | 
					                turf.point(intersectionPointsArray[0]),
 | 
				
			||||||
                turf.point(intersectionPointsArray[1]),
 | 
					                turf.point(intersectionPointsArray[1]),
 | 
				
			||||||
                feature
 | 
					                feature,
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (intersection == null) {
 | 
					            if (intersection == null) {
 | 
				
			||||||
| 
						 | 
					@ -1103,7 +1175,7 @@ export class GeoOperations {
 | 
				
			||||||
                    otherFeature,
 | 
					                    otherFeature,
 | 
				
			||||||
                    feature,
 | 
					                    feature,
 | 
				
			||||||
                    otherFeatureBBox,
 | 
					                    otherFeatureBBox,
 | 
				
			||||||
                    featureBBox
 | 
					                    featureBBox,
 | 
				
			||||||
                )
 | 
					                )
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1123,7 +1195,7 @@ export class GeoOperations {
 | 
				
			||||||
                    console.log("Applying fallback intersection...")
 | 
					                    console.log("Applying fallback intersection...")
 | 
				
			||||||
                    const intersection = turf.intersect(
 | 
					                    const intersection = turf.intersect(
 | 
				
			||||||
                        turf.truncate(feature),
 | 
					                        turf.truncate(feature),
 | 
				
			||||||
                        turf.truncate(otherFeature)
 | 
					                        turf.truncate(otherFeature),
 | 
				
			||||||
                    )
 | 
					                    )
 | 
				
			||||||
                    if (intersection == null) {
 | 
					                    if (intersection == null) {
 | 
				
			||||||
                        return null
 | 
					                        return null
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -7,7 +7,7 @@ import { Feature, LineString, Polygon } from "geojson"
 | 
				
			||||||
export abstract class OsmObject {
 | 
					export abstract class OsmObject {
 | 
				
			||||||
    private static defaultBackend = "https://api.openstreetmap.org/"
 | 
					    private static defaultBackend = "https://api.openstreetmap.org/"
 | 
				
			||||||
    protected static backendURL = OsmObject.defaultBackend
 | 
					    protected static backendURL = OsmObject.defaultBackend
 | 
				
			||||||
    private static polygonFeatures = OsmObject.constructPolygonFeatures()
 | 
					    public static polygonFeatures = OsmObject.constructPolygonFeatures()
 | 
				
			||||||
    type: "node" | "way" | "relation"
 | 
					    type: "node" | "way" | "relation"
 | 
				
			||||||
    id: number
 | 
					    id: number
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue