Add the possibility to snap onto another layer with imports, add location confirm on input, add metalayer exporting all nodes, various fixes

This commit is contained in:
Pieter Vander Vennet 2021-10-31 02:08:39 +01:00
parent f5d6441b70
commit 23ae9d39c8
24 changed files with 807 additions and 390 deletions

View file

@ -26,6 +26,7 @@ import {OsmConnection} from "../Osm/OsmConnection";
import {Tiles} from "../../Models/TileRange";
import TileFreshnessCalculator from "./TileFreshnessCalculator";
import {ElementStorage} from "../ElementStorage";
import FullNodeDatabaseSource from "./TiledFeatureSource/FullNodeDatabaseSource";
/**
@ -146,6 +147,11 @@ export default class FeaturePipeline {
this.freshnesses.set(id, new TileFreshnessCalculator())
if(id === "type_node"){
// Handles by the 'FullNodeDatabaseSource'
continue;
}
if (source.geojsonSource === undefined) {
// This is an OSM layer
// We load the cached values and register them
@ -220,6 +226,14 @@ export default class FeaturePipeline {
self.freshnesses.get(flayer.layerDef.id).addTileLoad(tileId, new Date())
})
})
if(state.layoutToUse.trackAllNodes){
new FullNodeDatabaseSource(state, osmFeatureSource, tile => {
new RegisteringAllFromFeatureSourceActor(tile)
perLayerHierarchy.get(tile.layer.layerDef.id).registerTile(tile)
tile.features.addCallbackAndRunD(_ => self.newDataLoadedSignal.setData(tile))
})
}
const updater = this.initOverpassUpdater(state, useOsmApi)

View file

@ -1,8 +1,6 @@
import {UIEventSource} from "../../UIEventSource";
import FilteredLayer from "../../../Models/FilteredLayer";
import {FeatureSourceForLayer, Tiled} from "../FeatureSource";
import {Utils} from "../../../Utils";
import {Tiles} from "../../../Models/TileRange";
import {BBox} from "../../BBox";
export default class SimpleFeatureSource implements FeatureSourceForLayer, Tiled {

View file

@ -0,0 +1,70 @@
import TileHierarchy from "./TileHierarchy";
import FeatureSource, {FeatureSourceForLayer, Tiled} from "../FeatureSource";
import {OsmNode, OsmObject, OsmWay} from "../../Osm/OsmObject";
import SimpleFeatureSource from "../Sources/SimpleFeatureSource";
import {UIEventSource} from "../../UIEventSource";
import FilteredLayer from "../../../Models/FilteredLayer";
export default class FullNodeDatabaseSource implements TileHierarchy<FeatureSource & Tiled> {
public readonly loadedTiles = new Map<number, FeatureSource & Tiled>()
private readonly onTileLoaded: (tile: (Tiled & FeatureSourceForLayer)) => void;
private readonly layer : FilteredLayer
constructor(
state: {
readonly filteredLayers: UIEventSource<FilteredLayer[]>},
osmFeatureSource: { rawDataHandlers: ((data: any, tileId: number) => void)[] },
onTileLoaded: ((tile: Tiled & FeatureSourceForLayer) => void)) {
this.onTileLoaded = onTileLoaded
this.layer = state.filteredLayers.data.filter(l => l.layerDef.id === "type_node")[0]
if(this.layer === undefined){
throw "Weird: tracking all nodes, but layer 'type_node' is not defined"
}
const self = this
osmFeatureSource.rawDataHandlers.push((osmJson, tileId) => self.handleOsmXml(osmJson, tileId))
}
private handleOsmXml(osmJson: any, tileId: number) {
const allObjects = OsmObject.ParseObjects(osmJson.elements)
const nodesById = new Map<number, OsmNode>()
for (const osmObj of allObjects) {
if (osmObj.type !== "node") {
continue
}
const osmNode = <OsmNode>osmObj;
nodesById.set(osmNode.id, osmNode)
}
const parentWaysByNodeId = new Map<number, OsmWay[]>()
for (const osmObj of allObjects) {
if (osmObj.type !== "way") {
continue
}
const osmWay = <OsmWay>osmObj;
for (const nodeId of osmWay.nodes) {
if (!parentWaysByNodeId.has(nodeId)) {
parentWaysByNodeId.set(nodeId, [])
}
parentWaysByNodeId.get(nodeId).push(osmWay)
}
}
parentWaysByNodeId.forEach((allWays, nodeId) => {
nodesById.get(nodeId).tags["parent_ways"] = JSON.stringify(allWays.map(w => w.tags))
})
const now = new Date()
const asGeojsonFeatures = Array.from(nodesById.values()).map(osmNode => ({
feature: osmNode.asGeoJson(),freshness: now
}))
const featureSource = new SimpleFeatureSource(this.layer, tileId)
featureSource.features.setData(asGeojsonFeatures)
this.loadedTiles.set(tileId, featureSource)
this.onTileLoaded(featureSource)
}
}

View file

@ -30,6 +30,8 @@ export default class OsmFeatureSource {
};
public readonly downloadedTiles = new Set<number>()
private readonly allowedTags: TagsFilter;
public rawDataHandlers: ((osmJson: any, tileId: number) => void)[] = []
constructor(options: {
handleTile: (tile: FeatureSourceForLayer & Tiled) => void;
@ -94,11 +96,11 @@ export default class OsmFeatureSource {
try {
console.log("Attempting to get tile", z, x, y, "from the osm api")
const osmXml = await Utils.download(url, {"accept": "application/xml"})
const osmJson = await Utils.downloadJson(url)
try {
const parsed = new DOMParser().parseFromString(osmXml, "text/xml");
console.log("Got tile", z, x, y, "from the osm api")
const geojson = OsmToGeoJson.default(parsed,
this.rawDataHandlers.forEach(handler => handler(osmJson, Tiles.tile_index(z, x, y)))
const geojson = OsmToGeoJson.default(osmJson,
// @ts-ignore
{
flatProperties: true

View file

@ -235,6 +235,13 @@ export class GeoOperations {
* @param point Point defined as [lon, lat]
*/
public static nearestPoint(way, point: [number, number]) {
if(way.geometry.type === "Polygon"){
way = {...way}
way.geometry = {...way.geometry}
way.geometry.type = "LineString"
way.geometry.coordinates = way.geometry.coordinates[0]
}
return turf.nearestPointOnLine(way, point, {units: "kilometers"});
}

View file

@ -4,23 +4,40 @@ import {Changes} from "../Changes";
import {Tag} from "../../Tags/Tag";
import CreateNewNodeAction from "./CreateNewNodeAction";
import {And} from "../../Tags/And";
import {TagsFilter} from "../../Tags/TagsFilter";
export default class CreateNewWayAction extends OsmChangeAction {
public newElementId: string = undefined
private readonly coordinates: ({ nodeId?: number, lat: number, lon: number })[];
private readonly tags: Tag[];
private readonly _options: { theme: string };
private readonly _options: {
theme: string, existingPointHandling?: {
withinRangeOfM: number,
ifMatches?: TagsFilter,
mode: "reuse_osm_point" | "move_osm_point"
} []
};
/***
* Creates a new way to upload to OSM
* @param tags: the tags to apply to the wya
* @param coordinates: the coordinates. Might have a nodeId, in this case, this node will be used
* @param options
* @param options
*/
constructor(tags: Tag[], coordinates: ({ nodeId?: number, lat: number, lon: number })[], options: {
theme: string
}) {
constructor(tags: Tag[], coordinates: ({ nodeId?: number, lat: number, lon: number })[],
options: {
theme: string,
/**
* IF specified, an existing OSM-point within this range and satisfying the condition 'ifMatches' will be used instead of a new coordinate.
* If multiple points are possible, only the closest point is considered
*/
existingPointHandling?: {
withinRangeOfM: number,
ifMatches?: TagsFilter,
mode: "reuse_osm_point" | "move_osm_point"
} []
}) {
super()
this.coordinates = coordinates;
this.tags = tags;
@ -49,14 +66,14 @@ export default class CreateNewWayAction extends OsmChangeAction {
// We have all created (or reused) all the points!
// Time to create the actual way
const id = changes.getNewID()
const newWay = <ChangeDescription> {
const newWay = <ChangeDescription>{
id,
type: "way",
meta:{
meta: {
theme: this._options.theme,
changeType: "import"
},
@ -67,7 +84,7 @@ export default class CreateNewWayAction extends OsmChangeAction {
}
}
newElements.push(newWay)
this.newElementId = "way/"+id
this.newElementId = "way/" + id
return newElements
}

View file

@ -206,7 +206,7 @@ export abstract class OsmObject {
return result;
}
private static ParseObjects(elements: any[]): OsmObject[] {
public static ParseObjects(elements: any[]): OsmObject[] {
const objects: OsmObject[] = [];
const allNodes: Map<number, OsmNode> = new Map<number, OsmNode>()