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
|
@ -1,13 +1,16 @@
|
|||
import * as L from "leaflet";
|
||||
import {UIEventSource} from "../UIEventSource";
|
||||
import Svg from "../../Svg";
|
||||
import Img from "../../UI/Base/Img";
|
||||
import {LocalStorageSource} from "../Web/LocalStorageSource";
|
||||
import {VariableUiElement} from "../../UI/Base/VariableUIElement";
|
||||
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig";
|
||||
import {QueryParameters} from "../Web/QueryParameters";
|
||||
import FeatureSource from "../FeatureSource/FeatureSource";
|
||||
import StaticFeatureSource from "../FeatureSource/Sources/StaticFeatureSource";
|
||||
|
||||
export default class GeoLocationHandler extends VariableUiElement {
|
||||
|
||||
public readonly currentLocation : FeatureSource
|
||||
|
||||
/**
|
||||
* Wether or not the geolocation is active, aka the user requested the current location
|
||||
* @private
|
||||
|
@ -25,20 +28,12 @@ export default class GeoLocationHandler extends VariableUiElement {
|
|||
* @private
|
||||
*/
|
||||
private readonly _permission: UIEventSource<string>;
|
||||
/***
|
||||
* The marker on the map, in order to update it
|
||||
* @private
|
||||
*/
|
||||
private _marker: L.Marker;
|
||||
/**
|
||||
* Literally: _currentGPSLocation.data != undefined
|
||||
* @private
|
||||
*/
|
||||
private readonly _hasLocation: UIEventSource<boolean>;
|
||||
private readonly _currentGPSLocation: UIEventSource<{
|
||||
latlng: any;
|
||||
accuracy: number;
|
||||
}>;
|
||||
private readonly _currentGPSLocation: UIEventSource<Coordinates>;
|
||||
/**
|
||||
* Kept in order to update the marker
|
||||
* @private
|
||||
|
@ -63,8 +58,8 @@ export default class GeoLocationHandler extends VariableUiElement {
|
|||
private readonly _layoutToUse: LayoutConfig;
|
||||
|
||||
constructor(
|
||||
currentGPSLocation: UIEventSource<{ latlng: any; accuracy: number }>,
|
||||
leafletMap: UIEventSource<L.Map>,
|
||||
currentGPSLocation: UIEventSource<Coordinates>,
|
||||
leafletMap: UIEventSource<any>,
|
||||
layoutToUse: LayoutConfig
|
||||
) {
|
||||
const hasLocation = currentGPSLocation.map(
|
||||
|
@ -182,10 +177,25 @@ export default class GeoLocationHandler extends VariableUiElement {
|
|||
}
|
||||
})
|
||||
|
||||
|
||||
this.currentLocation = new StaticFeatureSource([], false)
|
||||
this._currentGPSLocation.addCallback((location) => {
|
||||
self._previousLocationGrant.setData("granted");
|
||||
|
||||
const feature = {
|
||||
"type": "Feature",
|
||||
properties: {
|
||||
"user:location":"yes",
|
||||
"accuracy":location.accuracy,
|
||||
"speed":location.speed,
|
||||
},
|
||||
geometry:{
|
||||
type:"Point",
|
||||
coordinates: [location.longitude, location.latitude],
|
||||
}
|
||||
}
|
||||
|
||||
self.currentLocation.features.setData([{feature, freshness: new Date()}])
|
||||
|
||||
const timeSinceRequest =
|
||||
(new Date().getTime() - (self._lastUserRequest?.getTime() ?? 0)) / 1000;
|
||||
if (timeSinceRequest < 30) {
|
||||
|
@ -194,33 +204,8 @@ export default class GeoLocationHandler extends VariableUiElement {
|
|||
self.MoveToCurrentLoction();
|
||||
}
|
||||
|
||||
let color = "#1111cc";
|
||||
try {
|
||||
color = getComputedStyle(document.body).getPropertyValue(
|
||||
"--catch-detail-color"
|
||||
);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
const icon = L.icon({
|
||||
iconUrl: Img.AsData(Svg.location.replace(/#000000/g, color).replace(/#000/g, color)),
|
||||
iconSize: [40, 40], // size of the icon
|
||||
iconAnchor: [20, 20], // point of the icon which will correspond to marker's location
|
||||
});
|
||||
|
||||
const map = self._leafletMap.data;
|
||||
if(map === undefined){
|
||||
return;
|
||||
}
|
||||
|
||||
const newMarker = L.marker(location.latlng, {icon: icon});
|
||||
newMarker.addTo(map);
|
||||
|
||||
if (self._marker !== undefined) {
|
||||
map.removeLayer(self._marker);
|
||||
}
|
||||
self._marker = newMarker;
|
||||
});
|
||||
}
|
||||
|
||||
private init(askPermission: boolean, forceZoom: boolean) {
|
||||
|
@ -261,8 +246,8 @@ export default class GeoLocationHandler extends VariableUiElement {
|
|||
this._lastUserRequest = undefined;
|
||||
|
||||
if (
|
||||
this._currentGPSLocation.data.latlng[0] === 0 &&
|
||||
this._currentGPSLocation.data.latlng[1] === 0
|
||||
this._currentGPSLocation.data.latitude === 0 &&
|
||||
this._currentGPSLocation.data.longitude === 0
|
||||
) {
|
||||
console.debug("Not moving to GPS-location: it is null island");
|
||||
return;
|
||||
|
@ -275,20 +260,20 @@ export default class GeoLocationHandler extends VariableUiElement {
|
|||
if (b !== true) {
|
||||
// B is an array with our locklocation
|
||||
inRange =
|
||||
b[0][0] <= location.latlng[0] &&
|
||||
location.latlng[0] <= b[1][0] &&
|
||||
b[0][1] <= location.latlng[1] &&
|
||||
location.latlng[1] <= b[1][1];
|
||||
b[0][0] <= location.latitude &&
|
||||
location.latitude <= b[1][0] &&
|
||||
b[0][1] <= location.longitude &&
|
||||
location.longitude <= b[1][1];
|
||||
}
|
||||
}
|
||||
if (!inRange) {
|
||||
console.log(
|
||||
"Not zooming to GPS location: out of bounds",
|
||||
b,
|
||||
location.latlng
|
||||
location
|
||||
);
|
||||
} else {
|
||||
this._leafletMap.data.setView(location.latlng, targetZoom);
|
||||
this._leafletMap.data.setView([location.latitude, location.longitude], targetZoom);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -312,10 +297,7 @@ export default class GeoLocationHandler extends VariableUiElement {
|
|||
|
||||
navigator.geolocation.watchPosition(
|
||||
function (position) {
|
||||
self._currentGPSLocation.setData({
|
||||
latlng: [position.coords.latitude, position.coords.longitude],
|
||||
accuracy: position.coords.accuracy,
|
||||
});
|
||||
self._currentGPSLocation.setData(position.coords);
|
||||
},
|
||||
function () {
|
||||
console.warn("Could not get location with navigator.geolocation");
|
||||
|
|
|
@ -117,6 +117,11 @@ export class BBox {
|
|||
return this.minLat
|
||||
}
|
||||
|
||||
contains(lonLat: [number, number]){
|
||||
return this.minLat <= lonLat[1] && lonLat[1] <= this.maxLat
|
||||
&& this.minLon<= lonLat[0] && lonLat[0] <= this.maxLon
|
||||
}
|
||||
|
||||
pad(factor: number, maxIncrease = 2): BBox {
|
||||
|
||||
const latDiff = Math.min(maxIncrease / 2, Math.abs(this.maxLat - this.minLat) * factor)
|
||||
|
|
|
@ -228,11 +228,15 @@ export default class FeaturePipeline {
|
|||
})
|
||||
|
||||
if(state.layoutToUse.trackAllNodes){
|
||||
new FullNodeDatabaseSource(state, osmFeatureSource, tile => {
|
||||
const fullNodeDb = new FullNodeDatabaseSource(
|
||||
state.filteredLayers.data.filter(l => l.layerDef.id === "type_node")[0],
|
||||
tile => {
|
||||
new RegisteringAllFromFeatureSourceActor(tile)
|
||||
perLayerHierarchy.get(tile.layer.layerDef.id).registerTile(tile)
|
||||
tile.features.addCallbackAndRunD(_ => self.newDataLoadedSignal.setData(tile))
|
||||
})
|
||||
|
||||
osmFeatureSource.rawDataHandlers.push((osmJson, tileId) => fullNodeDb.handleOsmJson(osmJson, tileId))
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -70,7 +70,7 @@ export class NewGeometryFromChangesFeatureSource implements FeatureSource {
|
|||
const w = new OsmWay(change.id)
|
||||
w.tags = tags
|
||||
w.nodes = change.changes["nodes"]
|
||||
w.coordinates = change.changes["coordinates"].map(coor => coor.reverse())
|
||||
w.coordinates = change.changes["coordinates"].map(coor => [coor[1], coor[0]])
|
||||
add(w.asGeoJson())
|
||||
break;
|
||||
case "relation":
|
||||
|
|
|
@ -46,8 +46,6 @@ export default class RenderingMultiPlexerFeatureSource {
|
|||
|
||||
for (const f of features) {
|
||||
const feat = f.feature;
|
||||
|
||||
|
||||
if (feat.geometry.type === "Point") {
|
||||
|
||||
for (const rendering of pointRenderings) {
|
||||
|
|
|
@ -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)
|
||||
|
@ -67,4 +140,11 @@ export default class FullNodeDatabaseSource implements TileHierarchy<FeatureSour
|
|||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
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,
|
||||
|
|
|
@ -11,7 +11,7 @@ export default class ChangeTagAction extends OsmChangeAction {
|
|||
|
||||
constructor(elementId: string, tagsFilter: TagsFilter, currentTags: any, meta: {
|
||||
theme: string,
|
||||
changeType: "answer" | "soft-delete" | "add-image"
|
||||
changeType: "answer" | "soft-delete" | "add-image" | string
|
||||
}) {
|
||||
super();
|
||||
this._elementId = elementId;
|
||||
|
@ -27,11 +27,16 @@ export default class ChangeTagAction extends OsmChangeAction {
|
|||
const key = kv.k;
|
||||
const value = kv.v;
|
||||
if (key === undefined || key === null) {
|
||||
console.log("Invalid key");
|
||||
console.error("Invalid key:", key);
|
||||
return undefined;
|
||||
}
|
||||
if (value === undefined || value === null) {
|
||||
console.log("Invalid value for ", key);
|
||||
console.error("Invalid value for ", key,":", value);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if(typeof value !== "string"){
|
||||
console.error("Invalid value for ", key, "as it is not a string:", value)
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
|
|
@ -4,39 +4,25 @@ 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, existingPointHandling?: {
|
||||
withinRangeOfM: number,
|
||||
ifMatches?: TagsFilter,
|
||||
mode: "reuse_osm_point" | "move_osm_point"
|
||||
} []
|
||||
theme: string
|
||||
};
|
||||
|
||||
|
||||
/***
|
||||
* Creates a new way to upload to OSM
|
||||
* @param tags: the tags to apply to the wya
|
||||
* @param tags: the tags to apply to the way
|
||||
* @param coordinates: the coordinates. Might have a nodeId, in this case, this node will be used
|
||||
* @param options
|
||||
*/
|
||||
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"
|
||||
} []
|
||||
theme: string
|
||||
}) {
|
||||
super()
|
||||
this.coordinates = coordinates;
|
||||
|
|
232
Logic/Osm/Actions/ReplaceGeometryAction.ts
Normal file
232
Logic/Osm/Actions/ReplaceGeometryAction.ts
Normal file
|
@ -0,0 +1,232 @@
|
|||
import OsmChangeAction from "./OsmChangeAction";
|
||||
import {Changes} from "../Changes";
|
||||
import {ChangeDescription} from "./ChangeDescription";
|
||||
import {Tag} from "../../Tags/Tag";
|
||||
import FeatureSource from "../../FeatureSource/FeatureSource";
|
||||
import {OsmNode, OsmObject, OsmWay} from "../OsmObject";
|
||||
import {GeoOperations} from "../../GeoOperations";
|
||||
import StaticFeatureSource from "../../FeatureSource/Sources/StaticFeatureSource";
|
||||
import CreateNewNodeAction from "./CreateNewNodeAction";
|
||||
import ChangeTagAction from "./ChangeTagAction";
|
||||
import {And} from "../../Tags/And";
|
||||
import {Utils} from "../../../Utils";
|
||||
import {OsmConnection} from "../OsmConnection";
|
||||
|
||||
export default class ReplaceGeometryAction extends OsmChangeAction {
|
||||
private readonly feature: any;
|
||||
private readonly state: {
|
||||
osmConnection: OsmConnection
|
||||
};
|
||||
private readonly wayToReplaceId: string;
|
||||
private readonly theme: string;
|
||||
private readonly targetCoordinates: [number, number][];
|
||||
private readonly newTags: Tag[] | undefined;
|
||||
|
||||
constructor(
|
||||
state: {
|
||||
osmConnection: OsmConnection
|
||||
},
|
||||
feature: any,
|
||||
wayToReplaceId: string,
|
||||
options: {
|
||||
theme: string,
|
||||
newTags?: Tag[]
|
||||
}
|
||||
) {
|
||||
super();
|
||||
this.state = state;
|
||||
this.feature = feature;
|
||||
this.wayToReplaceId = wayToReplaceId;
|
||||
this.theme = options.theme;
|
||||
|
||||
const geom = this.feature.geometry
|
||||
let coordinates: [number, number][]
|
||||
if (geom.type === "LineString") {
|
||||
coordinates = geom.coordinates
|
||||
} else if (geom.type === "Polygon") {
|
||||
coordinates = geom.coordinates[0]
|
||||
}
|
||||
this.targetCoordinates = coordinates
|
||||
this.newTags = options.newTags
|
||||
}
|
||||
|
||||
public async GetPreview(): Promise<FeatureSource> {
|
||||
const {closestIds, allNodesById} = await this.GetClosestIds();
|
||||
const preview = closestIds.map((newId, i) => {
|
||||
if (newId === undefined) {
|
||||
return {
|
||||
type: "Feature",
|
||||
properties: {
|
||||
"newpoint": "yes",
|
||||
"id": "replace-geometry-move-" + i
|
||||
},
|
||||
geometry: {
|
||||
type: "Point",
|
||||
coordinates: this.targetCoordinates[i]
|
||||
}
|
||||
};
|
||||
}
|
||||
const origPoint = allNodesById.get(newId).centerpoint()
|
||||
return {
|
||||
type: "Feature",
|
||||
properties: {
|
||||
"move": "yes",
|
||||
"osm-id": newId,
|
||||
"id": "replace-geometry-move-" + i
|
||||
},
|
||||
geometry: {
|
||||
type: "LineString",
|
||||
coordinates: [[origPoint[1], origPoint[0]], this.targetCoordinates[i]]
|
||||
}
|
||||
};
|
||||
})
|
||||
return new StaticFeatureSource(preview, false)
|
||||
|
||||
}
|
||||
|
||||
protected async CreateChangeDescriptions(changes: Changes): Promise<ChangeDescription[]> {
|
||||
|
||||
const allChanges: ChangeDescription[] = []
|
||||
const actualIdsToUse: number[] = []
|
||||
|
||||
const {closestIds, osmWay} = await this.GetClosestIds()
|
||||
|
||||
for (let i = 0; i < closestIds.length; i++) {
|
||||
const closestId = closestIds[i];
|
||||
const [lon, lat] = this.targetCoordinates[i]
|
||||
if (closestId === undefined) {
|
||||
|
||||
const newNodeAction = new CreateNewNodeAction(
|
||||
[],
|
||||
lat, lon,
|
||||
{
|
||||
allowReuseOfPreviouslyCreatedPoints: true,
|
||||
theme: this.theme, changeType: null
|
||||
})
|
||||
const changeDescr = await newNodeAction.CreateChangeDescriptions(changes)
|
||||
allChanges.push(...changeDescr)
|
||||
actualIdsToUse.push(newNodeAction.newElementIdNumber)
|
||||
|
||||
} else {
|
||||
const change = <ChangeDescription>{
|
||||
id: closestId,
|
||||
type: "node",
|
||||
meta: {
|
||||
theme: this.theme,
|
||||
changeType: "move"
|
||||
},
|
||||
changes: {lon, lat}
|
||||
}
|
||||
actualIdsToUse.push(closestId)
|
||||
allChanges.push(change)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (this.newTags !== undefined && this.newTags.length > 0) {
|
||||
const addExtraTags = new ChangeTagAction(
|
||||
this.wayToReplaceId,
|
||||
new And(this.newTags),
|
||||
osmWay.tags, {
|
||||
theme: this.theme,
|
||||
changeType: "conflation"
|
||||
}
|
||||
)
|
||||
allChanges.push(...await addExtraTags.CreateChangeDescriptions(changes))
|
||||
}
|
||||
|
||||
// AT the very last: actually change the nodes of the way!
|
||||
allChanges.push({
|
||||
type: "way",
|
||||
id: osmWay.id,
|
||||
changes: {
|
||||
nodes: actualIdsToUse,
|
||||
coordinates: this.targetCoordinates
|
||||
},
|
||||
meta: {
|
||||
theme: this.theme,
|
||||
changeType: "conflation"
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
return allChanges
|
||||
}
|
||||
|
||||
/**
|
||||
* For 'this.feature`, gets a corresponding closest node that alreay exsists
|
||||
* @constructor
|
||||
* @private
|
||||
*/
|
||||
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: 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
|
||||
const splitted = this.wayToReplaceId.split("/");
|
||||
const type = splitted[0];
|
||||
const idN = Number(splitted[1]);
|
||||
if (idN < 0 || type !== "way") {
|
||||
throw "Invalid ID to conflate: " + this.wayToReplaceId
|
||||
}
|
||||
const url = `${this.state.osmConnection._oauth_config.url}/api/0.6/${this.wayToReplaceId}/full`;
|
||||
const rawData = await Utils.downloadJsonCached(url, 1000)
|
||||
const parsed = OsmObject.ParseObjects(rawData.elements);
|
||||
const allNodesById = new Map<number, OsmNode>()
|
||||
const allNodes = parsed.filter(o => o.type === "node")
|
||||
for (const node of allNodes) {
|
||||
allNodesById.set(node.id, <OsmNode>node)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Allright! We know all the nodes of the original way and all the nodes of the target coordinates.
|
||||
* For each of the target coordinates, we search the closest, already existing point and reuse this point
|
||||
*/
|
||||
|
||||
const closestIds = []
|
||||
const distances = []
|
||||
for (const target of this.targetCoordinates) {
|
||||
let closestDistance = undefined
|
||||
let closestId = undefined;
|
||||
for (const osmNode of allNodes) {
|
||||
|
||||
const cp = osmNode.centerpoint()
|
||||
const d = GeoOperations.distanceBetween(target, [cp[1], cp[0]])
|
||||
if (closestId === undefined || closestDistance > d) {
|
||||
closestId = osmNode.id
|
||||
closestDistance = d
|
||||
}
|
||||
}
|
||||
closestIds.push(closestId)
|
||||
distances.push(closestDistance)
|
||||
}
|
||||
|
||||
// Next step: every closestId can only occur once in the list
|
||||
for (let i = 0; i < closestIds.length; i++) {
|
||||
const closestId = closestIds[i]
|
||||
for (let j = i + 1; j < closestIds.length; j++) {
|
||||
const otherClosestId = closestIds[j]
|
||||
if (closestId !== otherClosestId) {
|
||||
continue
|
||||
}
|
||||
// We have two occurences of 'closestId' - we only keep the closest instance!
|
||||
const di = distances[i]
|
||||
const dj = distances[j]
|
||||
if (di < dj) {
|
||||
closestIds[j] = undefined
|
||||
} else {
|
||||
closestIds[i] = undefined
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const osmWay = <OsmWay>parsed[parsed.length - 1]
|
||||
if (osmWay.type !== "way") {
|
||||
throw "WEIRD: expected an OSM-way as last element here!"
|
||||
}
|
||||
return {closestIds, allNodesById, osmWay};
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -114,7 +114,16 @@ export class Changes {
|
|||
}
|
||||
|
||||
public async applyAction(action: OsmChangeAction): Promise<void> {
|
||||
const changes = await action.Perform(this)
|
||||
this.applyChanges(await action.Perform(this))
|
||||
}
|
||||
|
||||
public async applyActions(actions: OsmChangeAction[]) {
|
||||
for (const action of actions) {
|
||||
await this.applyAction(action)
|
||||
}
|
||||
}
|
||||
|
||||
public applyChanges(changes: ChangeDescription[]) {
|
||||
console.log("Received changes:", changes)
|
||||
this.pendingChanges.data.push(...changes);
|
||||
this.pendingChanges.ping();
|
||||
|
@ -126,6 +135,7 @@ export class Changes {
|
|||
CreateNewNodeAction.registerIdRewrites(mappings)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* UPload the selected changes to OSM.
|
||||
* Returns 'true' if successfull and if they can be removed
|
||||
|
|
|
@ -14,6 +14,7 @@ import {QueryParameters} from "../Web/QueryParameters";
|
|||
import * as personal from "../../assets/themes/personal/personal.json";
|
||||
import FilterConfig from "../../Models/ThemeConfig/FilterConfig";
|
||||
import ShowOverlayLayer from "../../UI/ShowDataLayer/ShowOverlayLayer";
|
||||
import {Coord} from "@turf/turf";
|
||||
|
||||
/**
|
||||
* Contains all the leaflet-map related state
|
||||
|
@ -44,13 +45,7 @@ export default class MapState extends UserRelatedState {
|
|||
/**
|
||||
* The location as delivered by the GPS
|
||||
*/
|
||||
public currentGPSLocation: UIEventSource<{
|
||||
latlng: { lat: number; lng: number };
|
||||
accuracy: number;
|
||||
}> = new UIEventSource<{
|
||||
latlng: { lat: number; lng: number };
|
||||
accuracy: number;
|
||||
}>(undefined);
|
||||
public currentGPSLocation: UIEventSource<Coordinates> = new UIEventSource<Coordinates>(undefined);
|
||||
|
||||
public readonly mainMapObject: BaseUIElement & MinimapObj;
|
||||
|
||||
|
|
28
UI/Base/AsyncLazy.ts
Normal file
28
UI/Base/AsyncLazy.ts
Normal file
|
@ -0,0 +1,28 @@
|
|||
import BaseUIElement from "../BaseUIElement";
|
||||
import {VariableUiElement} from "./VariableUIElement";
|
||||
import {UIEventSource} from "../../Logic/UIEventSource";
|
||||
import Loading from "./Loading";
|
||||
|
||||
export default class AsyncLazy extends BaseUIElement{
|
||||
private readonly _f: () => Promise<BaseUIElement>;
|
||||
|
||||
constructor(f: () => Promise<BaseUIElement>) {
|
||||
super();
|
||||
this._f = f;
|
||||
}
|
||||
|
||||
protected InnerConstructElement(): HTMLElement {
|
||||
// The caching of the BaseUIElement will guarantee that _f will only be called once
|
||||
|
||||
return new VariableUiElement(
|
||||
UIEventSource.FromPromise(this._f()).map(el => {
|
||||
if(el === undefined){
|
||||
return new Loading()
|
||||
}
|
||||
return el
|
||||
})
|
||||
|
||||
).ConstructElement()
|
||||
}
|
||||
|
||||
}
|
|
@ -19,6 +19,7 @@ export interface MinimapOptions {
|
|||
export interface MinimapObj {
|
||||
readonly leafletMap: UIEventSource<any>,
|
||||
installBounds(factor: number | BBox, showRange?: boolean) : void
|
||||
TakeScreenshot(): Promise<any>;
|
||||
}
|
||||
|
||||
export default class Minimap {
|
||||
|
|
|
@ -9,6 +9,7 @@ import {Map} from "leaflet";
|
|||
import Minimap, {MinimapObj, MinimapOptions} from "./Minimap";
|
||||
import {BBox} from "../../Logic/BBox";
|
||||
import 'leaflet-polylineoffset'
|
||||
import {SimpleMapScreenshoter} from "leaflet-simple-map-screenshoter";
|
||||
|
||||
export default class MinimapImplementation extends BaseUIElement implements MinimapObj {
|
||||
private static _nextId = 0;
|
||||
|
@ -278,4 +279,10 @@ export default class MinimapImplementation extends BaseUIElement implements Mini
|
|||
|
||||
this.leafletMap.setData(map)
|
||||
}
|
||||
|
||||
public async TakeScreenshot(){
|
||||
const screenshotter = new SimpleMapScreenshoter();
|
||||
screenshotter.addTo(this.leafletMap.data);
|
||||
return await screenshotter.takeScreen('image')
|
||||
}
|
||||
}
|
|
@ -14,6 +14,9 @@ import Toggle from "../Input/Toggle";
|
|||
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig";
|
||||
import {Utils} from "../../Utils";
|
||||
import UserRelatedState from "../../Logic/State/UserRelatedState";
|
||||
import Loc from "../../Models/Loc";
|
||||
import BaseLayer from "../../Models/BaseLayer";
|
||||
import FilteredLayer from "../../Models/FilteredLayer";
|
||||
|
||||
export default class FullWelcomePaneWithTabs extends ScrollableFullScreen {
|
||||
|
||||
|
@ -24,7 +27,10 @@ export default class FullWelcomePaneWithTabs extends ScrollableFullScreen {
|
|||
layoutToUse: LayoutConfig,
|
||||
osmConnection: OsmConnection,
|
||||
featureSwitchShareScreen: UIEventSource<boolean>,
|
||||
featureSwitchMoreQuests: UIEventSource<boolean>
|
||||
featureSwitchMoreQuests: UIEventSource<boolean>,
|
||||
locationControl: UIEventSource<Loc>,
|
||||
backgroundLayer: UIEventSource<BaseLayer>,
|
||||
filteredLayers: UIEventSource<FilteredLayer[]>
|
||||
} & UserRelatedState) {
|
||||
const layoutToUse = state.layoutToUse;
|
||||
super(
|
||||
|
@ -39,7 +45,8 @@ export default class FullWelcomePaneWithTabs extends ScrollableFullScreen {
|
|||
layoutToUse: LayoutConfig,
|
||||
osmConnection: OsmConnection,
|
||||
featureSwitchShareScreen: UIEventSource<boolean>,
|
||||
featureSwitchMoreQuests: UIEventSource<boolean>
|
||||
featureSwitchMoreQuests: UIEventSource<boolean>,
|
||||
locationControl: UIEventSource<Loc>, backgroundLayer: UIEventSource<BaseLayer>, filteredLayers: UIEventSource<FilteredLayer[]>
|
||||
} & UserRelatedState,
|
||||
isShown: UIEventSource<boolean>):
|
||||
{ header: string | BaseUIElement; content: BaseUIElement }[] {
|
||||
|
@ -77,7 +84,8 @@ export default class FullWelcomePaneWithTabs extends ScrollableFullScreen {
|
|||
layoutToUse: LayoutConfig,
|
||||
osmConnection: OsmConnection,
|
||||
featureSwitchShareScreen: UIEventSource<boolean>,
|
||||
featureSwitchMoreQuests: UIEventSource<boolean>
|
||||
featureSwitchMoreQuests: UIEventSource<boolean>,
|
||||
locationControl: UIEventSource<Loc>, backgroundLayer: UIEventSource<BaseLayer>, filteredLayers: UIEventSource<FilteredLayer[]>
|
||||
} & UserRelatedState, currentTab: UIEventSource<number>, isShown: UIEventSource<boolean>) {
|
||||
|
||||
const tabs = FullWelcomePaneWithTabs.ConstructBaseTabs(state, isShown)
|
||||
|
|
|
@ -9,7 +9,6 @@ import Toggle from "../Input/Toggle";
|
|||
import CreateNewNodeAction from "../../Logic/Osm/Actions/CreateNewNodeAction";
|
||||
import {Tag} from "../../Logic/Tags/Tag";
|
||||
import Loading from "../Base/Loading";
|
||||
import OsmChangeAction from "../../Logic/Osm/Actions/OsmChangeAction";
|
||||
import CreateNewWayAction from "../../Logic/Osm/Actions/CreateNewWayAction";
|
||||
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig";
|
||||
import {OsmConnection} from "../../Logic/Osm/OsmConnection";
|
||||
|
@ -26,6 +25,13 @@ import SpecialVisualizations, {SpecialVisualization} from "../SpecialVisualizati
|
|||
import {FixedUiElement} from "../Base/FixedUiElement";
|
||||
import Svg from "../../Svg";
|
||||
import {Utils} from "../../Utils";
|
||||
import Minimap from "../Base/Minimap";
|
||||
import ShowDataLayer from "../ShowDataLayer/ShowDataLayer";
|
||||
import AllKnownLayers from "../../Customizations/AllKnownLayers";
|
||||
import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource";
|
||||
import ShowDataMultiLayer from "../ShowDataLayer/ShowDataMultiLayer";
|
||||
import BaseLayer from "../../Models/BaseLayer";
|
||||
import ReplaceGeometryAction from "../../Logic/Osm/Actions/ReplaceGeometryAction";
|
||||
|
||||
|
||||
export interface ImportButtonState {
|
||||
|
@ -38,6 +44,8 @@ export interface ImportButtonState {
|
|||
feature: any,
|
||||
minZoom: number,
|
||||
state: {
|
||||
backgroundLayer: UIEventSource<BaseLayer>;
|
||||
filteredLayers: UIEventSource<FilteredLayer[]>;
|
||||
featureSwitchUserbadge: UIEventSource<boolean>;
|
||||
featurePipeline: FeaturePipeline;
|
||||
allElements: ElementStorage;
|
||||
|
@ -48,8 +56,14 @@ export interface ImportButtonState {
|
|||
locationControl: UIEventSource<{ zoom: number }>
|
||||
},
|
||||
guiState: { filterViewIsOpened: UIEventSource<boolean> },
|
||||
snapToLayers?: string[],
|
||||
|
||||
snapSettings?: {
|
||||
snapToLayers: string[],
|
||||
snapToLayersMaxDist?: number
|
||||
},
|
||||
conflationSettings?: {
|
||||
conflateWayId: string
|
||||
}
|
||||
}
|
||||
|
||||
export class ImportButtonSpecialViz implements SpecialVisualization {
|
||||
|
@ -83,7 +97,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
|||
|
||||
#### Specifying which tags to copy or add
|
||||
|
||||
The first argument of the import button takes a \`;\`-seperated list of tags to add.
|
||||
The argument \`tags\` of the import button takes a \`;\`-seperated list of tags to add.
|
||||
|
||||
${Utils.Special_visualizations_tagsToApplyHelpText}
|
||||
|
||||
|
@ -113,8 +127,9 @@ ${Utils.Special_visualizations_tagsToApplyHelpText}
|
|||
doc: "How far the contributor must zoom in before being able to import the point",
|
||||
defaultValue: "18"
|
||||
}, {
|
||||
name: "Snap onto layer(s)",
|
||||
doc: "If a way of the given layer is closeby, will snap the new point onto this way (similar as preset might snap). To show multiple layers to snap onto, use a `;`-seperated list",
|
||||
name: "Snap onto layer(s)/replace geometry with this other way",
|
||||
doc: " - If the value corresponding with this key starts with 'way/' and the feature is a LineString or Polygon, the original OSM-way geometry will be changed to match the new geometry\n" +
|
||||
" - If a way of the given layer(s) is closeby, will snap the new point onto this way (similar as preset might snap). To show multiple layers to snap onto, use a `;`-seperated list",
|
||||
}, {
|
||||
name: "snap max distance",
|
||||
doc: "The maximum distance that this point will move to snap onto a layer (in meters)",
|
||||
|
@ -130,7 +145,7 @@ ${Utils.Special_visualizations_tagsToApplyHelpText}
|
|||
const id = tagSource.data.id;
|
||||
const feature = state.allElements.ContainingFeatures.get(id)
|
||||
let minZoom = args[4] == "" ? 18 : Number(args[4])
|
||||
if(isNaN(minZoom)){
|
||||
if (isNaN(minZoom)) {
|
||||
console.warn("Invalid minzoom:", minZoom)
|
||||
minZoom = 18
|
||||
}
|
||||
|
@ -145,6 +160,17 @@ ${Utils.Special_visualizations_tagsToApplyHelpText}
|
|||
img = () => Svg.add_ui()
|
||||
}
|
||||
|
||||
let snapSettings = undefined
|
||||
let conflationSettings = undefined
|
||||
const possibleWayId = tagSource.data[args[5]]
|
||||
if (possibleWayId?.startsWith("way/")) {
|
||||
// This is a conflation
|
||||
conflationSettings = {
|
||||
conflateWayId: possibleWayId
|
||||
}
|
||||
} else {
|
||||
|
||||
|
||||
const snapToLayers = args[5]?.split(";").filter(s => s !== "")
|
||||
const snapToLayersMaxDist = Number(args[6] ?? 6)
|
||||
|
||||
|
@ -153,6 +179,11 @@ ${Utils.Special_visualizations_tagsToApplyHelpText}
|
|||
console.error(e)
|
||||
return new FixedUiElement(e).SetClass("alert")
|
||||
}
|
||||
snapSettings = {
|
||||
snapToLayers,
|
||||
snapToLayersMaxDist
|
||||
}
|
||||
}
|
||||
|
||||
return new ImportButton(
|
||||
{
|
||||
|
@ -160,8 +191,8 @@ ${Utils.Special_visualizations_tagsToApplyHelpText}
|
|||
feature, newTags, message, minZoom,
|
||||
originalTags: tagSource,
|
||||
targetLayer,
|
||||
snapToLayers,
|
||||
snapToLayersMaxDist
|
||||
snapSettings,
|
||||
conflationSettings
|
||||
}
|
||||
);
|
||||
}
|
||||
|
@ -201,7 +232,7 @@ export default class ImportButton extends Toggle {
|
|||
|
||||
const importClicked = new UIEventSource(false);
|
||||
const importFlow = new Toggle(
|
||||
new Lazy(() => ImportButton.createConfirmPanel(o, isImported, importClicked)),
|
||||
ImportButton.createConfirmPanel(o, isImported, importClicked),
|
||||
importButton,
|
||||
importClicked
|
||||
)
|
||||
|
@ -228,7 +259,121 @@ export default class ImportButton extends Toggle {
|
|||
)
|
||||
}
|
||||
|
||||
public static createConfirmPanel(
|
||||
public static createConfirmPanel(o: ImportButtonState,
|
||||
isImported: UIEventSource<boolean>,
|
||||
importClicked: UIEventSource<boolean>) {
|
||||
const geometry = o.feature.geometry
|
||||
if (geometry.type === "Point") {
|
||||
return new Lazy(() => ImportButton.createConfirmPanelForPoint(o, isImported, importClicked))
|
||||
}
|
||||
|
||||
|
||||
if (geometry.type === "Polygon" || geometry.type == "LineString") {
|
||||
return new Lazy(() => ImportButton.createConfirmForWay(o, isImported, importClicked))
|
||||
}
|
||||
console.error("Invalid type to import", geometry.type)
|
||||
return new FixedUiElement("Invalid geometry type:" + geometry.type).SetClass("alert")
|
||||
|
||||
|
||||
}
|
||||
|
||||
public static createConfirmForWay(o: ImportButtonState,
|
||||
isImported: UIEventSource<boolean>,
|
||||
importClicked: UIEventSource<boolean>): BaseUIElement {
|
||||
|
||||
const confirmationMap = Minimap.createMiniMap({
|
||||
allowMoving: false,
|
||||
background: o.state.backgroundLayer
|
||||
})
|
||||
confirmationMap.SetStyle("height: 20rem; overflow: hidden").SetClass("rounded-xl")
|
||||
|
||||
const relevantFeatures = Utils.NoNull([o.feature, o.state.allElements?.ContainingFeatures?.get(o.conflationSettings?.conflateWayId)])
|
||||
// SHow all relevant data - including (eventually) the way of which the geometry will be replaced
|
||||
new ShowDataMultiLayer({
|
||||
leafletMap: confirmationMap.leafletMap,
|
||||
enablePopups: false,
|
||||
zoomToFeatures: true,
|
||||
features: new StaticFeatureSource(relevantFeatures, false),
|
||||
allElements: o.state.allElements,
|
||||
layers: o.state.filteredLayers
|
||||
})
|
||||
|
||||
const theme = o.state.layoutToUse.id
|
||||
|
||||
|
||||
const changes = o.state.changes
|
||||
let confirm: () => Promise<string>
|
||||
if (o.conflationSettings !== undefined) {
|
||||
|
||||
let replaceGeometryAction = new ReplaceGeometryAction(
|
||||
o.state,
|
||||
o.feature,
|
||||
o.conflationSettings.conflateWayId,
|
||||
{
|
||||
theme: o.state.layoutToUse.id,
|
||||
newTags: o.newTags.data
|
||||
}
|
||||
)
|
||||
|
||||
replaceGeometryAction.GetPreview().then(changePreview => {
|
||||
new ShowDataLayer({
|
||||
leafletMap: confirmationMap.leafletMap,
|
||||
enablePopups: false,
|
||||
zoomToFeatures: false,
|
||||
features: changePreview,
|
||||
allElements: o.state.allElements,
|
||||
layerToShow: AllKnownLayers.sharedLayers.get("conflation")
|
||||
})
|
||||
})
|
||||
|
||||
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)
|
||||
confirmButton.onClick(async () => {
|
||||
{
|
||||
if (isImported.data) {
|
||||
return
|
||||
}
|
||||
o.originalTags.data["_imported"] = "yes"
|
||||
o.originalTags.ping() // will set isImported as per its definition
|
||||
|
||||
const idToSelect = await confirm()
|
||||
|
||||
o.state.selectedElement.setData(o.state.allElements.ContainingFeatures.get(idToSelect))
|
||||
|
||||
}
|
||||
})
|
||||
|
||||
const cancel = new SubtleButton(Svg.close_ui(), Translations.t.general.cancel).onClick(() => {
|
||||
importClicked.setData(false)
|
||||
})
|
||||
|
||||
|
||||
return new Combine([confirmationMap, confirmButton, cancel]).SetClass("flex flex-col")
|
||||
}
|
||||
|
||||
public static createConfirmPanelForPoint(
|
||||
o: ImportButtonState,
|
||||
isImported: UIEventSource<boolean>,
|
||||
importClicked: UIEventSource<boolean>): BaseUIElement {
|
||||
|
@ -239,21 +384,24 @@ export default class ImportButton extends Toggle {
|
|||
}
|
||||
o.originalTags.data["_imported"] = "yes"
|
||||
o.originalTags.ping() // will set isImported as per its definition
|
||||
const newElementAction = ImportButton.createAddActionForFeature(o.newTags.data, o.feature, o.state.layoutToUse.id)
|
||||
const geometry = o.feature.geometry
|
||||
const lat = geometry.coordinates[1]
|
||||
const lon = geometry.coordinates[0];
|
||||
const newElementAction = new CreateNewNodeAction(o.newTags.data, lat, lon, {
|
||||
theme: o.state.layoutToUse.id,
|
||||
changeType: "import"
|
||||
})
|
||||
|
||||
await o.state.changes.applyAction(newElementAction)
|
||||
o.state.selectedElement.setData(o.state.allElements.ContainingFeatures.get(
|
||||
newElementAction.newElementId
|
||||
))
|
||||
console.log("Did set selected element to", o.state.allElements.ContainingFeatures.get(
|
||||
newElementAction.newElementId
|
||||
))
|
||||
}
|
||||
|
||||
function cancel() {
|
||||
importClicked.setData(false)
|
||||
}
|
||||
|
||||
if (o.feature.geometry.type === "Point") {
|
||||
const presetInfo = <PresetInfo>{
|
||||
tags: o.newTags.data,
|
||||
icon: o.image,
|
||||
|
@ -261,17 +409,18 @@ export default class ImportButton extends Toggle {
|
|||
layerToAddTo: o.targetLayer,
|
||||
name: o.message,
|
||||
title: o.message,
|
||||
preciseInput: { snapToLayers: o.snapToLayers,
|
||||
maxSnapDistance: o.snapToLayersMaxDist}
|
||||
preciseInput: {
|
||||
snapToLayers: o.snapSettings?.snapToLayers,
|
||||
maxSnapDistance: o.snapSettings?.snapToLayersMaxDist
|
||||
}
|
||||
}
|
||||
|
||||
const [lon, lat] = o.feature.geometry.coordinates
|
||||
console.log("Creating an import dialog at location", lon, lat)
|
||||
return new ConfirmLocationOfPoint(o.state, o.guiState.filterViewIsOpened, presetInfo, Translations.W(o.message), {
|
||||
lon,
|
||||
lat
|
||||
}, confirm, cancel)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@ -279,41 +428,4 @@ export default class ImportButton extends Toggle {
|
|||
const type = feature.geometry.type
|
||||
return type === "Point" || type === "LineString" || (type === "Polygon" && feature.geometry.coordinates.length === 1)
|
||||
}
|
||||
|
||||
private static createAddActionForFeature(newTags: Tag[], feature: any, theme: string):
|
||||
OsmChangeAction & { newElementId: string } {
|
||||
const geometry = feature.geometry
|
||||
const type = geometry.type
|
||||
if (type === "Point") {
|
||||
const lat = geometry.coordinates[1]
|
||||
const lon = geometry.coordinates[0];
|
||||
return new CreateNewNodeAction(newTags, lat, lon, {
|
||||
theme,
|
||||
changeType: "import"
|
||||
})
|
||||
}
|
||||
|
||||
if (type === "LineString") {
|
||||
return new CreateNewWayAction(
|
||||
newTags,
|
||||
geometry.coordinates.map(coor => ({lon: coor[0], lat: coor[1]})),
|
||||
{
|
||||
theme
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
if (type === "Polygon") {
|
||||
return new CreateNewWayAction(
|
||||
newTags,
|
||||
geometry.coordinates[0].map(coor => ({lon: coor[0], lat: coor[1]})),
|
||||
{
|
||||
theme
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
return undefined;
|
||||
|
||||
}
|
||||
}
|
|
@ -14,6 +14,8 @@ import Loc from "../../Models/Loc";
|
|||
import {BBox} from "../../Logic/BBox";
|
||||
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig";
|
||||
import FilteredLayer from "../../Models/FilteredLayer";
|
||||
import BaseLayer from "../../Models/BaseLayer";
|
||||
import {OsmConnection} from "../../Logic/Osm/OsmConnection";
|
||||
|
||||
export default class LeftControls extends Combine {
|
||||
|
||||
|
@ -26,7 +28,9 @@ export default class LeftControls extends Combine {
|
|||
featureSwitchEnableExport: UIEventSource<boolean>,
|
||||
featureSwitchExportAsPdf: UIEventSource<boolean>,
|
||||
filteredLayers: UIEventSource<FilteredLayer[]>,
|
||||
featureSwitchFilter: UIEventSource<boolean>
|
||||
featureSwitchFilter: UIEventSource<boolean>,
|
||||
backgroundLayer: UIEventSource<BaseLayer>,
|
||||
osmConnection: OsmConnection
|
||||
},
|
||||
guiState: {
|
||||
downloadControlIsOpened: UIEventSource<boolean>,
|
||||
|
|
|
@ -4,17 +4,30 @@ import MapControlButton from "../MapControlButton";
|
|||
import GeoLocationHandler from "../../Logic/Actors/GeoLocationHandler";
|
||||
import Svg from "../../Svg";
|
||||
import MapState from "../../Logic/State/MapState";
|
||||
import ShowDataLayer from "../ShowDataLayer/ShowDataLayer";
|
||||
import AllKnownLayers from "../../Customizations/AllKnownLayers";
|
||||
|
||||
export default class RightControls extends Combine {
|
||||
|
||||
constructor(state:MapState) {
|
||||
const geolocationButton = new Toggle(
|
||||
new MapControlButton(
|
||||
new GeoLocationHandler(
|
||||
|
||||
const geolocatioHandler = new GeoLocationHandler(
|
||||
state.currentGPSLocation,
|
||||
state.leafletMap,
|
||||
state.layoutToUse
|
||||
), {
|
||||
)
|
||||
|
||||
new ShowDataLayer({
|
||||
layerToShow: AllKnownLayers.sharedLayers.get("gps_location"),
|
||||
leafletMap: state.leafletMap,
|
||||
enablePopups: true,
|
||||
features: geolocatioHandler.currentLocation
|
||||
})
|
||||
|
||||
const geolocationButton = new Toggle(
|
||||
new MapControlButton(
|
||||
geolocatioHandler
|
||||
, {
|
||||
dontStyle: true
|
||||
}
|
||||
),
|
||||
|
|
|
@ -8,11 +8,14 @@ import Toggle from "../Input/Toggle";
|
|||
import Translations from "../i18n/Translations";
|
||||
import BaseUIElement from "../BaseUIElement";
|
||||
import LayerConfig from "../../Models/ThemeConfig/LayerConfig";
|
||||
import MapState from "../../Logic/State/MapState";
|
||||
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig";
|
||||
import Loc from "../../Models/Loc";
|
||||
import BaseLayer from "../../Models/BaseLayer";
|
||||
import FilteredLayer from "../../Models/FilteredLayer";
|
||||
|
||||
export default class ShareScreen extends Combine {
|
||||
|
||||
constructor(state: MapState) {
|
||||
constructor(state: {layoutToUse: LayoutConfig, locationControl: UIEventSource<Loc>, backgroundLayer: UIEventSource<BaseLayer>, filteredLayers: UIEventSource<FilteredLayer[]>}) {
|
||||
const layout = state?.layoutToUse;
|
||||
const tr = Translations.t.general.sharescreen;
|
||||
|
||||
|
|
|
@ -6,9 +6,6 @@ import FullWelcomePaneWithTabs from "./BigComponents/FullWelcomePaneWithTabs";
|
|||
import MapControlButton from "./MapControlButton";
|
||||
import Svg from "../Svg";
|
||||
import Toggle from "./Input/Toggle";
|
||||
import Hash from "../Logic/Web/Hash";
|
||||
import {QueryParameters} from "../Logic/Web/QueryParameters";
|
||||
import Constants from "../Models/Constants";
|
||||
import UserBadge from "./BigComponents/UserBadge";
|
||||
import SearchAndGo from "./BigComponents/SearchAndGo";
|
||||
import Link from "./Base/Link";
|
||||
|
@ -24,77 +21,7 @@ import Translations from "./i18n/Translations";
|
|||
import SimpleAddUI from "./BigComponents/SimpleAddUI";
|
||||
import StrayClickHandler from "../Logic/Actors/StrayClickHandler";
|
||||
import Lazy from "./Base/Lazy";
|
||||
|
||||
export class DefaultGuiState {
|
||||
public readonly welcomeMessageIsOpened : UIEventSource<boolean>;
|
||||
public readonly downloadControlIsOpened: UIEventSource<boolean>;
|
||||
public readonly filterViewIsOpened: UIEventSource<boolean>;
|
||||
public readonly copyrightViewIsOpened: UIEventSource<boolean>;
|
||||
public readonly welcomeMessageOpenedTab: UIEventSource<number>
|
||||
public readonly allFullScreenStates: UIEventSource<boolean>[] = []
|
||||
static state: DefaultGuiState;
|
||||
|
||||
constructor() {
|
||||
|
||||
|
||||
|
||||
this.welcomeMessageOpenedTab = UIEventSource.asFloat(QueryParameters.GetQueryParameter(
|
||||
"tab",
|
||||
"0",
|
||||
`The tab that is shown in the welcome-message. 0 = the explanation of the theme,1 = OSM-credits, 2 = sharescreen, 3 = more themes, 4 = about mapcomplete (user must be logged in and have >${Constants.userJourney.mapCompleteHelpUnlock} changesets)`
|
||||
));
|
||||
this.welcomeMessageIsOpened = QueryParameters.GetBooleanQueryParameter(
|
||||
"welcome-control-toggle",
|
||||
"false",
|
||||
"Whether or not the welcome panel is shown"
|
||||
)
|
||||
this.downloadControlIsOpened = QueryParameters.GetBooleanQueryParameter(
|
||||
"download-control-toggle",
|
||||
"false",
|
||||
"Whether or not the download panel is shown"
|
||||
)
|
||||
this.filterViewIsOpened = QueryParameters.GetBooleanQueryParameter(
|
||||
"filter-toggle",
|
||||
"false",
|
||||
"Whether or not the filter view is shown"
|
||||
)
|
||||
this.copyrightViewIsOpened = QueryParameters.GetBooleanQueryParameter(
|
||||
"copyright-toggle",
|
||||
"false",
|
||||
"Whether or not the copyright view is shown"
|
||||
)
|
||||
if(Hash.hash.data === "download"){
|
||||
this.downloadControlIsOpened.setData(true)
|
||||
}
|
||||
if(Hash.hash.data === "filters"){
|
||||
this.filterViewIsOpened.setData(true)
|
||||
}
|
||||
if(Hash.hash.data === "copyright"){
|
||||
this.copyrightViewIsOpened.setData(true)
|
||||
}
|
||||
if(Hash.hash.data === "" || Hash.hash.data === undefined || Hash.hash.data === "welcome"){
|
||||
this.welcomeMessageIsOpened.setData(true)
|
||||
}
|
||||
|
||||
this.allFullScreenStates.push(this.downloadControlIsOpened, this.filterViewIsOpened, this.copyrightViewIsOpened, this.welcomeMessageIsOpened)
|
||||
|
||||
for (let i = 0; i < this.allFullScreenStates.length; i++){
|
||||
const fullScreenState = this.allFullScreenStates[i];
|
||||
for (let j = 0; j < this.allFullScreenStates.length; j++){
|
||||
if(i == j){
|
||||
continue
|
||||
}
|
||||
const otherState = this.allFullScreenStates[j];
|
||||
fullScreenState.addCallbackAndRunD(isOpened => {
|
||||
if(isOpened){
|
||||
otherState.setData(false)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
import {DefaultGuiState} from "./DefaultGuiState";
|
||||
|
||||
|
||||
/**
|
||||
|
|
74
UI/DefaultGuiState.ts
Normal file
74
UI/DefaultGuiState.ts
Normal file
|
@ -0,0 +1,74 @@
|
|||
import {UIEventSource} from "../Logic/UIEventSource";
|
||||
import {QueryParameters} from "../Logic/Web/QueryParameters";
|
||||
import Constants from "../Models/Constants";
|
||||
import Hash from "../Logic/Web/Hash";
|
||||
|
||||
export class DefaultGuiState {
|
||||
public readonly welcomeMessageIsOpened: UIEventSource<boolean>;
|
||||
public readonly downloadControlIsOpened: UIEventSource<boolean>;
|
||||
public readonly filterViewIsOpened: UIEventSource<boolean>;
|
||||
public readonly copyrightViewIsOpened: UIEventSource<boolean>;
|
||||
public readonly welcomeMessageOpenedTab: UIEventSource<number>
|
||||
public readonly allFullScreenStates: UIEventSource<boolean>[] = []
|
||||
static state: DefaultGuiState;
|
||||
|
||||
constructor() {
|
||||
|
||||
|
||||
this.welcomeMessageOpenedTab = UIEventSource.asFloat(QueryParameters.GetQueryParameter(
|
||||
"tab",
|
||||
"0",
|
||||
`The tab that is shown in the welcome-message. 0 = the explanation of the theme,1 = OSM-credits, 2 = sharescreen, 3 = more themes, 4 = about mapcomplete (user must be logged in and have >${Constants.userJourney.mapCompleteHelpUnlock} changesets)`
|
||||
));
|
||||
this.welcomeMessageIsOpened = QueryParameters.GetBooleanQueryParameter(
|
||||
"welcome-control-toggle",
|
||||
"false",
|
||||
"Whether or not the welcome panel is shown"
|
||||
)
|
||||
this.downloadControlIsOpened = QueryParameters.GetBooleanQueryParameter(
|
||||
"download-control-toggle",
|
||||
"false",
|
||||
"Whether or not the download panel is shown"
|
||||
)
|
||||
this.filterViewIsOpened = QueryParameters.GetBooleanQueryParameter(
|
||||
"filter-toggle",
|
||||
"false",
|
||||
"Whether or not the filter view is shown"
|
||||
)
|
||||
this.copyrightViewIsOpened = QueryParameters.GetBooleanQueryParameter(
|
||||
"copyright-toggle",
|
||||
"false",
|
||||
"Whether or not the copyright view is shown"
|
||||
)
|
||||
if (Hash.hash.data === "download") {
|
||||
this.downloadControlIsOpened.setData(true)
|
||||
}
|
||||
if (Hash.hash.data === "filters") {
|
||||
this.filterViewIsOpened.setData(true)
|
||||
}
|
||||
if (Hash.hash.data === "copyright") {
|
||||
this.copyrightViewIsOpened.setData(true)
|
||||
}
|
||||
if (Hash.hash.data === "" || Hash.hash.data === undefined || Hash.hash.data === "welcome") {
|
||||
this.welcomeMessageIsOpened.setData(true)
|
||||
}
|
||||
|
||||
this.allFullScreenStates.push(this.downloadControlIsOpened, this.filterViewIsOpened, this.copyrightViewIsOpened, this.welcomeMessageIsOpened)
|
||||
|
||||
for (let i = 0; i < this.allFullScreenStates.length; i++) {
|
||||
const fullScreenState = this.allFullScreenStates[i];
|
||||
for (let j = 0; j < this.allFullScreenStates.length; j++) {
|
||||
if (i == j) {
|
||||
continue
|
||||
}
|
||||
const otherState = this.allFullScreenStates[j];
|
||||
fullScreenState.addCallbackAndRunD(isOpened => {
|
||||
if (isOpened) {
|
||||
otherState.setData(false)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -1,9 +1,6 @@
|
|||
|
||||
|
||||
import jsPDF from "jspdf";
|
||||
import {SimpleMapScreenshoter} from "leaflet-simple-map-screenshoter";
|
||||
import {UIEventSource} from "../Logic/UIEventSource";
|
||||
import Minimap from "./Base/Minimap";
|
||||
import Minimap, {MinimapObj} from "./Base/Minimap";
|
||||
import Loc from "../Models/Loc";
|
||||
import BaseLayer from "../Models/BaseLayer";
|
||||
import {FixedUiElement} from "./Base/FixedUiElement";
|
||||
|
@ -14,7 +11,6 @@ import LayoutConfig from "../Models/ThemeConfig/LayoutConfig";
|
|||
import FeaturePipeline from "../Logic/FeatureSource/FeaturePipeline";
|
||||
import ShowDataLayer from "./ShowDataLayer/ShowDataLayer";
|
||||
import {BBox} from "../Logic/BBox";
|
||||
import ShowOverlayLayer from "./ShowDataLayer/ShowOverlayLayer";
|
||||
/**
|
||||
* Creates screenshoter to take png screenshot
|
||||
* Creates jspdf and downloads it
|
||||
|
@ -63,14 +59,12 @@ export default class ExportPDF {
|
|||
location: new UIEventSource<Loc>(loc), // We remove the link between the old and the new UI-event source as moving the map while the export is running fucks up the screenshot
|
||||
background: options.background,
|
||||
allowMoving: false,
|
||||
|
||||
|
||||
onFullyLoaded: leaflet => window.setTimeout(() => {
|
||||
onFullyLoaded: _ => window.setTimeout(() => {
|
||||
if (self._screenhotTaken) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
self.CreatePdf(leaflet)
|
||||
self.CreatePdf(minimap)
|
||||
.then(() => self.cleanup())
|
||||
.catch(() => self.cleanup())
|
||||
} catch (e) {
|
||||
|
@ -112,20 +106,17 @@ export default class ExportPDF {
|
|||
this._screenhotTaken = true;
|
||||
}
|
||||
|
||||
private async CreatePdf(leaflet: L.Map) {
|
||||
private async CreatePdf(minimap: MinimapObj) {
|
||||
|
||||
|
||||
|
||||
console.log("PDF creation started")
|
||||
const t = Translations.t.general.pdf;
|
||||
const layout = this._layout
|
||||
const screenshotter = new SimpleMapScreenshoter();
|
||||
//minimap op index.html -> hidden daar alles op doen en dan weg
|
||||
//minimap - leaflet map ophalen - boundaries ophalen - State.state.featurePipeline
|
||||
screenshotter.addTo(leaflet);
|
||||
|
||||
|
||||
let doc = new jsPDF('landscape');
|
||||
|
||||
|
||||
const image = (await screenshotter.takeScreen('image'))
|
||||
const image = await minimap.TakeScreenshot()
|
||||
// @ts-ignore
|
||||
doc.addImage(image, 'PNG', 0, 0, this.mapW, this.mapH);
|
||||
|
||||
|
|
|
@ -167,6 +167,9 @@ export default class LocationInput extends InputElement<Loc> implements MinimapO
|
|||
installBounds(factor: number | BBox, showRange?: boolean): void {
|
||||
this.map.installBounds(factor, showRange)
|
||||
}
|
||||
TakeScreenshot(): Promise<any> {
|
||||
return this.map.TakeScreenshot()
|
||||
}
|
||||
|
||||
protected InnerConstructElement(): HTMLElement {
|
||||
try {
|
||||
|
|
|
@ -58,7 +58,6 @@ export default class FeatureInfoBox extends ScrollableFullScreen {
|
|||
for (const groupName of allGroupNames) {
|
||||
const questions = layerConfig.tagRenderings.filter(tr => tr.group === groupName)
|
||||
const questionBox = new QuestionBox(tags, questions, layerConfig.units);
|
||||
console.log("Groupname:", groupName)
|
||||
questionBoxes.set(groupName, questionBox)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -155,7 +155,6 @@ export default class ShowDataLayer {
|
|||
continue
|
||||
}
|
||||
try {
|
||||
|
||||
if ((feat.geometry.type === "LineString" || feat.geometry.type === "MultiLineString")) {
|
||||
const self = this;
|
||||
const coords = L.GeoJSON.coordsToLatLngs(feat.geometry.coordinates)
|
||||
|
@ -190,9 +189,10 @@ export default class ShowDataLayer {
|
|||
|
||||
if (options.zoomToFeatures ?? false) {
|
||||
try {
|
||||
mp.fitBounds(this.geoLayer.getBounds(), {animate: false})
|
||||
const bounds = this.geoLayer.getBounds()
|
||||
mp.fitBounds(bounds, {animate: false})
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
console.debug("Invalid bounds",e)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ import Histogram from "./BigComponents/Histogram";
|
|||
import Loc from "../Models/Loc";
|
||||
import {Utils} from "../Utils";
|
||||
import LayerConfig from "../Models/ThemeConfig/LayerConfig";
|
||||
import ImportButton, {ImportButtonSpecialViz} from "./BigComponents/ImportButton";
|
||||
import {ImportButtonSpecialViz} from "./BigComponents/ImportButton";
|
||||
import {Tag} from "../Logic/Tags/Tag";
|
||||
import StaticFeatureSource from "../Logic/FeatureSource/Sources/StaticFeatureSource";
|
||||
import ShowDataMultiLayer from "./ShowDataLayer/ShowDataMultiLayer";
|
||||
|
@ -38,9 +38,9 @@ import {SubtleButton} from "./Base/SubtleButton";
|
|||
import ChangeTagAction from "../Logic/Osm/Actions/ChangeTagAction";
|
||||
import {And} from "../Logic/Tags/And";
|
||||
import Toggle from "./Input/Toggle";
|
||||
import {DefaultGuiState} from "./DefaultGUI";
|
||||
import Img from "./Base/Img";
|
||||
import FilteredLayer from "../Models/FilteredLayer";
|
||||
import {DefaultGuiState} from "./DefaultGuiState";
|
||||
|
||||
export interface SpecialVisualization {
|
||||
funcName: string,
|
||||
|
|
|
@ -8,7 +8,7 @@ import {Utils} from "../Utils";
|
|||
import {VariableUiElement} from "./Base/VariableUIElement";
|
||||
import Combine from "./Base/Combine";
|
||||
import BaseUIElement from "./BaseUIElement";
|
||||
import {DefaultGuiState} from "./DefaultGUI";
|
||||
import {DefaultGuiState} from "./DefaultGuiState";
|
||||
|
||||
export class SubstitutedTranslation extends VariableUiElement {
|
||||
|
||||
|
|
32
assets/layers/conflation/conflation.json
Normal file
32
assets/layers/conflation/conflation.json
Normal file
|
@ -0,0 +1,32 @@
|
|||
{
|
||||
"id": "conflation",
|
||||
"description": "This is a special meta_layer which render geometry-changes for inspection",
|
||||
"minzoom": 1,
|
||||
"source": {
|
||||
"osmTags": {
|
||||
"or": ["move=yes","newpoint=yes"]
|
||||
}
|
||||
},
|
||||
"name": "Conflation",
|
||||
"title": "Conflation",
|
||||
"mapRendering": [
|
||||
{
|
||||
"location": "point",
|
||||
"icon": "addSmall:#000",
|
||||
"iconSize": "10,10,center"
|
||||
},
|
||||
{
|
||||
"location": "end",
|
||||
"icon": "circle:#0f0",
|
||||
"iconSize": "10,10,center"
|
||||
},{
|
||||
"location": "start",
|
||||
"icon": "square:#f00",
|
||||
"iconSize": "10,10,center"
|
||||
},
|
||||
{
|
||||
"width": "3",
|
||||
"color": "#00f"
|
||||
}
|
||||
]
|
||||
}
|
15
assets/layers/gps_location/gps_location.json
Normal file
15
assets/layers/gps_location/gps_location.json
Normal file
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"id": "gps_location",
|
||||
"description": "Meta layer showing the current location of the user",
|
||||
"minzoom": 0,
|
||||
"source": {
|
||||
"osmTags": "user:location=yes"
|
||||
},
|
||||
"mapRendering": [
|
||||
{
|
||||
"icon": "crosshair:#00f",
|
||||
"iconSize": "40,40,center",
|
||||
"location": "point"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -5,15 +5,6 @@
|
|||
"source": {
|
||||
"osmTags": "user:home=yes"
|
||||
},
|
||||
"icon": {
|
||||
"render": "circle:white;./assets/svg/home.svg"
|
||||
},
|
||||
"iconSize": {
|
||||
"render": "20,20,center"
|
||||
},
|
||||
"color": {
|
||||
"render": "#00f"
|
||||
},
|
||||
"mapRendering": [
|
||||
{
|
||||
"icon": {
|
||||
|
@ -22,14 +13,7 @@
|
|||
"iconSize": {
|
||||
"render": "20,20,center"
|
||||
},
|
||||
"location": [
|
||||
"point"
|
||||
]
|
||||
},
|
||||
{
|
||||
"color": {
|
||||
"render": "#00f"
|
||||
}
|
||||
"location": "point"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -235,11 +235,6 @@
|
|||
}
|
||||
],
|
||||
"mapRendering": [
|
||||
{
|
||||
"location": [
|
||||
"point"
|
||||
]
|
||||
},
|
||||
{
|
||||
"color": {
|
||||
"render": "#ff7392",
|
||||
|
|
|
@ -38,6 +38,8 @@
|
|||
"override": {
|
||||
"calculatedTags": [
|
||||
"_is_part_of_building=feat.get('parent_ways')?.some(p => p.building !== undefined && p.building !== '') ?? 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_landuse=feat.get('parent_ways')?.some(p => (p.landuse !== undefined && p.landuse !== '') || (p.natural !== undefined && p.natural !== '')) ?? false"
|
||||
]
|
||||
}
|
||||
|
@ -674,7 +676,7 @@
|
|||
"mappings": [
|
||||
{
|
||||
"if": "_overlaps_with!=null",
|
||||
"then": "Cannot be imported directly, there is a nearly identical building geometry in OpenStreetMap"
|
||||
"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)}"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
3
index.ts
3
index.ts
|
@ -9,10 +9,11 @@ import {Utils} from "./Utils";
|
|||
import AllThemesGui from "./UI/AllThemesGui";
|
||||
import DetermineLayout from "./Logic/DetermineLayout";
|
||||
import LayoutConfig from "./Models/ThemeConfig/LayoutConfig";
|
||||
import DefaultGUI, {DefaultGuiState} from "./UI/DefaultGUI";
|
||||
import DefaultGUI from "./UI/DefaultGUI";
|
||||
import State from "./State";
|
||||
import AvailableBaseLayersImplementation from "./Logic/Actors/AvailableBaseLayersImplementation";
|
||||
import ShowOverlayLayerImplementation from "./UI/ShowDataLayer/ShowOverlayLayerImplementation";
|
||||
import {DefaultGuiState} from "./UI/DefaultGuiState";
|
||||
|
||||
// Workaround for a stupid crash: inject some functions which would give stupid circular dependencies or crash the other nodejs scripts running from console
|
||||
MinimapImplementation.initialize()
|
||||
|
|
163
test.ts
163
test.ts
|
@ -1,139 +1,26 @@
|
|||
import {Utils} from "./Utils";
|
||||
import FullNodeDatabaseSource from "./Logic/FeatureSource/TiledFeatureSource/FullNodeDatabaseSource";
|
||||
|
||||
|
||||
const data = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
|
||||
"<osm version=\"0.6\" generator=\"CGImap 0.8.5 (2610109 spike-08.openstreetmap.org)\" copyright=\"OpenStreetMap and contributors\" attribution=\"http://www.openstreetmap.org/copyright\" license=\"http://opendatacommons.org/licenses/odbl/1-0/\">\n" +
|
||||
" <bounds minlat=\"51.2154864\" minlon=\"3.2176208\" maxlat=\"51.2163466\" maxlon=\"3.2189941\"/>\n" +
|
||||
" <node id=\"315739208\" visible=\"true\" version=\"6\" changeset=\"11063832\" timestamp=\"2012-03-22T15:12:47Z\" user=\"zors1843\" uid=\"233248\" lat=\"51.2149470\" lon=\"3.2183010\"/>\n" +
|
||||
" <node id=\"315739215\" visible=\"true\" version=\"8\" changeset=\"7350988\" timestamp=\"2011-02-21T09:50:46Z\" user=\"zors1843\" uid=\"233248\" lat=\"51.2160281\" lon=\"3.2174966\"/>\n" +
|
||||
" <node id=\"315739216\" visible=\"true\" version=\"7\" changeset=\"11037951\" timestamp=\"2012-03-20T06:38:28Z\" user=\"zors1843\" uid=\"233248\" lat=\"51.2152977\" lon=\"3.2195995\"/>\n" +
|
||||
" <node id=\"315739242\" visible=\"true\" version=\"2\" changeset=\"11037951\" timestamp=\"2012-03-20T06:38:29Z\" user=\"zors1843\" uid=\"233248\" lat=\"51.2164491\" lon=\"3.2187218\"/>\n" +
|
||||
" <node id=\"315739243\" visible=\"true\" version=\"9\" changeset=\"11037951\" timestamp=\"2012-03-20T06:38:29Z\" user=\"zors1843\" uid=\"233248\" lat=\"51.2166162\" lon=\"3.2182807\"/>\n" +
|
||||
" <node id=\"1682824800\" visible=\"true\" version=\"2\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:21Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2154405\" lon=\"3.2193489\"/>\n" +
|
||||
" <node id=\"1682824805\" visible=\"true\" version=\"2\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:21Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2154354\" lon=\"3.2193255\"/>\n" +
|
||||
" <node id=\"1682824813\" visible=\"true\" version=\"3\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:21Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2155117\" lon=\"3.2192071\"/>\n" +
|
||||
" <node id=\"1682824815\" visible=\"true\" version=\"2\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:21Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2154693\" lon=\"3.2193015\"/>\n" +
|
||||
" <node id=\"1682824817\" visible=\"true\" version=\"3\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:21Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2153821\" lon=\"3.2193628\"/>\n" +
|
||||
" <node id=\"1682832761\" visible=\"true\" version=\"2\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:21Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2159858\" lon=\"3.2190646\"/>\n" +
|
||||
" <node id=\"1682832762\" visible=\"true\" version=\"2\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:21Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2157564\" lon=\"3.2191917\"/>\n" +
|
||||
" <node id=\"1682832764\" visible=\"true\" version=\"2\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:21Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2157971\" lon=\"3.2187018\"/>\n" +
|
||||
" <node id=\"1682832775\" visible=\"true\" version=\"2\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:21Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2153841\" lon=\"3.2186735\"/>\n" +
|
||||
" <node id=\"1682832777\" visible=\"true\" version=\"2\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:21Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2156194\" lon=\"3.2188368\"/>\n" +
|
||||
" <node id=\"1682832780\" visible=\"true\" version=\"2\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:21Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2153013\" lon=\"3.2188337\"/>\n" +
|
||||
" <node id=\"1682832782\" visible=\"true\" version=\"2\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:21Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2157716\" lon=\"3.2190346\"/>\n" +
|
||||
" <node id=\"1682832784\" visible=\"true\" version=\"2\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:21Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2153225\" lon=\"3.2189135\"/>\n" +
|
||||
" <node id=\"1682832786\" visible=\"true\" version=\"2\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:21Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2154508\" lon=\"3.2187894\"/>\n" +
|
||||
" <node id=\"1682832789\" visible=\"true\" version=\"2\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:21Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2159305\" lon=\"3.2188530\"/>\n" +
|
||||
" <node id=\"1682832791\" visible=\"true\" version=\"2\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:21Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2157323\" lon=\"3.2180624\"/>\n" +
|
||||
" <node id=\"1682832793\" visible=\"true\" version=\"2\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:21Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2159622\" lon=\"3.2188025\"/>\n" +
|
||||
" <node id=\"1682832795\" visible=\"true\" version=\"2\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:22Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2159076\" lon=\"3.2189725\"/>\n" +
|
||||
" <node id=\"1682832804\" visible=\"true\" version=\"2\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:22Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2157453\" lon=\"3.2186489\"/>\n" +
|
||||
" <node id=\"1682832808\" visible=\"true\" version=\"2\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:22Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2157935\" lon=\"3.2186116\"/>\n" +
|
||||
" <node id=\"1682832813\" visible=\"true\" version=\"3\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:22Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2152199\" lon=\"3.2186903\"/>\n" +
|
||||
" <node id=\"1682832815\" visible=\"true\" version=\"2\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:22Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2160796\" lon=\"3.2189893\"/>\n" +
|
||||
" <node id=\"1682832817\" visible=\"true\" version=\"2\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:22Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2154484\" lon=\"3.2188760\"/>\n" +
|
||||
" <node id=\"1682832818\" visible=\"true\" version=\"2\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:22Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2152395\" lon=\"3.2187686\"/>\n" +
|
||||
" <node id=\"1682832819\" visible=\"true\" version=\"2\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:22Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2153621\" lon=\"3.2185962\"/>\n" +
|
||||
" <node id=\"1682832820\" visible=\"true\" version=\"2\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:22Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2155109\" lon=\"3.2184903\"/>\n" +
|
||||
" <node id=\"1682832821\" visible=\"true\" version=\"2\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:22Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2155922\" lon=\"3.2187693\"/>\n" +
|
||||
" <node id=\"1682832825\" visible=\"true\" version=\"2\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:22Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2154945\" lon=\"3.2191530\"/>\n" +
|
||||
" <node id=\"1682832826\" visible=\"true\" version=\"2\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:22Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2157122\" lon=\"3.2192275\"/>\n" +
|
||||
" <node id=\"1682832827\" visible=\"true\" version=\"2\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:22Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2154505\" lon=\"3.2187683\"/>\n" +
|
||||
" <node id=\"1682832828\" visible=\"true\" version=\"3\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:22Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2160312\" lon=\"3.2188379\"/>\n" +
|
||||
" <node id=\"1682832829\" visible=\"true\" version=\"2\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:22Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2160071\" lon=\"3.2188563\"/>\n" +
|
||||
" <node id=\"1682832830\" visible=\"true\" version=\"2\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:22Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2158432\" lon=\"3.2189346\"/>\n" +
|
||||
" <node id=\"1682832831\" visible=\"true\" version=\"2\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:22Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2157027\" lon=\"3.2184476\"/>\n" +
|
||||
" <node id=\"1682832832\" visible=\"true\" version=\"2\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:22Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2159424\" lon=\"3.2189018\"/>\n" +
|
||||
" <node id=\"1682832833\" visible=\"true\" version=\"2\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:22Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2156684\" lon=\"3.2191183\"/>\n" +
|
||||
" <node id=\"1682832834\" visible=\"true\" version=\"2\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:23Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2158295\" lon=\"3.2185829\"/>\n" +
|
||||
" <node id=\"1682832835\" visible=\"true\" version=\"2\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:23Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2156820\" lon=\"3.2180867\"/>\n" +
|
||||
" <node id=\"1682832836\" visible=\"true\" version=\"2\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:23Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2155219\" lon=\"3.2191313\"/>\n" +
|
||||
" <node id=\"1682832837\" visible=\"true\" version=\"2\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:23Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2158056\" lon=\"3.2190810\"/>\n" +
|
||||
" <node id=\"1682832838\" visible=\"true\" version=\"2\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:23Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2157585\" lon=\"3.2190453\"/>\n" +
|
||||
" <node id=\"1682832839\" visible=\"true\" version=\"2\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:23Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2153634\" lon=\"3.2186009\"/>\n" +
|
||||
" <node id=\"1682832840\" visible=\"true\" version=\"2\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:23Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2157257\" lon=\"3.2185567\"/>\n" +
|
||||
" <node id=\"1682832841\" visible=\"true\" version=\"2\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:23Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2159207\" lon=\"3.2190210\"/>\n" +
|
||||
" <node id=\"1682832842\" visible=\"true\" version=\"2\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:23Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2160295\" lon=\"3.2190298\"/>\n" +
|
||||
" <node id=\"1682832843\" visible=\"true\" version=\"2\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:23Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2156835\" lon=\"3.2183577\"/>\n" +
|
||||
" <node id=\"1682832846\" visible=\"true\" version=\"2\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:23Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2158946\" lon=\"3.2189083\"/>\n" +
|
||||
" <node id=\"1682832848\" visible=\"true\" version=\"1\" changeset=\"11037951\" timestamp=\"2012-03-20T06:38:23Z\" user=\"zors1843\" uid=\"233248\" lat=\"51.2162257\" lon=\"3.2189152\"/>\n" +
|
||||
" <node id=\"1682832850\" visible=\"true\" version=\"2\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:23Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2156455\" lon=\"3.2189033\"/>\n" +
|
||||
" <node id=\"1682832851\" visible=\"true\" version=\"2\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:23Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2157814\" lon=\"3.2191010\"/>\n" +
|
||||
" <node id=\"1682832852\" visible=\"true\" version=\"2\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:23Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2158297\" lon=\"3.2191463\"/>\n" +
|
||||
" <node id=\"1682832853\" visible=\"true\" version=\"2\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:23Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2153884\" lon=\"3.2186848\"/>\n" +
|
||||
" <node id=\"1682832854\" visible=\"true\" version=\"2\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:23Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2155190\" lon=\"3.2191206\"/>\n" +
|
||||
" <node id=\"1682832856\" visible=\"true\" version=\"2\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:23Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2156298\" lon=\"3.2190100\"/>\n" +
|
||||
" <node id=\"1682832858\" visible=\"true\" version=\"2\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:23Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2156874\" lon=\"3.2190086\"/>\n" +
|
||||
" <node id=\"1682832859\" visible=\"true\" version=\"2\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:23Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2159818\" lon=\"3.2188688\"/>\n" +
|
||||
" <node id=\"1682832860\" visible=\"true\" version=\"2\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:23Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2159413\" lon=\"3.2190997\"/>\n" +
|
||||
" <node id=\"1682832861\" visible=\"true\" version=\"2\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:23Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2159831\" lon=\"3.2187874\"/>\n" +
|
||||
" <node id=\"1682832862\" visible=\"true\" version=\"2\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:23Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2157432\" lon=\"3.2189602\"/>\n" +
|
||||
" <node id=\"1682837112\" visible=\"true\" version=\"3\" changeset=\"113131738\" timestamp=\"2021-10-29T16:20:51Z\" user=\"Pieter Vander Vennet\" uid=\"3818858\" lat=\"51.2160077\" lon=\"3.2187651\"/>\n" +
|
||||
" <node id=\"1682837113\" visible=\"true\" version=\"2\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:24Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2161667\" lon=\"3.2187271\"/>\n" +
|
||||
" <node id=\"1682837114\" visible=\"true\" version=\"2\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:24Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2162728\" lon=\"3.2185965\"/>\n" +
|
||||
" <node id=\"1682837115\" visible=\"true\" version=\"2\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:24Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2161349\" lon=\"3.2185984\"/>\n" +
|
||||
" <node id=\"1682837120\" visible=\"true\" version=\"2\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:24Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2163153\" lon=\"3.2187451\"/>\n" +
|
||||
" <node id=\"1682837122\" visible=\"true\" version=\"2\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:24Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2164091\" lon=\"3.2186697\"/>\n" +
|
||||
" <node id=\"1682837124\" visible=\"true\" version=\"2\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:24Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2161535\" lon=\"3.2183166\"/>\n" +
|
||||
" <node id=\"1682837126\" visible=\"true\" version=\"2\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:24Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2162789\" lon=\"3.2187745\"/>\n" +
|
||||
" <node id=\"1682837128\" visible=\"true\" version=\"2\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:24Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2161929\" lon=\"3.2185504\"/>\n" +
|
||||
" <node id=\"1682837130\" visible=\"true\" version=\"2\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:24Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2162296\" lon=\"3.2186760\"/>\n" +
|
||||
" <node id=\"1682837132\" visible=\"true\" version=\"3\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:24Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2165327\" lon=\"3.2183323\"/>\n" +
|
||||
" <node id=\"1682837133\" visible=\"true\" version=\"2\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:24Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2163805\" lon=\"3.2186936\"/>\n" +
|
||||
" <node id=\"1682837134\" visible=\"true\" version=\"2\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:24Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2162456\" lon=\"3.2186644\"/>\n" +
|
||||
" <node id=\"1682837135\" visible=\"true\" version=\"2\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:24Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2162404\" lon=\"3.2188053\"/>\n" +
|
||||
" <node id=\"1682837136\" visible=\"true\" version=\"2\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:24Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2162428\" lon=\"3.2184412\"/>\n" +
|
||||
" <node id=\"1682837138\" visible=\"true\" version=\"2\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:24Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2162938\" lon=\"3.2185770\"/>\n" +
|
||||
" <node id=\"1682837142\" visible=\"true\" version=\"2\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:24Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2161321\" lon=\"3.2183620\"/>\n" +
|
||||
" <node id=\"1682837143\" visible=\"true\" version=\"2\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:24Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2161991\" lon=\"3.2188378\"/>\n" +
|
||||
" <node id=\"1682837145\" visible=\"true\" version=\"2\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:24Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2160656\" lon=\"3.2189444\"/>\n" +
|
||||
" <node id=\"1682837146\" visible=\"true\" version=\"3\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:24Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2163975\" lon=\"3.2181513\"/>\n" +
|
||||
" <node id=\"1682837147\" visible=\"true\" version=\"2\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:24Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2161668\" lon=\"3.2185723\"/>\n" +
|
||||
" <node id=\"1682837148\" visible=\"true\" version=\"2\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:24Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2161842\" lon=\"3.2187118\"/>\n" +
|
||||
" <node id=\"1682837149\" visible=\"true\" version=\"2\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:24Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2160893\" lon=\"3.2186362\"/>\n" +
|
||||
" <node id=\"1682837150\" visible=\"true\" version=\"2\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:24Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2161619\" lon=\"3.2188670\"/>\n" +
|
||||
" <node id=\"1682874140\" visible=\"true\" version=\"2\" changeset=\"50433000\" timestamp=\"2017-07-20T13:11:32Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2163477\" lon=\"3.2191243\"/>\n" +
|
||||
" <node id=\"1682874141\" visible=\"true\" version=\"2\" changeset=\"50433000\" timestamp=\"2017-07-20T13:11:32Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2160579\" lon=\"3.2190935\"/>\n" +
|
||||
" <node id=\"1682874142\" visible=\"true\" version=\"2\" changeset=\"50433000\" timestamp=\"2017-07-20T13:11:32Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2161979\" lon=\"3.2193991\"/>\n" +
|
||||
" <node id=\"1682874143\" visible=\"true\" version=\"2\" changeset=\"50433000\" timestamp=\"2017-07-20T13:11:32Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2162760\" lon=\"3.2189217\"/>\n" +
|
||||
" <node id=\"1682874144\" visible=\"true\" version=\"2\" changeset=\"50433000\" timestamp=\"2017-07-20T13:11:32Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2163468\" lon=\"3.2188607\"/>\n" +
|
||||
" <node id=\"1682874150\" visible=\"true\" version=\"2\" changeset=\"50433000\" timestamp=\"2017-07-20T13:11:32Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2163835\" lon=\"3.2190937\"/>\n" +
|
||||
" <node id=\"1682874151\" visible=\"true\" version=\"2\" changeset=\"50433000\" timestamp=\"2017-07-20T13:11:32Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2164206\" lon=\"3.2190633\"/>\n" +
|
||||
" <node id=\"1682874161\" visible=\"true\" version=\"2\" changeset=\"50433000\" timestamp=\"2017-07-20T13:11:33Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2161339\" lon=\"3.2194350\"/>\n" +
|
||||
" <node id=\"1682874164\" visible=\"true\" version=\"2\" changeset=\"50433000\" timestamp=\"2017-07-20T13:11:33Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2163293\" lon=\"3.2191391\"/>\n" +
|
||||
" <node id=\"1682874165\" visible=\"true\" version=\"2\" changeset=\"50433000\" timestamp=\"2017-07-20T13:11:33Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2160741\" lon=\"3.2191659\"/>\n" +
|
||||
" <node id=\"1682874166\" visible=\"true\" version=\"2\" changeset=\"50433000\" timestamp=\"2017-07-20T13:11:33Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2163108\" lon=\"3.2188917\"/>\n" +
|
||||
" <node id=\"1682875368\" visible=\"true\" version=\"2\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:24Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2159045\" lon=\"3.2178572\"/>\n" +
|
||||
" <node id=\"1682875370\" visible=\"true\" version=\"2\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:24Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2158354\" lon=\"3.2179572\"/>\n" +
|
||||
" <node id=\"1682875372\" visible=\"true\" version=\"2\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:24Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2158939\" lon=\"3.2176815\"/>\n" +
|
||||
" <node id=\"1682875373\" visible=\"true\" version=\"2\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:24Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2158483\" lon=\"3.2179326\"/>\n" +
|
||||
" <node id=\"1682875374\" visible=\"true\" version=\"2\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:25Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2157153\" lon=\"3.2177946\"/>\n" +
|
||||
" <node id=\"1682875381\" visible=\"true\" version=\"2\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:25Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2158748\" lon=\"3.2178159\"/>\n" +
|
||||
" <node id=\"1682875385\" visible=\"true\" version=\"2\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:25Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2158683\" lon=\"3.2179618\"/>\n" +
|
||||
" <node id=\"1682875387\" visible=\"true\" version=\"2\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:25Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2158782\" lon=\"3.2179443\"/>\n" +
|
||||
" <node id=\"1682875389\" visible=\"true\" version=\"2\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:25Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2158141\" lon=\"3.2177312\"/>\n" +
|
||||
" <node id=\"1682875395\" visible=\"true\" version=\"2\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:25Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2157649\" lon=\"3.2177632\"/>\n" +
|
||||
" <node id=\"1682876085\" visible=\"true\" version=\"2\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:25Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2155829\" lon=\"3.2178849\"/>\n" +
|
||||
" <node id=\"1682876086\" visible=\"true\" version=\"2\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:25Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2156324\" lon=\"3.2178513\"/>\n" +
|
||||
" <node id=\"1682876088\" visible=\"true\" version=\"2\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:25Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2156714\" lon=\"3.2180361\"/>\n" +
|
||||
" <node id=\"1682876091\" visible=\"true\" version=\"2\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:25Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2156926\" lon=\"3.2178103\"/>\n" +
|
||||
" <node id=\"1682876092\" visible=\"true\" version=\"2\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:25Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2156297\" lon=\"3.2178592\"/>\n" +
|
||||
" <node id=\"1682876099\" visible=\"true\" version=\"2\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:25Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2157351\" lon=\"3.2180027\"/>\n" +
|
||||
" <node id=\"1682876100\" visible=\"true\" version=\"2\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:25Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2156190\" lon=\"3.2180623\"/>\n" +
|
||||
" <node id=\"1682876102\" visible=\"true\" version=\"2\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:25Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2157232\" lon=\"3.2179435\"/>\n" +
|
||||
" <node id=\"1682877254\" visible=\"true\" version=\"3\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:25Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2154254\" lon=\"3.2179953\"/>\n" +
|
||||
" <node id=\"1682877255\" visible=\"true\" version=\"2\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:25Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2155716\" lon=\"3.2180819\"/>\n" +
|
||||
" <node id=\"1682877256\" visible=\"true\" version=\"2\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:25Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2155250\" lon=\"3.2179243\"/>\n" +
|
||||
" <node id=\"1682877257\" visible=\"true\" version=\"2\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:25Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2154821\" lon=\"3.2181863\"/>\n" +
|
||||
" <node id=\"1682879309\" visible=\"true\" version=\"2\" changeset=\"50435292\" timestamp=\"2017-07-20T15:06:25Z\" user=\"catweazle67\" uid=\"1976209\" lat=\"51.2154292\" lon=\"3.2182116\"/>\n" +
|
||||
" </osm>"
|
||||
|
||||
const url = "https://www.openstreetmap.org/api/0.6/map?bbox=3.217620849609375,51.21548639922819,3.218994140625,51.21634661126673"
|
||||
Utils.downloadJson(url).then(data =>{
|
||||
const osmSource = {
|
||||
rawDataHandlers : []
|
||||
}
|
||||
new FullNodeDatabaseSource(osmSource)
|
||||
osmSource.rawDataHandlers[0]( data, 0)
|
||||
import ShowDataLayer from "./UI/ShowDataLayer/ShowDataLayer";
|
||||
import AllKnownLayers from "./Customizations/AllKnownLayers";
|
||||
import Minimap from "./UI/Base/Minimap";
|
||||
import StaticFeatureSource from "./Logic/FeatureSource/Sources/StaticFeatureSource";
|
||||
import MinimapImplementation from "./UI/Base/MinimapImplementation";
|
||||
import AvailableBaseLayers from "./Logic/Actors/AvailableBaseLayers";
|
||||
import BaseLayer from "./Models/BaseLayer";
|
||||
import {UIEventSource} from "./Logic/UIEventSource";
|
||||
import AvailableBaseLayersImplementation from "./Logic/Actors/AvailableBaseLayersImplementation";
|
||||
MinimapImplementation.initialize()
|
||||
AvailableBaseLayers.implement(new AvailableBaseLayersImplementation())
|
||||
const confirmationMap = Minimap.createMiniMap({
|
||||
background: new UIEventSource<BaseLayer>(AvailableBaseLayers.osmCarto)
|
||||
})
|
||||
const features = [{"feature":{"type":"Feature","properties":{"move":"yes","osm-id":1728823483},"geometry":{"type":"LineString","coordinates":[[3.216693,51.2147409],[3.2166930000000225,51.214740500000055]]}},"freshness":"2021-11-02T20:06:53.088Z"},{"feature":{"type":"Feature","properties":{"move":"yes","osm-id":1728823481},"geometry":{"type":"LineString","coordinates":[[3.2167247,51.2146969],[3.21671060000004,51.2147159000002]]}},"freshness":"2021-11-02T20:06:53.088Z"},{"feature":{"type":"Feature","properties":{"move":"yes","osm-id":1728823481},"geometry":{"type":"LineString","coordinates":[[3.2167247,51.2146969],[3.2167241999999976,51.214696799999714]]}},"freshness":"2021-11-02T20:06:53.088Z"},{"feature":{"type":"Feature","properties":{"move":"yes","osm-id":1728823549},"geometry":{"type":"LineString","coordinates":[[3.2168871,51.2147399],[3.2168876999999547,51.21474009999989]]}},"freshness":"2021-11-02T20:06:53.088Z"},{"feature":{"type":"Feature","properties":{"move":"yes","osm-id":4978289383},"geometry":{"type":"LineString","coordinates":[[3.2169973,51.2147676],[3.2169969000000034,51.21476780000005]]}},"freshness":"2021-11-02T20:06:53.088Z"},{"feature":{"type":"Feature","properties":{"move":"yes","osm-id":4978289388},"geometry":{"type":"LineString","coordinates":[[3.2169829,51.2147884],[3.2169673999999895,51.21481170000002]]}},"freshness":"2021-11-02T20:06:53.088Z"},{"feature":{"type":"Feature","properties":{"move":"yes","osm-id":4978289388},"geometry":{"type":"LineString","coordinates":[[3.2169829,51.2147884],[3.216949899999979,51.214808000000225]]}},"freshness":"2021-11-02T20:06:53.088Z"},{"feature":{"type":"Feature","properties":{"move":"yes","osm-id":4978289388},"geometry":{"type":"LineString","coordinates":[[3.2169829,51.2147884],[3.2169306,51.21480400000028]]}},"freshness":"2021-11-02T20:06:53.088Z"},{"feature":{"type":"Feature","properties":{"move":"yes","osm-id":4978289388},"geometry":{"type":"LineString","coordinates":[[3.2169829,51.2147884],[3.2169465999999756,51.214779199999825]]}},"freshness":"2021-11-02T20:06:53.088Z"},{"feature":{"type":"Feature","properties":{"move":"yes","osm-id":4978288381},"geometry":{"type":"LineString","coordinates":[[3.2168856,51.2147638],[3.216885599999961,51.214763799999986]]}},"freshness":"2021-11-02T20:06:53.088Z"},{"feature":{"type":"Feature","properties":{"move":"yes","osm-id":4978289386},"geometry":{"type":"LineString","coordinates":[[3.2168815,51.2147718],[3.216881100000038,51.21477160000009]]}},"freshness":"2021-11-02T20:06:53.088Z"},{"feature":{"type":"Feature","properties":{"move":"yes","osm-id":4978289384},"geometry":{"type":"LineString","coordinates":[[3.2168674,51.2147683],[3.216867399999983,51.214768400000224]]}},"freshness":"2021-11-02T20:06:53.088Z"},{"feature":{"type":"Feature","properties":{"move":"yes","osm-id":1728823514},"geometry":{"type":"LineString","coordinates":[[3.2168551,51.2147863],[3.2168551000000436,51.21478629999984]]}},"freshness":"2021-11-02T20:06:53.088Z"},{"feature":{"type":"Feature","properties":{"move":"yes","osm-id":1728823483},"geometry":{"type":"LineString","coordinates":[[3.216693,51.2147409],[3.2166930000000225,51.214740500000055]]}},"freshness":"2021-11-02T20:06:53.088Z"}]
|
||||
const changePreview = new StaticFeatureSource(features.map(f => f.feature), false)
|
||||
console.log("ChangePreview", changePreview.features.data)
|
||||
new ShowDataLayer({
|
||||
leafletMap: confirmationMap.leafletMap,
|
||||
enablePopups: false,
|
||||
zoomToFeatures: true,
|
||||
features: changePreview,
|
||||
layerToShow: AllKnownLayers.sharedLayers.get("conflation")
|
||||
})
|
||||
|
||||
confirmationMap.SetStyle("height: 20rem").SetClass("w-full").AttachTo("maindiv")
|
185
test/ReplaceGeometry.spec.ts
Normal file
185
test/ReplaceGeometry.spec.ts
Normal file
|
@ -0,0 +1,185 @@
|
|||
import T from "./TestHelper";
|
||||
import FullNodeDatabaseSource from "../Logic/FeatureSource/TiledFeatureSource/FullNodeDatabaseSource";
|
||||
import {Utils} from "../Utils";
|
||||
|
||||
export default class ReplaceGeometrySpec extends T {
|
||||
constructor() {
|
||||
super("ReplaceGeometry", [
|
||||
["Simple house replacement", async () => {
|
||||
const coordinates = <[number, number][]>[[
|
||||
3.216690793633461,
|
||||
51.21474084112525
|
||||
],
|
||||
[
|
||||
3.2167256623506546,
|
||||
51.214696737309964
|
||||
],
|
||||
[
|
||||
3.2169999182224274,
|
||||
51.214768983537674
|
||||
],
|
||||
[
|
||||
3.2169650495052338,
|
||||
51.21480720678671
|
||||
],
|
||||
[
|
||||
3.2169368863105774,
|
||||
51.21480090625335
|
||||
],
|
||||
[
|
||||
3.2169489562511444,
|
||||
51.21478074454077
|
||||
],
|
||||
[
|
||||
3.216886594891548,
|
||||
51.214765203214625
|
||||
],
|
||||
[
|
||||
3.2168812304735184,
|
||||
51.21477192378873
|
||||
],
|
||||
[
|
||||
3.2168644666671753,
|
||||
51.214768983537674
|
||||
],
|
||||
[
|
||||
3.2168537378311157,
|
||||
51.21478746511261
|
||||
],
|
||||
[
|
||||
3.216690793633461,
|
||||
51.21474084112525
|
||||
]
|
||||
]
|
||||
|
||||
Utils.injectJsonDownloadForTests(
|
||||
"https://www.openstreetmap.org/api/0.6/way/160909312/full",
|
||||
{
|
||||
"version": "0.6",
|
||||
"generator": "CGImap 0.8.5 (920083 spike-06.openstreetmap.org)",
|
||||
"copyright": "OpenStreetMap and contributors",
|
||||
"attribution": "http://www.openstreetmap.org/copyright",
|
||||
"license": "http://opendatacommons.org/licenses/odbl/1-0/",
|
||||
"elements": [{
|
||||
"type": "node",
|
||||
"id": 1728823481,
|
||||
"lat": 51.2146969,
|
||||
"lon": 3.2167247,
|
||||
"timestamp": "2017-07-18T22:52:45Z",
|
||||
"version": 2,
|
||||
"changeset": 50391526,
|
||||
"user": "catweazle67",
|
||||
"uid": 1976209
|
||||
}, {
|
||||
"type": "node",
|
||||
"id": 1728823483,
|
||||
"lat": 51.2147409,
|
||||
"lon": 3.216693,
|
||||
"timestamp": "2017-07-18T22:52:45Z",
|
||||
"version": 2,
|
||||
"changeset": 50391526,
|
||||
"user": "catweazle67",
|
||||
"uid": 1976209
|
||||
}, {
|
||||
"type": "node",
|
||||
"id": 1728823514,
|
||||
"lat": 51.2147863,
|
||||
"lon": 3.2168551,
|
||||
"timestamp": "2017-07-18T22:52:45Z",
|
||||
"version": 2,
|
||||
"changeset": 50391526,
|
||||
"user": "catweazle67",
|
||||
"uid": 1976209
|
||||
}, {
|
||||
"type": "node",
|
||||
"id": 1728823549,
|
||||
"lat": 51.2147399,
|
||||
"lon": 3.2168871,
|
||||
"timestamp": "2017-07-18T22:52:46Z",
|
||||
"version": 2,
|
||||
"changeset": 50391526,
|
||||
"user": "catweazle67",
|
||||
"uid": 1976209
|
||||
}, {
|
||||
"type": "node",
|
||||
"id": 4978288381,
|
||||
"lat": 51.2147638,
|
||||
"lon": 3.2168856,
|
||||
"timestamp": "2017-07-18T22:52:21Z",
|
||||
"version": 1,
|
||||
"changeset": 50391526,
|
||||
"user": "catweazle67",
|
||||
"uid": 1976209
|
||||
}, {
|
||||
"type": "node",
|
||||
"id": 4978289383,
|
||||
"lat": 51.2147676,
|
||||
"lon": 3.2169973,
|
||||
"timestamp": "2017-07-18T22:52:21Z",
|
||||
"version": 1,
|
||||
"changeset": 50391526,
|
||||
"user": "catweazle67",
|
||||
"uid": 1976209
|
||||
}, {
|
||||
"type": "node",
|
||||
"id": 4978289384,
|
||||
"lat": 51.2147683,
|
||||
"lon": 3.2168674,
|
||||
"timestamp": "2017-07-18T22:52:21Z",
|
||||
"version": 1,
|
||||
"changeset": 50391526,
|
||||
"user": "catweazle67",
|
||||
"uid": 1976209
|
||||
}, {
|
||||
"type": "node",
|
||||
"id": 4978289386,
|
||||
"lat": 51.2147718,
|
||||
"lon": 3.2168815,
|
||||
"timestamp": "2017-07-18T22:52:21Z",
|
||||
"version": 1,
|
||||
"changeset": 50391526,
|
||||
"user": "catweazle67",
|
||||
"uid": 1976209
|
||||
}, {
|
||||
"type": "node",
|
||||
"id": 4978289388,
|
||||
"lat": 51.2147884,
|
||||
"lon": 3.2169829,
|
||||
"timestamp": "2017-07-18T22:52:21Z",
|
||||
"version": 1,
|
||||
"changeset": 50391526,
|
||||
"user": "catweazle67",
|
||||
"uid": 1976209
|
||||
}, {
|
||||
"type": "way",
|
||||
"id": 160909312,
|
||||
"timestamp": "2017-07-18T22:52:30Z",
|
||||
"version": 2,
|
||||
"changeset": 50391526,
|
||||
"user": "catweazle67",
|
||||
"uid": 1976209,
|
||||
"nodes": [1728823483, 1728823514, 4978289384, 4978289386, 4978288381, 4978289388, 4978289383, 1728823549, 1728823481, 1728823483],
|
||||
"tags": {
|
||||
"addr:city": "Brugge",
|
||||
"addr:country": "BE",
|
||||
"addr:housenumber": "108",
|
||||
"addr:postcode": "8000",
|
||||
"addr:street": "Ezelstraat",
|
||||
"building": "yes"
|
||||
}
|
||||
}]
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
const wayId = "way/160909312"
|
||||
const url = `https://www.openstreetmap.org/api/0.6/${wayId}/full`;
|
||||
const rawData = await Utils.downloadJsonCached(url, 1000)
|
||||
|
||||
|
||||
|
||||
|
||||
}]
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -13,6 +13,7 @@ import TileFreshnessCalculatorSpec from "./TileFreshnessCalculator.spec";
|
|||
import WikidataSpecTest from "./Wikidata.spec.test";
|
||||
import ImageProviderSpec from "./ImageProvider.spec";
|
||||
import ActorsSpec from "./Actors.spec";
|
||||
import ReplaceGeometrySpec from "./ReplaceGeometry.spec";
|
||||
|
||||
|
||||
ScriptUtils.fixUtils()
|
||||
|
@ -29,7 +30,8 @@ const allTests = [
|
|||
new TileFreshnessCalculatorSpec(),
|
||||
new WikidataSpecTest(),
|
||||
new ImageProviderSpec(),
|
||||
new ActorsSpec()
|
||||
new ActorsSpec(),
|
||||
new ReplaceGeometrySpec()
|
||||
]
|
||||
|
||||
Utils.externalDownloadFunction = async (url) => {
|
||||
|
|
Loading…
Reference in a new issue