forked from MapComplete/MapComplete
Refactoring of GPS-location (uses featureSource too now), factoring out state, add ReplaceGeometryAction and conflation example
This commit is contained in:
parent
1db54f3c8e
commit
2484848cd6
37 changed files with 1035 additions and 467 deletions
|
@ -2,30 +2,103 @@ 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";
|
||||
import {TagsFilter} from "../../Tags/TagsFilter";
|
||||
import OsmChangeAction from "../../Osm/Actions/OsmChangeAction";
|
||||
import StaticFeatureSource from "../Sources/StaticFeatureSource";
|
||||
import {OsmConnection} from "../../Osm/OsmConnection";
|
||||
import {GeoOperations} from "../../GeoOperations";
|
||||
import {Utils} from "../../../Utils";
|
||||
import {UIEventSource} from "../../UIEventSource";
|
||||
import {BBox} from "../../BBox";
|
||||
import FeaturePipeline from "../FeaturePipeline";
|
||||
import {Tag} from "../../Tags/Tag";
|
||||
import LayoutConfig from "../../../Models/ThemeConfig/LayoutConfig";
|
||||
import {ChangeDescription} from "../../Osm/Actions/ChangeDescription";
|
||||
import CreateNewNodeAction from "../../Osm/Actions/CreateNewNodeAction";
|
||||
import ChangeTagAction from "../../Osm/Actions/ChangeTagAction";
|
||||
import {And} from "../../Tags/And";
|
||||
|
||||
|
||||
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
|
||||
|
||||
private readonly layer: FilteredLayer
|
||||
|
||||
constructor(
|
||||
state: {
|
||||
readonly filteredLayers: UIEventSource<FilteredLayer[]>},
|
||||
osmFeatureSource: { rawDataHandlers: ((data: any, tileId: number) => void)[] },
|
||||
layer: FilteredLayer,
|
||||
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"
|
||||
this.layer = layer;
|
||||
if (this.layer === undefined) {
|
||||
throw "Layer is undefined"
|
||||
}
|
||||
const self = this
|
||||
osmFeatureSource.rawDataHandlers.push((osmJson, tileId) => self.handleOsmXml(osmJson, tileId))
|
||||
}
|
||||
|
||||
private handleOsmXml(osmJson: any, tileId: number) {
|
||||
/**
|
||||
* Given a list of coordinates, will search already existing OSM-points to snap onto.
|
||||
* Either the geometry will be moved OR the existing point will be moved, depending on configuration and tags.
|
||||
* This requires the 'type_node'-layer to be activated
|
||||
*/
|
||||
public static MergePoints(
|
||||
state: {
|
||||
filteredLayers: UIEventSource<FilteredLayer[]>,
|
||||
featurePipeline: FeaturePipeline,
|
||||
layoutToUse: LayoutConfig
|
||||
},
|
||||
newGeometryLngLats: [number, number][],
|
||||
configs: ConflationConfig[],
|
||||
) {
|
||||
const typeNode = state.filteredLayers.data.filter(l => l.layerDef.id === "type_node")[0]
|
||||
if (typeNode === undefined) {
|
||||
throw "Type Node layer is not defined. Add 'type_node' as layer to your layerconfig to use this feature"
|
||||
}
|
||||
|
||||
const bbox = new BBox(newGeometryLngLats)
|
||||
const bbox_padded = bbox.pad(1.2)
|
||||
const allNodes: any[] = [].concat(...state.featurePipeline.GetFeaturesWithin("type_node", bbox).map(tile => tile.filter(
|
||||
feature => bbox_padded.contains(GeoOperations.centerpointCoordinates(feature))
|
||||
)))
|
||||
// The strategy: for every point of the new geometry, we search a point that is closeby and matches
|
||||
// If multiple options match, we choose the most optimal (aka closest)
|
||||
|
||||
const maxDistance = Math.max(...configs.map(c => c.withinRangeOfM))
|
||||
for (const coordinate of newGeometryLngLats) {
|
||||
|
||||
let closestNode = undefined;
|
||||
let closestNodeDistance = undefined
|
||||
for (const node of allNodes) {
|
||||
const d = GeoOperations.distanceBetween(GeoOperations.centerpointCoordinates(node), coordinate)
|
||||
if (d > maxDistance) {
|
||||
continue
|
||||
}
|
||||
let matchesSomeConfig = false
|
||||
for (const config of configs) {
|
||||
if (d > config.withinRangeOfM) {
|
||||
continue
|
||||
}
|
||||
if (!config.ifMatches.matchesProperties(node.properties)) {
|
||||
continue
|
||||
}
|
||||
matchesSomeConfig = true;
|
||||
}
|
||||
if (!matchesSomeConfig) {
|
||||
continue
|
||||
}
|
||||
if (closestNode === undefined || closestNodeDistance > d) {
|
||||
closestNode = node;
|
||||
closestNodeDistance = d;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
public handleOsmJson(osmJson: any, tileId: number) {
|
||||
|
||||
const allObjects = OsmObject.ParseObjects(osmJson.elements)
|
||||
const nodesById = new Map<number, OsmNode>()
|
||||
|
@ -57,7 +130,7 @@ export default class FullNodeDatabaseSource implements TileHierarchy<FeatureSour
|
|||
})
|
||||
const now = new Date()
|
||||
const asGeojsonFeatures = Array.from(nodesById.values()).map(osmNode => ({
|
||||
feature: osmNode.asGeoJson(),freshness: now
|
||||
feature: osmNode.asGeoJson(), freshness: now
|
||||
}))
|
||||
|
||||
const featureSource = new SimpleFeatureSource(this.layer, tileId)
|
||||
|
@ -66,5 +139,12 @@ export default class FullNodeDatabaseSource implements TileHierarchy<FeatureSour
|
|||
this.onTileLoaded(featureSource)
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
export interface ConflationConfig {
|
||||
withinRangeOfM: number,
|
||||
ifMatches: TagsFilter,
|
||||
mode: "reuse_osm_point" | "move_osm_point"
|
||||
}
|
|
@ -68,7 +68,7 @@ export default class OsmFeatureSource {
|
|||
console.log("Tile download", Tiles.tile_from_index(neededTile).join("/"), "started")
|
||||
self.downloadedTiles.add(neededTile)
|
||||
self.LoadTile(...Tiles.tile_from_index(neededTile)).then(_ => {
|
||||
console.log("Tile ", Tiles.tile_from_index(neededTile).join("/"), "loaded")
|
||||
console.debug("Tile ", Tiles.tile_from_index(neededTile).join("/"), "loaded")
|
||||
})
|
||||
}
|
||||
} catch (e) {
|
||||
|
@ -98,7 +98,7 @@ export default class OsmFeatureSource {
|
|||
console.log("Attempting to get tile", z, x, y, "from the osm api")
|
||||
const osmJson = await Utils.downloadJson(url)
|
||||
try {
|
||||
console.log("Got tile", z, x, y, "from the osm api")
|
||||
console.debug("Got tile", z, x, y, "from the osm api")
|
||||
this.rawDataHandlers.forEach(handler => handler(osmJson, Tiles.tile_index(z, x, y)))
|
||||
const geojson = OsmToGeoJson.default(osmJson,
|
||||
// @ts-ignore
|
||||
|
@ -110,10 +110,8 @@ export default class OsmFeatureSource {
|
|||
// We only keep what is needed
|
||||
|
||||
geojson.features = geojson.features.filter(feature => this.allowedTags.matchesProperties(feature.properties))
|
||||
|
||||
geojson.features.forEach(f => f.properties["_backend"] = this._backend)
|
||||
|
||||
console.log("Tile geojson:", z, x, y, "is", geojson)
|
||||
const index = Tiles.tile_index(z, x, y);
|
||||
new PerLayerFeatureSourceSplitter(this.filteredLayers,
|
||||
this.handleTile,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue