forked from MapComplete/MapComplete
		
	Add 'CreateNewWayWithNodeReuse'-action, use it in the GRB-theme
This commit is contained in:
		
							parent
							
								
									4e3f408d53
								
							
						
					
					
						commit
						63acca1638
					
				
					 10 changed files with 473 additions and 133 deletions
				
			
		| 
						 | 
					@ -58,10 +58,15 @@ export default class GeoLocationHandler extends VariableUiElement {
 | 
				
			||||||
    private readonly _layoutToUse: LayoutConfig;
 | 
					    private readonly _layoutToUse: LayoutConfig;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    constructor(
 | 
					    constructor(
 | 
				
			||||||
 | 
					        state: {
 | 
				
			||||||
            currentGPSLocation: UIEventSource<Coordinates>,
 | 
					            currentGPSLocation: UIEventSource<Coordinates>,
 | 
				
			||||||
            leafletMap: UIEventSource<any>,
 | 
					            leafletMap: UIEventSource<any>,
 | 
				
			||||||
        layoutToUse: LayoutConfig
 | 
					            layoutToUse: LayoutConfig,
 | 
				
			||||||
 | 
					            featureSwitchGeolocation: UIEventSource<boolean>
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    ) {
 | 
					    ) {
 | 
				
			||||||
 | 
					        const currentGPSLocation = state.currentGPSLocation
 | 
				
			||||||
 | 
					        const leafletMap = state.leafletMap
 | 
				
			||||||
        const hasLocation = currentGPSLocation.map(
 | 
					        const hasLocation = currentGPSLocation.map(
 | 
				
			||||||
            (location) => location !== undefined
 | 
					            (location) => location !== undefined
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
| 
						 | 
					@ -122,7 +127,7 @@ export default class GeoLocationHandler extends VariableUiElement {
 | 
				
			||||||
        this._previousLocationGrant = previousLocationGrant;
 | 
					        this._previousLocationGrant = previousLocationGrant;
 | 
				
			||||||
        this._currentGPSLocation = currentGPSLocation;
 | 
					        this._currentGPSLocation = currentGPSLocation;
 | 
				
			||||||
        this._leafletMap = leafletMap;
 | 
					        this._leafletMap = leafletMap;
 | 
				
			||||||
        this._layoutToUse = layoutToUse;
 | 
					        this._layoutToUse = state.layoutToUse;
 | 
				
			||||||
        this._hasLocation = hasLocation;
 | 
					        this._hasLocation = hasLocation;
 | 
				
			||||||
        const self = this;
 | 
					        const self = this;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -167,7 +172,7 @@ export default class GeoLocationHandler extends VariableUiElement {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const latLonGiven = QueryParameters.wasInitialized("lat") && QueryParameters.wasInitialized("lon")
 | 
					        const latLonGiven = QueryParameters.wasInitialized("lat") && QueryParameters.wasInitialized("lon")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.init(false, !latLonGiven);
 | 
					        this.init(false, !latLonGiven && state.featureSwitchGeolocation.data);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        isLocked.addCallbackAndRunD(isLocked => {
 | 
					        isLocked.addCallbackAndRunD(isLocked => {
 | 
				
			||||||
            if (isLocked) {
 | 
					            if (isLocked) {
 | 
				
			||||||
| 
						 | 
					@ -208,7 +213,7 @@ export default class GeoLocationHandler extends VariableUiElement {
 | 
				
			||||||
  
 | 
					  
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private init(askPermission: boolean, forceZoom: boolean) {
 | 
					    private init(askPermission: boolean, zoomToLocation: boolean) {
 | 
				
			||||||
        const self = this;
 | 
					        const self = this;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (self._isActive.data) {
 | 
					        if (self._isActive.data) {
 | 
				
			||||||
| 
						 | 
					@ -222,7 +227,7 @@ export default class GeoLocationHandler extends VariableUiElement {
 | 
				
			||||||
                ?.then(function (status) {
 | 
					                ?.then(function (status) {
 | 
				
			||||||
                    console.log("Geolocation permission is ", status.state);
 | 
					                    console.log("Geolocation permission is ", status.state);
 | 
				
			||||||
                    if (status.state === "granted") {
 | 
					                    if (status.state === "granted") {
 | 
				
			||||||
                        self.StartGeolocating(forceZoom);
 | 
					                        self.StartGeolocating(zoomToLocation);
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                    self._permission.setData(status.state);
 | 
					                    self._permission.setData(status.state);
 | 
				
			||||||
                    status.onchange = function () {
 | 
					                    status.onchange = function () {
 | 
				
			||||||
| 
						 | 
					@ -234,10 +239,10 @@ export default class GeoLocationHandler extends VariableUiElement {
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (askPermission) {
 | 
					        if (askPermission) {
 | 
				
			||||||
            self.StartGeolocating(forceZoom);
 | 
					            self.StartGeolocating(zoomToLocation);
 | 
				
			||||||
        } else if (this._previousLocationGrant.data === "granted") {
 | 
					        } else if (this._previousLocationGrant.data === "granted") {
 | 
				
			||||||
            this._previousLocationGrant.setData("");
 | 
					            this._previousLocationGrant.setData("");
 | 
				
			||||||
            self.StartGeolocating(forceZoom);
 | 
					            self.StartGeolocating(zoomToLocation);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,21 +3,6 @@ import FeatureSource, {FeatureSourceForLayer, Tiled} from "../FeatureSource";
 | 
				
			||||||
import {OsmNode, OsmObject, OsmWay} from "../../Osm/OsmObject";
 | 
					import {OsmNode, OsmObject, OsmWay} from "../../Osm/OsmObject";
 | 
				
			||||||
import SimpleFeatureSource from "../Sources/SimpleFeatureSource";
 | 
					import SimpleFeatureSource from "../Sources/SimpleFeatureSource";
 | 
				
			||||||
import FilteredLayer from "../../../Models/FilteredLayer";
 | 
					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> {
 | 
					export default class FullNodeDatabaseSource implements TileHierarchy<FeatureSource & Tiled> {
 | 
				
			||||||
| 
						 | 
					@ -35,69 +20,6 @@ export default class FullNodeDatabaseSource implements TileHierarchy<FeatureSour
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * 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) {
 | 
					    public handleOsmJson(osmJson: any, tileId: number) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const allObjects = OsmObject.ParseObjects(osmJson.elements)
 | 
					        const allObjects = OsmObject.ParseObjects(osmJson.elements)
 | 
				
			||||||
| 
						 | 
					@ -143,8 +65,3 @@ export default class FullNodeDatabaseSource implements TileHierarchy<FeatureSour
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface ConflationConfig {
 | 
					 | 
				
			||||||
    withinRangeOfM: number,
 | 
					 | 
				
			||||||
    ifMatches: TagsFilter,
 | 
					 | 
				
			||||||
    mode: "reuse_osm_point" | "move_osm_point"
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -410,6 +410,31 @@ export class GeoOperations {
 | 
				
			||||||
        return undefined;
 | 
					        return undefined;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Tries to remove points which do not contribute much to the general outline.
 | 
				
			||||||
 | 
					     * Points for which the angle is ~ 180° are removed
 | 
				
			||||||
 | 
					     * @param coordinates
 | 
				
			||||||
 | 
					     * @constructor
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public static SimplifyCoordinates(coordinates: [number, number][]){
 | 
				
			||||||
 | 
					        const newCoordinates = []
 | 
				
			||||||
 | 
					        for (let i = 1; i < coordinates.length - 1; i++){
 | 
				
			||||||
 | 
					            const coordinate = coordinates[i];
 | 
				
			||||||
 | 
					            const prev = coordinates[i - 1]
 | 
				
			||||||
 | 
					            const next = coordinates[i + 1]
 | 
				
			||||||
 | 
					            const b0 = turf.bearing(prev, coordinate, {final: true})
 | 
				
			||||||
 | 
					            const b1 = turf.bearing(coordinate, next)
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            const diff = Math.abs(b1 - b0)
 | 
				
			||||||
 | 
					            if(diff < 2){
 | 
				
			||||||
 | 
					                continue
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            newCoordinates.push(coordinate)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return newCoordinates
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										313
									
								
								Logic/Osm/Actions/CreateWayWithPointReuseAction.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										313
									
								
								Logic/Osm/Actions/CreateWayWithPointReuseAction.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,313 @@
 | 
				
			||||||
 | 
					import OsmChangeAction from "./OsmChangeAction";
 | 
				
			||||||
 | 
					import {Tag} from "../../Tags/Tag";
 | 
				
			||||||
 | 
					import {Changes} from "../Changes";
 | 
				
			||||||
 | 
					import {ChangeDescription} from "./ChangeDescription";
 | 
				
			||||||
 | 
					import FeaturePipelineState from "../../State/FeaturePipelineState";
 | 
				
			||||||
 | 
					import {BBox} from "../../BBox";
 | 
				
			||||||
 | 
					import {TagsFilter} from "../../Tags/TagsFilter";
 | 
				
			||||||
 | 
					import {GeoOperations} from "../../GeoOperations";
 | 
				
			||||||
 | 
					import FeatureSource from "../../FeatureSource/FeatureSource";
 | 
				
			||||||
 | 
					import StaticFeatureSource from "../../FeatureSource/Sources/StaticFeatureSource";
 | 
				
			||||||
 | 
					import CreateNewNodeAction from "./CreateNewNodeAction";
 | 
				
			||||||
 | 
					import CreateNewWayAction from "./CreateNewWayAction";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface MergePointConfig {
 | 
				
			||||||
 | 
					    withinRangeOfM: number,
 | 
				
			||||||
 | 
					    ifMatches: TagsFilter,
 | 
				
			||||||
 | 
					    mode: "reuse_osm_point" | "move_osm_point"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface CoordinateInfo {
 | 
				
			||||||
 | 
					    lngLat: [number, number],
 | 
				
			||||||
 | 
					    identicalTo?: number,
 | 
				
			||||||
 | 
					    closebyNodes?: {
 | 
				
			||||||
 | 
					        d: number,
 | 
				
			||||||
 | 
					        node: any,
 | 
				
			||||||
 | 
					        config: MergePointConfig
 | 
				
			||||||
 | 
					    }[]
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * More or less the same as 'CreateNewWay', except that it'll try to reuse already existing points
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export default class CreateWayWithPointReuseAction extends OsmChangeAction {
 | 
				
			||||||
 | 
					    private readonly _tags: Tag[];
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * lngLat-coordinates
 | 
				
			||||||
 | 
					     * @private
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private _coordinateInfo: CoordinateInfo[];
 | 
				
			||||||
 | 
					    private _state: FeaturePipelineState;
 | 
				
			||||||
 | 
					    private _config: MergePointConfig[];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    constructor(tags: Tag[],
 | 
				
			||||||
 | 
					                coordinates: [number, number][],
 | 
				
			||||||
 | 
					                state: FeaturePipelineState,
 | 
				
			||||||
 | 
					                config: MergePointConfig[]
 | 
				
			||||||
 | 
					    ) {
 | 
				
			||||||
 | 
					        super();
 | 
				
			||||||
 | 
					        this._tags = tags;
 | 
				
			||||||
 | 
					        this._state = state;
 | 
				
			||||||
 | 
					        this._config = config;
 | 
				
			||||||
 | 
					        this._coordinateInfo = this.CalculateClosebyNodes(coordinates);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public async getPreview(): Promise<FeatureSource> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const features = []
 | 
				
			||||||
 | 
					        let geometryMoved = false;
 | 
				
			||||||
 | 
					        for (let i = 0; i < this._coordinateInfo.length; i++) {
 | 
				
			||||||
 | 
					            const coordinateInfo = this._coordinateInfo[i];
 | 
				
			||||||
 | 
					            if (coordinateInfo.identicalTo !== undefined) {
 | 
				
			||||||
 | 
					                continue
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            if (coordinateInfo.closebyNodes === undefined || coordinateInfo.closebyNodes.length === 0) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                const newPoint = {
 | 
				
			||||||
 | 
					                    type: "Feature",
 | 
				
			||||||
 | 
					                    properties: {
 | 
				
			||||||
 | 
					                        "newpoint": "yes",
 | 
				
			||||||
 | 
					                        id: "new-geometry-with-reuse-" + i
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    geometry: {
 | 
				
			||||||
 | 
					                        type: "Point",
 | 
				
			||||||
 | 
					                        coordinates: coordinateInfo.lngLat
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					                features.push(newPoint)
 | 
				
			||||||
 | 
					                continue
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            const reusedPoint = coordinateInfo.closebyNodes[0]
 | 
				
			||||||
 | 
					            if (reusedPoint.config.mode === "move_osm_point") {
 | 
				
			||||||
 | 
					                const moveDescription = {
 | 
				
			||||||
 | 
					                    type: "Feature",
 | 
				
			||||||
 | 
					                    properties: {
 | 
				
			||||||
 | 
					                        "move": "yes",
 | 
				
			||||||
 | 
					                        "osm-id": reusedPoint.node.properties.id,
 | 
				
			||||||
 | 
					                        "id": "new-geometry-move-existing" + i
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    geometry: {
 | 
				
			||||||
 | 
					                        type: "LineString",
 | 
				
			||||||
 | 
					                        coordinates: [reusedPoint.node.geometry.coordinates, coordinateInfo.lngLat]
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                features.push(moveDescription)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                // The geometry is moved
 | 
				
			||||||
 | 
					                geometryMoved = true
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (geometryMoved) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            const coords: [number, number][] = []
 | 
				
			||||||
 | 
					            for (const info of this._coordinateInfo) {
 | 
				
			||||||
 | 
					                if (info.identicalTo !== undefined) {
 | 
				
			||||||
 | 
					                    coords.push(coords[info.identicalTo])
 | 
				
			||||||
 | 
					                    continue
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (info.closebyNodes === undefined || info.closebyNodes.length === 0) {
 | 
				
			||||||
 | 
					                    coords.push(coords[info.identicalTo])
 | 
				
			||||||
 | 
					                    continue
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                const closest = info.closebyNodes[0]
 | 
				
			||||||
 | 
					                if (closest.config.mode === "reuse_osm_point") {
 | 
				
			||||||
 | 
					                    coords.push(closest.node.geometry.coordinates)
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    coords.push(info.lngLat)
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            const newGeometry = {
 | 
				
			||||||
 | 
					                type: "Feature",
 | 
				
			||||||
 | 
					                properties: {
 | 
				
			||||||
 | 
					                    "resulting-geometry": "yes",
 | 
				
			||||||
 | 
					                    "id": "new-geometry"
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                geometry: {
 | 
				
			||||||
 | 
					                    type: "LineString",
 | 
				
			||||||
 | 
					                    coordinates: coords
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            features.push(newGeometry)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        console.log("Preview:", features)
 | 
				
			||||||
 | 
					        return new StaticFeatureSource(features, false)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected async CreateChangeDescriptions(changes: Changes): Promise<ChangeDescription[]> {
 | 
				
			||||||
 | 
					        const theme = this._state.layoutToUse.id
 | 
				
			||||||
 | 
					        const allChanges: ChangeDescription[] = []
 | 
				
			||||||
 | 
					        const nodeIdsToUse: { lat: number, lon: number, nodeId?: number }[] = []
 | 
				
			||||||
 | 
					        for (let i = 0; i < this._coordinateInfo.length; i++) {
 | 
				
			||||||
 | 
					            const info = this._coordinateInfo[i]
 | 
				
			||||||
 | 
					            const lat = info.lngLat[1]
 | 
				
			||||||
 | 
					            const lon = info.lngLat[0]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (info.identicalTo !== undefined) {
 | 
				
			||||||
 | 
					                nodeIdsToUse.push(nodeIdsToUse[info.identicalTo])
 | 
				
			||||||
 | 
					                continue
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            if (info.closebyNodes === undefined || info.closebyNodes[0] === undefined) {
 | 
				
			||||||
 | 
					                const newNodeAction = new CreateNewNodeAction([], lat, lon, {
 | 
				
			||||||
 | 
					                    allowReuseOfPreviouslyCreatedPoints: true,
 | 
				
			||||||
 | 
					                    changeType: null,
 | 
				
			||||||
 | 
					                    theme
 | 
				
			||||||
 | 
					                })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                allChanges.push(...(await newNodeAction.CreateChangeDescriptions(changes)))
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                nodeIdsToUse.push({
 | 
				
			||||||
 | 
					                    lat, lon,
 | 
				
			||||||
 | 
					                    nodeId : newNodeAction.newElementIdNumber})
 | 
				
			||||||
 | 
					                continue
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            const closestPoint = info.closebyNodes[0]
 | 
				
			||||||
 | 
					            const id = Number(closestPoint.node.properties.id.split("/")[1])
 | 
				
			||||||
 | 
					            if(closestPoint.config.mode === "move_osm_point"){
 | 
				
			||||||
 | 
					                allChanges.push({
 | 
				
			||||||
 | 
					                    type: "node",
 | 
				
			||||||
 | 
					                    id,
 | 
				
			||||||
 | 
					                    changes: {
 | 
				
			||||||
 | 
					                        lat, lon
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    meta: {
 | 
				
			||||||
 | 
					                        theme,
 | 
				
			||||||
 | 
					                        changeType: null
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                })
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            nodeIdsToUse.push({lat, lon, nodeId: id})
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const newWay = new CreateNewWayAction(this._tags, nodeIdsToUse, {
 | 
				
			||||||
 | 
					            theme
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        allChanges.push(...(await newWay.Perform(changes)))
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        return allChanges
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private CalculateClosebyNodes(coordinates: [number, number][]): CoordinateInfo[] {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const bbox = new BBox(coordinates)
 | 
				
			||||||
 | 
					        const state = this._state
 | 
				
			||||||
 | 
					        const allNodes = [].concat(...state.featurePipeline.GetFeaturesWithin("type_node", bbox.pad(1.2)))
 | 
				
			||||||
 | 
					        const maxDistance = Math.max(...this._config.map(c => c.withinRangeOfM))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const coordinateInfo: {
 | 
				
			||||||
 | 
					            lngLat: [number, number],
 | 
				
			||||||
 | 
					            identicalTo?: number,
 | 
				
			||||||
 | 
					            closebyNodes?: {
 | 
				
			||||||
 | 
					                d: number,
 | 
				
			||||||
 | 
					                node: any,
 | 
				
			||||||
 | 
					                config: MergePointConfig
 | 
				
			||||||
 | 
					            }[]
 | 
				
			||||||
 | 
					        }[] = coordinates.map(_ => undefined)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for (let i = 0; i < coordinates.length; i++) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (coordinateInfo[i] !== undefined) {
 | 
				
			||||||
 | 
					                // Already seen, probably a duplicate coordinate
 | 
				
			||||||
 | 
					                continue
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            const coor = coordinates[i]
 | 
				
			||||||
 | 
					            // Check closeby (and probably identical) point further in the coordinate list, mark them as duplicate
 | 
				
			||||||
 | 
					            for (let j = i + 1; j < coordinates.length; j++) {
 | 
				
			||||||
 | 
					                if (1000 * GeoOperations.distanceBetween(coor, coordinates[j]) < 0.1) {
 | 
				
			||||||
 | 
					                    coordinateInfo[j] = {
 | 
				
			||||||
 | 
					                        lngLat: coor,
 | 
				
			||||||
 | 
					                        identicalTo: i
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    break;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Gather the actual info for this point
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Lets search applicable points and determine the merge mode
 | 
				
			||||||
 | 
					            const closebyNodes: {
 | 
				
			||||||
 | 
					                d: number,
 | 
				
			||||||
 | 
					                node: any,
 | 
				
			||||||
 | 
					                config: MergePointConfig
 | 
				
			||||||
 | 
					            }[] = []
 | 
				
			||||||
 | 
					            for (const node of allNodes) {
 | 
				
			||||||
 | 
					                const center = node.geometry.coordinates
 | 
				
			||||||
 | 
					                const d = 1000 * GeoOperations.distanceBetween(coor, center)
 | 
				
			||||||
 | 
					                if (d > maxDistance) {
 | 
				
			||||||
 | 
					                    continue
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                for (const config of this._config) {
 | 
				
			||||||
 | 
					                    if (d > config.withinRangeOfM) {
 | 
				
			||||||
 | 
					                        continue
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    if (!config.ifMatches.matchesProperties(node.properties)) {
 | 
				
			||||||
 | 
					                        continue
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    closebyNodes.push({node, d, config})
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            closebyNodes.sort((n0, n1) => {
 | 
				
			||||||
 | 
					                return n0.d - n1.d
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            coordinateInfo[i] = {
 | 
				
			||||||
 | 
					                identicalTo: undefined,
 | 
				
			||||||
 | 
					                lngLat: coor,
 | 
				
			||||||
 | 
					                closebyNodes
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let conflictFree = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        do {
 | 
				
			||||||
 | 
					            conflictFree = true;
 | 
				
			||||||
 | 
					            for (let i = 0; i < coordinateInfo.length; i++) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                const coorInfo = coordinateInfo[i]
 | 
				
			||||||
 | 
					                if (coorInfo.identicalTo !== undefined) {
 | 
				
			||||||
 | 
					                    continue
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                if (coorInfo.closebyNodes === undefined || coorInfo.closebyNodes[0] === undefined) {
 | 
				
			||||||
 | 
					                    continue
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                for (let j = i + 1; j < coordinates.length; j++) {
 | 
				
			||||||
 | 
					                    const other = coordinateInfo[j]
 | 
				
			||||||
 | 
					                    if (other.closebyNodes === undefined || other.closebyNodes[0] === undefined) {
 | 
				
			||||||
 | 
					                        continue
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    if (other.closebyNodes[0].node === coorInfo.closebyNodes[0].node) {
 | 
				
			||||||
 | 
					                        conflictFree = false
 | 
				
			||||||
 | 
					                        // We have found a conflict!
 | 
				
			||||||
 | 
					                        // We only keep the closest point
 | 
				
			||||||
 | 
					                        if (other.closebyNodes[0].d > coorInfo.closebyNodes[0].d) {
 | 
				
			||||||
 | 
					                            other.closebyNodes.shift()
 | 
				
			||||||
 | 
					                        } else {
 | 
				
			||||||
 | 
					                            coorInfo.closebyNodes.shift()
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        } while (!conflictFree)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return coordinateInfo
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -19,7 +19,15 @@ export default class ReplaceGeometryAction extends OsmChangeAction {
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
    private readonly wayToReplaceId: string;
 | 
					    private readonly wayToReplaceId: string;
 | 
				
			||||||
    private readonly theme: string;
 | 
					    private readonly theme: string;
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * The target coordinates that should end up in OpenStreetMap
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
    private readonly targetCoordinates: [number, number][];
 | 
					    private readonly targetCoordinates: [number, number][];
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * If a target coordinate is close to another target coordinate, 'identicalTo' will point to the first index.
 | 
				
			||||||
 | 
					     * @private
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private readonly identicalTo: number[]
 | 
				
			||||||
    private readonly newTags: Tag[] | undefined;
 | 
					    private readonly newTags: Tag[] | undefined;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    constructor(
 | 
					    constructor(
 | 
				
			||||||
| 
						 | 
					@ -46,13 +54,36 @@ export default class ReplaceGeometryAction extends OsmChangeAction {
 | 
				
			||||||
        } else if (geom.type === "Polygon") {
 | 
					        } else if (geom.type === "Polygon") {
 | 
				
			||||||
            coordinates = geom.coordinates[0]
 | 
					            coordinates = geom.coordinates[0]
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.identicalTo = coordinates.map(_ => undefined)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for (let i = 0; i < coordinates.length; i++) {
 | 
				
			||||||
 | 
					            if (this.identicalTo[i] !== undefined) {
 | 
				
			||||||
 | 
					                continue
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            for (let j = i + 1; j < coordinates.length; j++) {
 | 
				
			||||||
 | 
					                const d = 1000 * GeoOperations.distanceBetween(coordinates[i], coordinates[j])
 | 
				
			||||||
 | 
					                if (d < 0.1) {
 | 
				
			||||||
 | 
					                    console.log("Identical coordinates detected: ", i, " and ", j, ": ", coordinates[i], coordinates[j], "distance is", d)
 | 
				
			||||||
 | 
					                    this.identicalTo[j] = i
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.targetCoordinates = coordinates
 | 
					        this.targetCoordinates = coordinates
 | 
				
			||||||
        this.newTags = options.newTags
 | 
					        this.newTags = options.newTags
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public async GetPreview(): Promise<FeatureSource> {
 | 
					    public async getPreview(): Promise<FeatureSource> {
 | 
				
			||||||
        const {closestIds, allNodesById} = await this.GetClosestIds();
 | 
					        const {closestIds, allNodesById} = await this.GetClosestIds();
 | 
				
			||||||
 | 
					        console.log("Generating preview, identicals are ", )
 | 
				
			||||||
        const preview = closestIds.map((newId, i) => {
 | 
					        const preview = closestIds.map((newId, i) => {
 | 
				
			||||||
 | 
					            if(this.identicalTo[i] !== undefined){
 | 
				
			||||||
 | 
					                return undefined
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
            if (newId === undefined) {
 | 
					            if (newId === undefined) {
 | 
				
			||||||
                return {
 | 
					                return {
 | 
				
			||||||
                    type: "Feature",
 | 
					                    type: "Feature",
 | 
				
			||||||
| 
						 | 
					@ -80,7 +111,7 @@ export default class ReplaceGeometryAction extends OsmChangeAction {
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            };
 | 
					            };
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
        return new StaticFeatureSource(preview, false)
 | 
					        return new StaticFeatureSource(Utils.NoNull(preview), false)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -92,6 +123,11 @@ export default class ReplaceGeometryAction extends OsmChangeAction {
 | 
				
			||||||
        const {closestIds, osmWay} = await this.GetClosestIds()
 | 
					        const {closestIds, osmWay} = await this.GetClosestIds()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        for (let i = 0; i < closestIds.length; i++) {
 | 
					        for (let i = 0; i < closestIds.length; i++) {
 | 
				
			||||||
 | 
					            if(this.identicalTo[i] !== undefined){
 | 
				
			||||||
 | 
					                const j = this.identicalTo[i]
 | 
				
			||||||
 | 
					                actualIdsToUse.push(actualIdsToUse[j])
 | 
				
			||||||
 | 
					                continue
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
            const closestId = closestIds[i];
 | 
					            const closestId = closestIds[i];
 | 
				
			||||||
            const [lon, lat] = this.targetCoordinates[i]
 | 
					            const [lon, lat] = this.targetCoordinates[i]
 | 
				
			||||||
            if (closestId === undefined) {
 | 
					            if (closestId === undefined) {
 | 
				
			||||||
| 
						 | 
					@ -161,7 +197,7 @@ export default class ReplaceGeometryAction extends OsmChangeAction {
 | 
				
			||||||
    private async GetClosestIds(): Promise<{ closestIds: number[], allNodesById: Map<number, OsmNode>, osmWay: OsmWay }> {
 | 
					    private async GetClosestIds(): Promise<{ closestIds: number[], allNodesById: Map<number, OsmNode>, osmWay: OsmWay }> {
 | 
				
			||||||
        // TODO FIXME: cap move length on points which are embedded into other ways (ev. disconnect them)
 | 
					        // TODO FIXME: cap move length on points which are embedded into other ways (ev. disconnect them)
 | 
				
			||||||
        // TODO FIXME: if a new point has to be created, snap to already existing ways
 | 
					        // TODO FIXME: if a new point has to be created, snap to already existing ways
 | 
				
			||||||
        // TODO FIXME: reuse points if they are the same in the target coordinates
 | 
					        // TODO FIXME: detect intersections with other ways if moved
 | 
				
			||||||
        const splitted = this.wayToReplaceId.split("/");
 | 
					        const splitted = this.wayToReplaceId.split("/");
 | 
				
			||||||
        const type = splitted[0];
 | 
					        const type = splitted[0];
 | 
				
			||||||
        const idN = Number(splitted[1]);
 | 
					        const idN = Number(splitted[1]);
 | 
				
			||||||
| 
						 | 
					@ -185,7 +221,8 @@ export default class ReplaceGeometryAction extends OsmChangeAction {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const closestIds = []
 | 
					        const closestIds = []
 | 
				
			||||||
        const distances = []
 | 
					        const distances = []
 | 
				
			||||||
        for (const target of this.targetCoordinates) {
 | 
					        for (let i = 0; i < this.targetCoordinates.length; i++){
 | 
				
			||||||
 | 
					            const target = this.targetCoordinates[i];
 | 
				
			||||||
            let closestDistance = undefined
 | 
					            let closestDistance = undefined
 | 
				
			||||||
            let closestId = undefined;
 | 
					            let closestId = undefined;
 | 
				
			||||||
            for (const osmNode of allNodes) {
 | 
					            for (const osmNode of allNodes) {
 | 
				
			||||||
| 
						 | 
					@ -202,9 +239,18 @@ export default class ReplaceGeometryAction extends OsmChangeAction {
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Next step: every closestId can only occur once in the list
 | 
					        // Next step: every closestId can only occur once in the list
 | 
				
			||||||
 | 
					        // We skip the ones which are identical
 | 
				
			||||||
 | 
					            console.log("Erasing double ids")
 | 
				
			||||||
        for (let i = 0; i < closestIds.length; i++) {
 | 
					        for (let i = 0; i < closestIds.length; i++) {
 | 
				
			||||||
 | 
					            if(this.identicalTo[i] !== undefined){
 | 
				
			||||||
 | 
					                closestIds[i] = closestIds[this.identicalTo[i]]
 | 
				
			||||||
 | 
					                continue
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
            const closestId = closestIds[i]
 | 
					            const closestId = closestIds[i]
 | 
				
			||||||
            for (let j = i + 1; j < closestIds.length; j++) {
 | 
					            for (let j = i + 1; j < closestIds.length; j++) {
 | 
				
			||||||
 | 
					                if(this.identicalTo[j] !== undefined){
 | 
				
			||||||
 | 
					                    continue
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
                const otherClosestId = closestIds[j]
 | 
					                const otherClosestId = closestIds[j]
 | 
				
			||||||
                if (closestId !== otherClosestId) {
 | 
					                if (closestId !== otherClosestId) {
 | 
				
			||||||
                    continue
 | 
					                    continue
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,7 +2,7 @@ import {Utils} from "../Utils";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default class Constants {
 | 
					export default class Constants {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public static vNumber = "0.12.1-beta";
 | 
					    public static vNumber = "0.12.2-beta";
 | 
				
			||||||
    public static ImgurApiKey = '7070e7167f0a25a'
 | 
					    public static ImgurApiKey = '7070e7167f0a25a'
 | 
				
			||||||
    public static readonly mapillary_client_token_v3 = 'TXhLaWthQ1d4RUg0czVxaTVoRjFJZzowNDczNjUzNmIyNTQyYzI2'
 | 
					    public static readonly mapillary_client_token_v3 = 'TXhLaWthQ1d4RUg0czVxaTVoRjFJZzowNDczNjUzNmIyNTQyYzI2'
 | 
				
			||||||
    public static readonly mapillary_client_token_v4 = "MLY|4441509239301885|b40ad2d3ea105435bd40c7e76993ae85"
 | 
					    public static readonly mapillary_client_token_v4 = "MLY|4441509239301885|b40ad2d3ea105435bd40c7e76993ae85"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -32,6 +32,10 @@ import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeature
 | 
				
			||||||
import ShowDataMultiLayer from "../ShowDataLayer/ShowDataMultiLayer";
 | 
					import ShowDataMultiLayer from "../ShowDataLayer/ShowDataMultiLayer";
 | 
				
			||||||
import BaseLayer from "../../Models/BaseLayer";
 | 
					import BaseLayer from "../../Models/BaseLayer";
 | 
				
			||||||
import ReplaceGeometryAction from "../../Logic/Osm/Actions/ReplaceGeometryAction";
 | 
					import ReplaceGeometryAction from "../../Logic/Osm/Actions/ReplaceGeometryAction";
 | 
				
			||||||
 | 
					import FullNodeDatabaseSource from "../../Logic/FeatureSource/TiledFeatureSource/FullNodeDatabaseSource";
 | 
				
			||||||
 | 
					import CreateWayWithPointReuseAction from "../../Logic/Osm/Actions/CreateWayWithPointReuseAction";
 | 
				
			||||||
 | 
					import OsmChangeAction from "../../Logic/Osm/Actions/OsmChangeAction";
 | 
				
			||||||
 | 
					import FeatureSource from "../../Logic/FeatureSource/FeatureSource";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface ImportButtonState {
 | 
					export interface ImportButtonState {
 | 
				
			||||||
| 
						 | 
					@ -282,7 +286,7 @@ export default class ImportButton extends Toggle {
 | 
				
			||||||
                                            importClicked: UIEventSource<boolean>): BaseUIElement {
 | 
					                                            importClicked: UIEventSource<boolean>): BaseUIElement {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const confirmationMap = Minimap.createMiniMap({
 | 
					        const confirmationMap = Minimap.createMiniMap({
 | 
				
			||||||
            allowMoving: false,
 | 
					            allowMoving: true,
 | 
				
			||||||
            background: o.state.backgroundLayer
 | 
					            background: o.state.backgroundLayer
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
        confirmationMap.SetStyle("height: 20rem; overflow: hidden").SetClass("rounded-xl")
 | 
					        confirmationMap.SetStyle("height: 20rem; overflow: hidden").SetClass("rounded-xl")
 | 
				
			||||||
| 
						 | 
					@ -298,14 +302,14 @@ export default class ImportButton extends Toggle {
 | 
				
			||||||
            layers: o.state.filteredLayers
 | 
					            layers: o.state.filteredLayers
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
 | 
					        let action : OsmChangeAction & {getPreview() : Promise<FeatureSource>}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const theme = o.state.layoutToUse.id
 | 
					        const theme = o.state.layoutToUse.id
 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        const changes = o.state.changes
 | 
					        const changes = o.state.changes
 | 
				
			||||||
        let confirm: () => Promise<string>
 | 
					        let confirm: () => Promise<string>
 | 
				
			||||||
        if (o.conflationSettings !== undefined) {
 | 
					        if (o.conflationSettings !== undefined) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            let replaceGeometryAction = new ReplaceGeometryAction(
 | 
					            action = new ReplaceGeometryAction(
 | 
				
			||||||
                o.state,
 | 
					                o.state,
 | 
				
			||||||
                o.feature,
 | 
					                o.feature,
 | 
				
			||||||
                o.conflationSettings.conflateWayId,
 | 
					                o.conflationSettings.conflateWayId,
 | 
				
			||||||
| 
						 | 
					@ -315,7 +319,43 @@ export default class ImportButton extends Toggle {
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            replaceGeometryAction.GetPreview().then(changePreview => {
 | 
					            confirm = async () => {
 | 
				
			||||||
 | 
					                changes.applyAction (action)
 | 
				
			||||||
 | 
					                return o.feature.properties.id
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            const geom = o.feature.geometry
 | 
				
			||||||
 | 
					            let coordinates: [number, number][]
 | 
				
			||||||
 | 
					            if (geom.type === "LineString") {
 | 
				
			||||||
 | 
					                coordinates = geom.coordinates
 | 
				
			||||||
 | 
					            } else if (geom.type === "Polygon") {
 | 
				
			||||||
 | 
					                coordinates = geom.coordinates[0]
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            action = new CreateWayWithPointReuseAction(
 | 
				
			||||||
 | 
					                o.newTags.data,
 | 
				
			||||||
 | 
					                coordinates,
 | 
				
			||||||
 | 
					                // @ts-ignore
 | 
				
			||||||
 | 
					                o.state,
 | 
				
			||||||
 | 
					                [{
 | 
				
			||||||
 | 
					                    withinRangeOfM: 1,
 | 
				
			||||||
 | 
					                    ifMatches: new Tag("_is_part_of_building","true"),
 | 
				
			||||||
 | 
					                    mode:"move_osm_point"
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                }]
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            confirm = async () => {
 | 
				
			||||||
 | 
					                changes.applyAction(action)
 | 
				
			||||||
 | 
					                return undefined
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        action.getPreview().then(changePreview => {
 | 
				
			||||||
            new ShowDataLayer({
 | 
					            new ShowDataLayer({
 | 
				
			||||||
                leafletMap: confirmationMap.leafletMap,
 | 
					                leafletMap: confirmationMap.leafletMap,
 | 
				
			||||||
                enablePopups: false,
 | 
					                enablePopups: false,
 | 
				
			||||||
| 
						 | 
					@ -326,29 +366,6 @@ export default class ImportButton extends Toggle {
 | 
				
			||||||
            })
 | 
					            })
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            confirm = async () => {
 | 
					 | 
				
			||||||
                changes.applyAction (replaceGeometryAction)
 | 
					 | 
				
			||||||
                return o.feature.properties.id
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            confirm = async () => {
 | 
					 | 
				
			||||||
                const geom = o.feature.geometry
 | 
					 | 
				
			||||||
                let coordinates: [number, number][]
 | 
					 | 
				
			||||||
                if (geom.type === "LineString") {
 | 
					 | 
				
			||||||
                    coordinates = geom.coordinates
 | 
					 | 
				
			||||||
                } else if (geom.type === "Polygon") {
 | 
					 | 
				
			||||||
                    coordinates = geom.coordinates[0]
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                const action = new CreateNewWayAction(o.newTags.data, coordinates.map(lngLat => ({
 | 
					 | 
				
			||||||
                    lat: lngLat[1],
 | 
					 | 
				
			||||||
                    lon: lngLat[0]
 | 
					 | 
				
			||||||
                })), {theme})
 | 
					 | 
				
			||||||
                return action.newElementId
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        const confirmButton = new SubtleButton(o.image(), o.message)
 | 
					        const confirmButton = new SubtleButton(o.image(), o.message)
 | 
				
			||||||
        confirmButton.onClick(async () => {
 | 
					        confirmButton.onClick(async () => {
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -12,9 +12,7 @@ export default class RightControls extends Combine {
 | 
				
			||||||
    constructor(state:MapState) {
 | 
					    constructor(state:MapState) {
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        const geolocatioHandler = new GeoLocationHandler(
 | 
					        const geolocatioHandler = new GeoLocationHandler(
 | 
				
			||||||
            state.currentGPSLocation,
 | 
					            state
 | 
				
			||||||
            state.leafletMap,
 | 
					 | 
				
			||||||
            state.layoutToUse
 | 
					 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        new ShowDataLayer({
 | 
					        new ShowDataLayer({
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -30,7 +30,17 @@
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            "width": "3",
 | 
					            "width": "3",
 | 
				
			||||||
            "color": "#00f"
 | 
					            "color": "#00f",
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            "dasharray": {
 | 
				
			||||||
 | 
					                "render": "",
 | 
				
			||||||
 | 
					                "mappings": [
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        "if":   "resulting-geometry=yes",
 | 
				
			||||||
 | 
					                        "then": "6 6"
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                ]
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    ]
 | 
					    ]
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -29,6 +29,7 @@
 | 
				
			||||||
    "minzoom": 18
 | 
					    "minzoom": 18
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "trackAllNodes": true,
 | 
					  "trackAllNodes": true,
 | 
				
			||||||
 | 
					  "enableGeolocation": false,
 | 
				
			||||||
  "layers": [
 | 
					  "layers": [
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
      "builtin": "type_node",
 | 
					      "builtin": "type_node",
 | 
				
			||||||
| 
						 | 
					@ -41,6 +42,13 @@
 | 
				
			||||||
          "_is_part_of_building_passage=feat.get('parent_ways')?.some(p => p.tunnel === 'building_passage') ?? false",
 | 
					          "_is_part_of_building_passage=feat.get('parent_ways')?.some(p => p.tunnel === 'building_passage') ?? false",
 | 
				
			||||||
          "_is_part_of_highway=!feat.get('is_part_of_building_passage') && (feat.get('parent_ways')?.some(p => p.highway !== undefined && p.highway !== '') ?? false)",
 | 
					          "_is_part_of_highway=!feat.get('is_part_of_building_passage') && (feat.get('parent_ways')?.some(p => p.highway !== undefined && p.highway !== '') ?? false)",
 | 
				
			||||||
          "_is_part_of_landuse=feat.get('parent_ways')?.some(p => (p.landuse !== undefined && p.landuse !== '') || (p.natural !== undefined && p.natural !== '')) ?? false"
 | 
					          "_is_part_of_landuse=feat.get('parent_ways')?.some(p => (p.landuse !== undefined && p.landuse !== '') || (p.natural !== undefined && p.natural !== '')) ?? false"
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        "mapRendering": [
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "icon": "square:#00f",
 | 
				
			||||||
 | 
					            "iconSize": "5,5,center",
 | 
				
			||||||
 | 
					            "location": "point"
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
        ]
 | 
					        ]
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
| 
						 | 
					@ -638,7 +646,8 @@
 | 
				
			||||||
        "_grb_ref=feat.properties['source:geometry:entity'] + '/' + feat.properties['source:geometry:oidn']",
 | 
					        "_grb_ref=feat.properties['source:geometry:entity'] + '/' + feat.properties['source:geometry:oidn']",
 | 
				
			||||||
        "_imported_osm_object_found= feat.properties['_osm_obj:source:ref'] == feat.properties._grb_ref",
 | 
					        "_imported_osm_object_found= feat.properties['_osm_obj:source:ref'] == feat.properties._grb_ref",
 | 
				
			||||||
        "_grb_date=feat.properties['source:geometry:date'].replace(/\\//g,'-')",
 | 
					        "_grb_date=feat.properties['source:geometry:date'].replace(/\\//g,'-')",
 | 
				
			||||||
        "_imported_osm_still_fresh= feat.properties['_osm_obj:source:date'] == feat.properties._grb_date"
 | 
					        "_imported_osm_still_fresh= feat.properties['_osm_obj:source:date'] == feat.properties._grb_date",
 | 
				
			||||||
 | 
					        "_target_building_type=feat.properties['_osm_obj:building'] === 'yes' ? feat.properties.building : (feat.properties['_osm_obj:building'] ?? feat.properties.building)"
 | 
				
			||||||
      ],
 | 
					      ],
 | 
				
			||||||
      "tagRenderings": [
 | 
					      "tagRenderings": [
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
| 
						 | 
					@ -676,7 +685,7 @@
 | 
				
			||||||
          "mappings": [
 | 
					          "mappings": [
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
              "if": "_overlaps_with!=null",
 | 
					              "if": "_overlaps_with!=null",
 | 
				
			||||||
              "then": "{import_button(OSM-buildings,building=$building; source:geometry:date=$_grb_date; source:geometry:ref=$_grb_ref, Replace the geometry in OpenStreetMap,,,_osm_obj:id)}"
 | 
					              "then": "{import_button(OSM-buildings,building=$_target_building_type; source:geometry:date=$_grb_date; source:geometry:ref=$_grb_ref, Replace the geometry in OpenStreetMap,,,_osm_obj:id)}"
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
          ]
 | 
					          ]
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue