Add the possibility to snap onto another layer with imports, add location confirm on input, add metalayer exporting all nodes, various fixes
This commit is contained in:
parent
f5d6441b70
commit
23ae9d39c8
24 changed files with 807 additions and 390 deletions
|
@ -26,6 +26,7 @@ import {OsmConnection} from "../Osm/OsmConnection";
|
|||
import {Tiles} from "../../Models/TileRange";
|
||||
import TileFreshnessCalculator from "./TileFreshnessCalculator";
|
||||
import {ElementStorage} from "../ElementStorage";
|
||||
import FullNodeDatabaseSource from "./TiledFeatureSource/FullNodeDatabaseSource";
|
||||
|
||||
|
||||
/**
|
||||
|
@ -146,6 +147,11 @@ export default class FeaturePipeline {
|
|||
|
||||
this.freshnesses.set(id, new TileFreshnessCalculator())
|
||||
|
||||
if(id === "type_node"){
|
||||
// Handles by the 'FullNodeDatabaseSource'
|
||||
continue;
|
||||
}
|
||||
|
||||
if (source.geojsonSource === undefined) {
|
||||
// This is an OSM layer
|
||||
// We load the cached values and register them
|
||||
|
@ -220,6 +226,14 @@ export default class FeaturePipeline {
|
|||
self.freshnesses.get(flayer.layerDef.id).addTileLoad(tileId, new Date())
|
||||
})
|
||||
})
|
||||
|
||||
if(state.layoutToUse.trackAllNodes){
|
||||
new FullNodeDatabaseSource(state, osmFeatureSource, tile => {
|
||||
new RegisteringAllFromFeatureSourceActor(tile)
|
||||
perLayerHierarchy.get(tile.layer.layerDef.id).registerTile(tile)
|
||||
tile.features.addCallbackAndRunD(_ => self.newDataLoadedSignal.setData(tile))
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
const updater = this.initOverpassUpdater(state, useOsmApi)
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
import {UIEventSource} from "../../UIEventSource";
|
||||
import FilteredLayer from "../../../Models/FilteredLayer";
|
||||
import {FeatureSourceForLayer, Tiled} from "../FeatureSource";
|
||||
import {Utils} from "../../../Utils";
|
||||
import {Tiles} from "../../../Models/TileRange";
|
||||
import {BBox} from "../../BBox";
|
||||
|
||||
export default class SimpleFeatureSource implements FeatureSourceForLayer, Tiled {
|
||||
|
|
|
@ -0,0 +1,70 @@
|
|||
import TileHierarchy from "./TileHierarchy";
|
||||
import FeatureSource, {FeatureSourceForLayer, Tiled} from "../FeatureSource";
|
||||
import {OsmNode, OsmObject, OsmWay} from "../../Osm/OsmObject";
|
||||
import SimpleFeatureSource from "../Sources/SimpleFeatureSource";
|
||||
import {UIEventSource} from "../../UIEventSource";
|
||||
import FilteredLayer from "../../../Models/FilteredLayer";
|
||||
|
||||
|
||||
export default class FullNodeDatabaseSource implements TileHierarchy<FeatureSource & Tiled> {
|
||||
public readonly loadedTiles = new Map<number, FeatureSource & Tiled>()
|
||||
private readonly onTileLoaded: (tile: (Tiled & FeatureSourceForLayer)) => void;
|
||||
private readonly layer : FilteredLayer
|
||||
|
||||
constructor(
|
||||
state: {
|
||||
readonly filteredLayers: UIEventSource<FilteredLayer[]>},
|
||||
osmFeatureSource: { rawDataHandlers: ((data: any, tileId: number) => void)[] },
|
||||
onTileLoaded: ((tile: Tiled & FeatureSourceForLayer) => void)) {
|
||||
this.onTileLoaded = onTileLoaded
|
||||
this.layer = state.filteredLayers.data.filter(l => l.layerDef.id === "type_node")[0]
|
||||
if(this.layer === undefined){
|
||||
throw "Weird: tracking all nodes, but layer 'type_node' is not defined"
|
||||
}
|
||||
const self = this
|
||||
osmFeatureSource.rawDataHandlers.push((osmJson, tileId) => self.handleOsmXml(osmJson, tileId))
|
||||
}
|
||||
|
||||
private handleOsmXml(osmJson: any, tileId: number) {
|
||||
|
||||
const allObjects = OsmObject.ParseObjects(osmJson.elements)
|
||||
const nodesById = new Map<number, OsmNode>()
|
||||
|
||||
for (const osmObj of allObjects) {
|
||||
if (osmObj.type !== "node") {
|
||||
continue
|
||||
}
|
||||
const osmNode = <OsmNode>osmObj;
|
||||
nodesById.set(osmNode.id, osmNode)
|
||||
}
|
||||
|
||||
const parentWaysByNodeId = new Map<number, OsmWay[]>()
|
||||
for (const osmObj of allObjects) {
|
||||
if (osmObj.type !== "way") {
|
||||
continue
|
||||
}
|
||||
const osmWay = <OsmWay>osmObj;
|
||||
for (const nodeId of osmWay.nodes) {
|
||||
|
||||
if (!parentWaysByNodeId.has(nodeId)) {
|
||||
parentWaysByNodeId.set(nodeId, [])
|
||||
}
|
||||
parentWaysByNodeId.get(nodeId).push(osmWay)
|
||||
}
|
||||
}
|
||||
parentWaysByNodeId.forEach((allWays, nodeId) => {
|
||||
nodesById.get(nodeId).tags["parent_ways"] = JSON.stringify(allWays.map(w => w.tags))
|
||||
})
|
||||
const now = new Date()
|
||||
const asGeojsonFeatures = Array.from(nodesById.values()).map(osmNode => ({
|
||||
feature: osmNode.asGeoJson(),freshness: now
|
||||
}))
|
||||
|
||||
const featureSource = new SimpleFeatureSource(this.layer, tileId)
|
||||
featureSource.features.setData(asGeojsonFeatures)
|
||||
this.loadedTiles.set(tileId, featureSource)
|
||||
this.onTileLoaded(featureSource)
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -30,6 +30,8 @@ export default class OsmFeatureSource {
|
|||
};
|
||||
public readonly downloadedTiles = new Set<number>()
|
||||
private readonly allowedTags: TagsFilter;
|
||||
|
||||
public rawDataHandlers: ((osmJson: any, tileId: number) => void)[] = []
|
||||
|
||||
constructor(options: {
|
||||
handleTile: (tile: FeatureSourceForLayer & Tiled) => void;
|
||||
|
@ -94,11 +96,11 @@ export default class OsmFeatureSource {
|
|||
try {
|
||||
|
||||
console.log("Attempting to get tile", z, x, y, "from the osm api")
|
||||
const osmXml = await Utils.download(url, {"accept": "application/xml"})
|
||||
const osmJson = await Utils.downloadJson(url)
|
||||
try {
|
||||
const parsed = new DOMParser().parseFromString(osmXml, "text/xml");
|
||||
console.log("Got tile", z, x, y, "from the osm api")
|
||||
const geojson = OsmToGeoJson.default(parsed,
|
||||
this.rawDataHandlers.forEach(handler => handler(osmJson, Tiles.tile_index(z, x, y)))
|
||||
const geojson = OsmToGeoJson.default(osmJson,
|
||||
// @ts-ignore
|
||||
{
|
||||
flatProperties: true
|
||||
|
|
|
@ -235,6 +235,13 @@ export class GeoOperations {
|
|||
* @param point Point defined as [lon, lat]
|
||||
*/
|
||||
public static nearestPoint(way, point: [number, number]) {
|
||||
if(way.geometry.type === "Polygon"){
|
||||
way = {...way}
|
||||
way.geometry = {...way.geometry}
|
||||
way.geometry.type = "LineString"
|
||||
way.geometry.coordinates = way.geometry.coordinates[0]
|
||||
}
|
||||
|
||||
return turf.nearestPointOnLine(way, point, {units: "kilometers"});
|
||||
}
|
||||
|
||||
|
|
|
@ -4,23 +4,40 @@ import {Changes} from "../Changes";
|
|||
import {Tag} from "../../Tags/Tag";
|
||||
import CreateNewNodeAction from "./CreateNewNodeAction";
|
||||
import {And} from "../../Tags/And";
|
||||
import {TagsFilter} from "../../Tags/TagsFilter";
|
||||
|
||||
export default class CreateNewWayAction extends OsmChangeAction {
|
||||
public newElementId: string = undefined
|
||||
private readonly coordinates: ({ nodeId?: number, lat: number, lon: number })[];
|
||||
private readonly tags: Tag[];
|
||||
private readonly _options: { theme: string };
|
||||
private readonly _options: {
|
||||
theme: string, existingPointHandling?: {
|
||||
withinRangeOfM: number,
|
||||
ifMatches?: TagsFilter,
|
||||
mode: "reuse_osm_point" | "move_osm_point"
|
||||
} []
|
||||
};
|
||||
|
||||
|
||||
/***
|
||||
* Creates a new way to upload to OSM
|
||||
* @param tags: the tags to apply to the wya
|
||||
* @param coordinates: the coordinates. Might have a nodeId, in this case, this node will be used
|
||||
* @param options
|
||||
* @param options
|
||||
*/
|
||||
constructor(tags: Tag[], coordinates: ({ nodeId?: number, lat: number, lon: number })[], options: {
|
||||
theme: string
|
||||
}) {
|
||||
constructor(tags: Tag[], coordinates: ({ nodeId?: number, lat: number, lon: number })[],
|
||||
options: {
|
||||
theme: string,
|
||||
/**
|
||||
* IF specified, an existing OSM-point within this range and satisfying the condition 'ifMatches' will be used instead of a new coordinate.
|
||||
* If multiple points are possible, only the closest point is considered
|
||||
*/
|
||||
existingPointHandling?: {
|
||||
withinRangeOfM: number,
|
||||
ifMatches?: TagsFilter,
|
||||
mode: "reuse_osm_point" | "move_osm_point"
|
||||
} []
|
||||
}) {
|
||||
super()
|
||||
this.coordinates = coordinates;
|
||||
this.tags = tags;
|
||||
|
@ -49,14 +66,14 @@ export default class CreateNewWayAction extends OsmChangeAction {
|
|||
|
||||
// We have all created (or reused) all the points!
|
||||
// Time to create the actual way
|
||||
|
||||
|
||||
|
||||
|
||||
const id = changes.getNewID()
|
||||
|
||||
const newWay = <ChangeDescription> {
|
||||
|
||||
const newWay = <ChangeDescription>{
|
||||
id,
|
||||
type: "way",
|
||||
meta:{
|
||||
meta: {
|
||||
theme: this._options.theme,
|
||||
changeType: "import"
|
||||
},
|
||||
|
@ -67,7 +84,7 @@ export default class CreateNewWayAction extends OsmChangeAction {
|
|||
}
|
||||
}
|
||||
newElements.push(newWay)
|
||||
this.newElementId = "way/"+id
|
||||
this.newElementId = "way/" + id
|
||||
return newElements
|
||||
}
|
||||
|
||||
|
|
|
@ -206,7 +206,7 @@ export abstract class OsmObject {
|
|||
return result;
|
||||
}
|
||||
|
||||
private static ParseObjects(elements: any[]): OsmObject[] {
|
||||
public static ParseObjects(elements: any[]): OsmObject[] {
|
||||
const objects: OsmObject[] = [];
|
||||
const allNodes: Map<number, OsmNode> = new Map<number, OsmNode>()
|
||||
|
||||
|
|
|
@ -15,6 +15,8 @@ import LineRenderingConfig from "./LineRenderingConfig";
|
|||
import PointRenderingConfigJson from "./Json/PointRenderingConfigJson";
|
||||
import LineRenderingConfigJson from "./Json/LineRenderingConfigJson";
|
||||
import {TagRenderingConfigJson} from "./Json/TagRenderingConfigJson";
|
||||
import {UIEventSource} from "../../Logic/UIEventSource";
|
||||
import BaseUIElement from "../../UI/BaseUIElement";
|
||||
|
||||
export default class LayerConfig extends WithContextLoader {
|
||||
|
||||
|
@ -59,11 +61,11 @@ export default class LayerConfig extends WithContextLoader {
|
|||
this.id = json.id;
|
||||
|
||||
if (json.source === undefined) {
|
||||
throw "Layer " + this.id + " does not define a source section ("+context+")"
|
||||
throw "Layer " + this.id + " does not define a source section (" + context + ")"
|
||||
}
|
||||
|
||||
if (json.source.osmTags === undefined) {
|
||||
throw "Layer " + this.id + " does not define a osmTags in the source section - these should always be present, even for geojson layers ("+context+")"
|
||||
throw "Layer " + this.id + " does not define a osmTags in the source section - these should always be present, even for geojson layers (" + context + ")"
|
||||
|
||||
}
|
||||
|
||||
|
@ -262,6 +264,15 @@ export default class LayerConfig extends WithContextLoader {
|
|||
}
|
||||
}
|
||||
|
||||
public defaultIcon() : BaseUIElement | undefined{
|
||||
const mapRendering = this.mapRendering.filter(r => r.location.has("point"))[0]
|
||||
if (mapRendering === undefined) {
|
||||
return undefined
|
||||
}
|
||||
const defaultTags = new UIEventSource(TagUtils.changeAsProperties(this.source.osmTags.asChange({id: "node/-1"})))
|
||||
return mapRendering.GenerateLeafletStyle(defaultTags, false, {noSize: true}).html
|
||||
}
|
||||
|
||||
public ExtractLayerTagRenderings(json: LayerConfigJson): TagRenderingConfig[] {
|
||||
|
||||
if (json.tagRenderings === undefined) {
|
||||
|
@ -358,7 +369,6 @@ export default class LayerConfig extends WithContextLoader {
|
|||
|
||||
}
|
||||
|
||||
|
||||
public CustomCodeSnippets(): string[] {
|
||||
if (this.calculatedTags === undefined) {
|
||||
return [];
|
||||
|
@ -366,7 +376,6 @@ export default class LayerConfig extends WithContextLoader {
|
|||
return this.calculatedTags.map((code) => code[1]);
|
||||
}
|
||||
|
||||
|
||||
public ExtractImages(): Set<string> {
|
||||
const parts: Set<string>[] = [];
|
||||
parts.push(...this.tagRenderings?.map((tr) => tr.ExtractImages(false)));
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
import {Translation} from "../../UI/i18n/Translation";
|
||||
import TagRenderingConfig from "./TagRenderingConfig";
|
||||
import {LayoutConfigJson} from "./Json/LayoutConfigJson";
|
||||
import SharedTagRenderings from "../../Customizations/SharedTagRenderings";
|
||||
import AllKnownLayers from "../../Customizations/AllKnownLayers";
|
||||
import {Utils} from "../../Utils";
|
||||
import LayerConfig from "./LayerConfig";
|
||||
|
@ -54,6 +52,7 @@ export default class LayoutConfig {
|
|||
public readonly overpassMaxZoom: number
|
||||
public readonly osmApiTileSize: number
|
||||
public readonly official: boolean;
|
||||
public readonly trackAllNodes : boolean;
|
||||
|
||||
constructor(json: LayoutConfigJson, official = true, context?: string) {
|
||||
this.official = official;
|
||||
|
@ -63,6 +62,8 @@ export default class LayoutConfig {
|
|||
this.credits = json.credits;
|
||||
this.version = json.version;
|
||||
this.language = [];
|
||||
this.trackAllNodes = false
|
||||
|
||||
if (typeof json.language === "string") {
|
||||
this.language = [json.language];
|
||||
} else {
|
||||
|
@ -92,12 +93,16 @@ export default class LayoutConfig {
|
|||
if(json.widenFactor > 20){
|
||||
throw "Widenfactor is very big, use a value between 1 and 5 (current value is "+json.widenFactor+") at "+context
|
||||
}
|
||||
|
||||
this.widenFactor = json.widenFactor ?? 1.5;
|
||||
|
||||
this.defaultBackgroundId = json.defaultBackgroundId;
|
||||
this.tileLayerSources = (json.tileLayerSources??[]).map((config, i) => new TilesourceConfig(config, `${this.id}.tileLayerSources[${i}]`))
|
||||
this.layers = LayoutConfig.ExtractLayers(json, official, context);
|
||||
|
||||
const layerInfo = LayoutConfig.ExtractLayers(json, official, context);
|
||||
this.layers = layerInfo.layers
|
||||
this.trackAllNodes = layerInfo.extractAllNodes
|
||||
|
||||
|
||||
this.clustering = {
|
||||
maxZoom: 16,
|
||||
minNeededElements: 25,
|
||||
|
@ -147,10 +152,11 @@ export default class LayoutConfig {
|
|||
|
||||
}
|
||||
|
||||
private static ExtractLayers(json: LayoutConfigJson, official: boolean, context: string): LayerConfig[] {
|
||||
private static ExtractLayers(json: LayoutConfigJson, official: boolean, context: string): {layers: LayerConfig[], extractAllNodes: boolean} {
|
||||
const result: LayerConfig[] = []
|
||||
|
||||
let exportAllNodes = false
|
||||
json.layers.forEach((layer, i) => {
|
||||
|
||||
if (typeof layer === "string") {
|
||||
if (AllKnownLayers.sharedLayersJson.get(layer) !== undefined) {
|
||||
if (json.overrideAll !== undefined) {
|
||||
|
@ -177,12 +183,19 @@ export default class LayoutConfig {
|
|||
result.push(newLayer)
|
||||
return
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
let names = layer.builtin;
|
||||
if (typeof names === "string") {
|
||||
names = [names]
|
||||
}
|
||||
names.forEach(name => {
|
||||
|
||||
if(name === "type_node"){
|
||||
// This is a very special layer which triggers special behaviour
|
||||
exportAllNodes = true;
|
||||
}
|
||||
|
||||
const shared = AllKnownLayers.sharedLayersJson.get(name);
|
||||
if (shared === undefined) {
|
||||
throw `Unknown shared/builtin layer ${name} at ${context}.layers[${i}]. Available layers are ${Array.from(AllKnownLayers.sharedLayersJson.keys()).join(", ")}`;
|
||||
|
@ -199,7 +212,7 @@ export default class LayoutConfig {
|
|||
|
||||
});
|
||||
|
||||
return result
|
||||
return {layers: result, extractAllNodes: exportAllNodes}
|
||||
}
|
||||
|
||||
public CustomCodeSnippets(): string[] {
|
||||
|
|
|
@ -14,7 +14,6 @@ import FilteredLayer from "../../Models/FilteredLayer";
|
|||
import BackgroundSelector from "./BackgroundSelector";
|
||||
import FilterConfig from "../../Models/ThemeConfig/FilterConfig";
|
||||
import TilesourceConfig from "../../Models/ThemeConfig/TilesourceConfig";
|
||||
import {TagUtils} from "../../Logic/Tags/TagUtils";
|
||||
|
||||
export default class FilterView extends VariableUiElement {
|
||||
constructor(filteredLayer: UIEventSource<FilteredLayer[]>, tileLayers: { config: TilesourceConfig, isDisplayed: UIEventSource<boolean> }[]) {
|
||||
|
@ -83,7 +82,7 @@ export default class FilterView extends VariableUiElement {
|
|||
|
||||
const icon = new Combine([Svg.checkbox_filled]).SetStyle(iconStyle);
|
||||
const layer = filteredLayer.layerDef
|
||||
|
||||
|
||||
const iconUnselected = new Combine([Svg.checkbox_empty]).SetStyle(
|
||||
iconStyle
|
||||
);
|
||||
|
@ -113,18 +112,8 @@ export default class FilterView extends VariableUiElement {
|
|||
|
||||
const style =
|
||||
"display:flex;align-items:center;padding:0.5rem 0;";
|
||||
const mapRendering = layer.mapRendering.filter(r => r.location.has("point"))[0]
|
||||
let layerIcon = undefined
|
||||
let layerIconUnchecked = undefined
|
||||
try {
|
||||
if (mapRendering !== undefined) {
|
||||
const defaultTags = new UIEventSource( TagUtils.changeAsProperties(layer.source.osmTags.asChange({id: "node/-1"})))
|
||||
layerIcon = mapRendering.GenerateLeafletStyle(defaultTags, false, {noSize: true}).html.SetClass("w-8 h-8 ml-2")
|
||||
layerIconUnchecked = mapRendering.GenerateLeafletStyle(defaultTags, false, {noSize: true}).html.SetClass("opacity-50 w-8 h-8 ml-2")
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
const layerIcon = layer.defaultIcon()?.SetClass("w-8 h-8 ml-2")
|
||||
const layerIconUnchecked = layer.defaultIcon()?.SetClass("opacity-50 w-8 h-8 ml-2")
|
||||
|
||||
const layerChecked = new Combine([icon, layerIcon, styledNameChecked, zoomStatus])
|
||||
.SetStyle(style)
|
||||
|
|
|
@ -16,29 +16,165 @@ import {OsmConnection} from "../../Logic/Osm/OsmConnection";
|
|||
import {Changes} from "../../Logic/Osm/Changes";
|
||||
import {ElementStorage} from "../../Logic/ElementStorage";
|
||||
import FeaturePipeline from "../../Logic/FeatureSource/FeaturePipeline";
|
||||
import Lazy from "../Base/Lazy";
|
||||
import ConfirmLocationOfPoint from "../NewPoint/ConfirmLocationOfPoint";
|
||||
import {PresetInfo} from "./SimpleAddUI";
|
||||
import Img from "../Base/Img";
|
||||
import {Translation} from "../i18n/Translation";
|
||||
import FilteredLayer from "../../Models/FilteredLayer";
|
||||
import SpecialVisualizations, {SpecialVisualization} from "../SpecialVisualizations";
|
||||
import {FixedUiElement} from "../Base/FixedUiElement";
|
||||
import Svg from "../../Svg";
|
||||
import {Utils} from "../../Utils";
|
||||
|
||||
|
||||
export interface ImportButtonState {
|
||||
description?: Translation;
|
||||
image: () => BaseUIElement,
|
||||
message: string | BaseUIElement,
|
||||
originalTags: UIEventSource<any>,
|
||||
newTags: UIEventSource<Tag[]>,
|
||||
targetLayer: FilteredLayer,
|
||||
feature: any,
|
||||
minZoom: number,
|
||||
state: {
|
||||
featureSwitchUserbadge: UIEventSource<boolean>;
|
||||
featurePipeline: FeaturePipeline;
|
||||
allElements: ElementStorage;
|
||||
selectedElement: UIEventSource<any>;
|
||||
layoutToUse: LayoutConfig,
|
||||
osmConnection: OsmConnection,
|
||||
changes: Changes,
|
||||
locationControl: UIEventSource<{ zoom: number }>
|
||||
},
|
||||
guiState: { filterViewIsOpened: UIEventSource<boolean> },
|
||||
snapToLayers?: string[],
|
||||
snapToLayersMaxDist?: number
|
||||
}
|
||||
|
||||
export class ImportButtonSpecialViz implements SpecialVisualization {
|
||||
funcName = "import_button"
|
||||
docs = `This button will copy the data from an external dataset into OpenStreetMap. It is only functional in official themes but can be tested in unofficial themes.
|
||||
|
||||
#### Importing a dataset into OpenStreetMap: requirements
|
||||
|
||||
If you want to import a dataset, make sure that:
|
||||
|
||||
1. The dataset to import has a suitable license
|
||||
2. The community has been informed of the import
|
||||
3. All other requirements of the [import guidelines](https://wiki.openstreetmap.org/wiki/Import/Guidelines) have been followed
|
||||
|
||||
There are also some technicalities in your theme to keep in mind:
|
||||
|
||||
1. The new feature will be added and will flow through the program as any other new point as if it came from OSM.
|
||||
This means that there should be a layer which will match the new tags and which will display it.
|
||||
2. The original feature from your geojson layer will gain the tag '_imported=yes'.
|
||||
This should be used to change the appearance or even to hide it (eg by changing the icon size to zero)
|
||||
3. There should be a way for the theme to detect previously imported points, even after reloading.
|
||||
A reference number to the original dataset is an excellent way to do this
|
||||
4. When importing ways, the theme creator is also responsible of avoiding overlapping ways.
|
||||
|
||||
#### Disabled in unofficial themes
|
||||
|
||||
The import button can be tested in an unofficial theme by adding \`test=true\` or \`backend=osm-test\` as [URL-paramter](URL_Parameters.md).
|
||||
The import button will show up then. If in testmode, you can read the changeset-XML directly in the web console.
|
||||
In the case that MapComplete is pointed to the testing grounds, the edit will be made on ${OsmConnection.oauth_configs["osm-test"].url}
|
||||
|
||||
|
||||
#### Specifying which tags to copy or add
|
||||
|
||||
The first argument of the import button takes a \`;\`-seperated list of tags to add.
|
||||
|
||||
${Utils.Special_visualizations_tagsToApplyHelpText}
|
||||
|
||||
|
||||
`
|
||||
args = [
|
||||
{
|
||||
name: "targetLayer",
|
||||
doc: "The id of the layer where this point should end up. This is not very strict, it will simply result in checking that this layer is shown preventing possible duplicate elements"
|
||||
},
|
||||
{
|
||||
name: "tags",
|
||||
doc: "The tags to add onto the new object - see specification above"
|
||||
},
|
||||
{
|
||||
name: "text",
|
||||
doc: "The text to show on the button",
|
||||
defaultValue: "Import this data into OpenStreetMap"
|
||||
},
|
||||
{
|
||||
name: "icon",
|
||||
doc: "A nice icon to show in the button",
|
||||
defaultValue: "./assets/svg/addSmall.svg"
|
||||
},
|
||||
{
|
||||
name: "minzoom",
|
||||
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 max distance",
|
||||
doc: "The maximum distance that this point will move to snap onto a layer (in meters)",
|
||||
defaultValue: "5"
|
||||
}]
|
||||
|
||||
constr(state, tagSource, args, guiState) {
|
||||
if (!state.layoutToUse.official && !(state.featureSwitchIsTesting.data || state.osmConnection._oauth_config.url === OsmConnection.oauth_configs["osm-test"].url)) {
|
||||
return new Combine([new FixedUiElement("The import button is disabled for unofficial themes to prevent accidents.").SetClass("alert"),
|
||||
new FixedUiElement("To test, add <b>test=true</b> or <b>backend=osm-test</b> to the URL. The changeset will be printed in the console. Please open a PR to officialize this theme to actually enable the import button.")])
|
||||
}
|
||||
const newTags = SpecialVisualizations.generateTagsToApply(args[1], tagSource)
|
||||
const id = tagSource.data.id;
|
||||
const feature = state.allElements.ContainingFeatures.get(id)
|
||||
let minZoom = args[4] == "" ? 18 : Number(args[4])
|
||||
if(isNaN(minZoom)){
|
||||
console.warn("Invalid minzoom:", minZoom)
|
||||
minZoom = 18
|
||||
}
|
||||
const message = args[2]
|
||||
const imageUrl = args[3]
|
||||
let img: () => BaseUIElement
|
||||
const targetLayer: FilteredLayer = state.filteredLayers.data.filter(fl => fl.layerDef.id === args[0])[0]
|
||||
|
||||
if (imageUrl !== undefined && imageUrl !== "") {
|
||||
img = () => new Img(imageUrl)
|
||||
} else {
|
||||
img = () => Svg.add_ui()
|
||||
}
|
||||
|
||||
const snapToLayers = args[5]?.split(";").filter(s => s !== "")
|
||||
const snapToLayersMaxDist = Number(args[6] ?? 6)
|
||||
|
||||
if (targetLayer === undefined) {
|
||||
const e = "Target layer not defined: error in import button for theme: " + state.layoutToUse.id + ": layer " + args[0] + " not found"
|
||||
console.error(e)
|
||||
return new FixedUiElement(e).SetClass("alert")
|
||||
}
|
||||
|
||||
return new ImportButton(
|
||||
{
|
||||
state, guiState, image: img,
|
||||
feature, newTags, message, minZoom,
|
||||
originalTags: tagSource,
|
||||
targetLayer,
|
||||
snapToLayers,
|
||||
snapToLayersMaxDist
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default class ImportButton extends Toggle {
|
||||
constructor(imageUrl: string | BaseUIElement,
|
||||
message: string | BaseUIElement,
|
||||
originalTags: UIEventSource<any>,
|
||||
newTags: UIEventSource<Tag[]>,
|
||||
feature: any,
|
||||
minZoom: number,
|
||||
state: {
|
||||
featureSwitchUserbadge: UIEventSource<boolean>;
|
||||
featurePipeline: FeaturePipeline;
|
||||
allElements: ElementStorage;
|
||||
selectedElement: UIEventSource<any>;
|
||||
layoutToUse: LayoutConfig,
|
||||
osmConnection: OsmConnection,
|
||||
changes: Changes,
|
||||
locationControl: UIEventSource<{ zoom: number }>
|
||||
}) {
|
||||
|
||||
constructor(o: ImportButtonState) {
|
||||
const t = Translations.t.general.add;
|
||||
const isImported = originalTags.map(tags => tags._imported === "yes")
|
||||
const isImported = o.originalTags.map(tags => tags._imported === "yes")
|
||||
const appliedTags = new Toggle(
|
||||
new VariableUiElement(
|
||||
newTags.map(tgs => {
|
||||
o.newTags.map(tgs => {
|
||||
const parts = []
|
||||
for (const tag of tgs) {
|
||||
parts.push(tag.key + "=" + tag.value)
|
||||
|
@ -46,63 +182,106 @@ export default class ImportButton extends Toggle {
|
|||
const txt = parts.join(" & ")
|
||||
return t.presetInfo.Subs({tags: txt}).SetClass("subtle")
|
||||
})), undefined,
|
||||
state.osmConnection.userDetails.map(ud => ud.csCount >= Constants.userJourney.tagsVisibleAt)
|
||||
o.state.osmConnection.userDetails.map(ud => ud.csCount >= Constants.userJourney.tagsVisibleAt)
|
||||
)
|
||||
const button = new SubtleButton(imageUrl, message)
|
||||
const button = new SubtleButton(o.image(), o.message)
|
||||
|
||||
minZoom = Math.max(16, minZoom ?? 19)
|
||||
o.minZoom = Math.max(16, o.minZoom ?? 19)
|
||||
|
||||
button.onClick(async () => {
|
||||
if (isImported.data) {
|
||||
return
|
||||
}
|
||||
originalTags.data["_imported"] = "yes"
|
||||
originalTags.ping() // will set isImported as per its definition
|
||||
const newElementAction = ImportButton.createAddActionForFeature(newTags.data, feature, state.layoutToUse.id)
|
||||
await state.changes.applyAction(newElementAction)
|
||||
state.selectedElement.setData(state.allElements.ContainingFeatures.get(
|
||||
newElementAction.newElementId
|
||||
))
|
||||
console.log("Did set selected element to", state.allElements.ContainingFeatures.get(
|
||||
newElementAction.newElementId
|
||||
))
|
||||
|
||||
|
||||
})
|
||||
|
||||
const withLoadingCheck = new Toggle(new Toggle(
|
||||
new Loading(t.stillLoading.Clone()),
|
||||
new Combine([button, appliedTags]).SetClass("flex flex-col"),
|
||||
state.featurePipeline.runningQuery
|
||||
o.state.featurePipeline.runningQuery
|
||||
), t.zoomInFurther.Clone(),
|
||||
state.locationControl.map(l => l.zoom >= minZoom)
|
||||
o.state.locationControl.map(l => l.zoom >= o.minZoom)
|
||||
)
|
||||
const importButton = new Toggle(t.hasBeenImported, withLoadingCheck, isImported)
|
||||
|
||||
|
||||
const importClicked = new UIEventSource(false);
|
||||
const importFlow = new Toggle(
|
||||
new Lazy(() => ImportButton.createConfirmPanel(o, isImported, importClicked)),
|
||||
importButton,
|
||||
importClicked
|
||||
)
|
||||
|
||||
button.onClick(() => {
|
||||
importClicked.setData(true);
|
||||
})
|
||||
|
||||
|
||||
const pleaseLoginButton =
|
||||
new Toggle(t.pleaseLogin.Clone()
|
||||
.onClick(() => state.osmConnection.AttemptLogin())
|
||||
.onClick(() => o.state.osmConnection.AttemptLogin())
|
||||
.SetClass("login-button-friendly"),
|
||||
undefined,
|
||||
state.featureSwitchUserbadge)
|
||||
o.state.featureSwitchUserbadge)
|
||||
|
||||
|
||||
super(new Toggle(importButton,
|
||||
super(new Toggle(importFlow,
|
||||
pleaseLoginButton,
|
||||
state.osmConnection.isLoggedIn
|
||||
o.state.osmConnection.isLoggedIn
|
||||
),
|
||||
t.wrongType,
|
||||
new UIEventSource(ImportButton.canBeImported(feature))
|
||||
new UIEventSource(ImportButton.canBeImported(o.feature))
|
||||
)
|
||||
}
|
||||
|
||||
public static createConfirmPanel(
|
||||
o: ImportButtonState,
|
||||
isImported: UIEventSource<boolean>,
|
||||
importClicked: UIEventSource<boolean>): BaseUIElement {
|
||||
|
||||
async function confirm() {
|
||||
if (isImported.data) {
|
||||
return
|
||||
}
|
||||
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)
|
||||
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,
|
||||
description: o.description,
|
||||
layerToAddTo: o.targetLayer,
|
||||
name: o.message,
|
||||
title: o.message,
|
||||
preciseInput: { snapToLayers: o.snapToLayers,
|
||||
maxSnapDistance: o.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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static canBeImported(feature: any) {
|
||||
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 } {
|
||||
private static createAddActionForFeature(newTags: Tag[], feature: any, theme: string):
|
||||
OsmChangeAction & { newElementId: string } {
|
||||
const geometry = feature.geometry
|
||||
const type = geometry.type
|
||||
if (type === "Point") {
|
||||
|
|
|
@ -12,18 +12,16 @@ import BaseUIElement from "../BaseUIElement";
|
|||
import {VariableUiElement} from "../Base/VariableUIElement";
|
||||
import Toggle from "../Input/Toggle";
|
||||
import UserDetails, {OsmConnection} from "../../Logic/Osm/OsmConnection";
|
||||
import LocationInput from "../Input/LocationInput";
|
||||
import AvailableBaseLayers from "../../Logic/Actors/AvailableBaseLayers";
|
||||
import CreateNewNodeAction from "../../Logic/Osm/Actions/CreateNewNodeAction";
|
||||
import {OsmObject, OsmWay} from "../../Logic/Osm/OsmObject";
|
||||
import PresetConfig from "../../Models/ThemeConfig/PresetConfig";
|
||||
import FilteredLayer from "../../Models/FilteredLayer";
|
||||
import {BBox} from "../../Logic/BBox";
|
||||
import Loc from "../../Models/Loc";
|
||||
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig";
|
||||
import {Changes} from "../../Logic/Osm/Changes";
|
||||
import FeaturePipeline from "../../Logic/FeatureSource/FeaturePipeline";
|
||||
import {ElementStorage} from "../../Logic/ElementStorage";
|
||||
import ConfirmLocationOfPoint from "../NewPoint/ConfirmLocationOfPoint";
|
||||
|
||||
/*
|
||||
* The SimpleAddUI is a single panel, which can have multiple states:
|
||||
|
@ -33,8 +31,7 @@ import {ElementStorage} from "../../Logic/ElementStorage";
|
|||
* - A 'read your unread messages before adding a point'
|
||||
*/
|
||||
|
||||
/*private*/
|
||||
interface PresetInfo extends PresetConfig {
|
||||
export interface PresetInfo extends PresetConfig {
|
||||
name: string | BaseUIElement,
|
||||
icon: () => BaseUIElement,
|
||||
layerToAddTo: FilteredLayer
|
||||
|
@ -91,20 +88,29 @@ export default class SimpleAddUI extends Toggle {
|
|||
if (preset === undefined) {
|
||||
return presetsOverview
|
||||
}
|
||||
return SimpleAddUI.CreateConfirmButton(state, filterViewIsOpened, preset,
|
||||
(tags, location, snapOntoWayId?: string) => {
|
||||
if (snapOntoWayId === undefined) {
|
||||
createNewPoint(tags, location, undefined)
|
||||
} else {
|
||||
OsmObject.DownloadObject(snapOntoWayId).addCallbackAndRunD(way => {
|
||||
createNewPoint(tags, location, <OsmWay>way)
|
||||
return true;
|
||||
})
|
||||
}
|
||||
},
|
||||
() => {
|
||||
selectedPreset.setData(undefined)
|
||||
})
|
||||
|
||||
|
||||
function confirm(tags, location, snapOntoWayId?: string) {
|
||||
if (snapOntoWayId === undefined) {
|
||||
createNewPoint(tags, location, undefined)
|
||||
} else {
|
||||
OsmObject.DownloadObject(snapOntoWayId).addCallbackAndRunD(way => {
|
||||
createNewPoint(tags, location, <OsmWay>way)
|
||||
return true;
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function cancel() {
|
||||
selectedPreset.setData(undefined)
|
||||
}
|
||||
|
||||
const message =Translations.t.general.add.addNew.Subs({category: preset.name});
|
||||
return new ConfirmLocationOfPoint(state, filterViewIsOpened, preset,
|
||||
message,
|
||||
state.LastClickLocation.data,
|
||||
confirm,
|
||||
cancel)
|
||||
}
|
||||
))
|
||||
|
||||
|
@ -134,170 +140,7 @@ export default class SimpleAddUI extends Toggle {
|
|||
}
|
||||
|
||||
|
||||
private static CreateConfirmButton(
|
||||
state: {
|
||||
LastClickLocation: UIEventSource<{ lat: number, lon: number }>,
|
||||
osmConnection: OsmConnection,
|
||||
featurePipeline: FeaturePipeline
|
||||
},
|
||||
filterViewIsOpened: UIEventSource<boolean>,
|
||||
preset: PresetInfo,
|
||||
confirm: (tags: any[], location: { lat: number, lon: number }, snapOntoWayId: string) => void,
|
||||
cancel: () => void): BaseUIElement {
|
||||
|
||||
let location = state.LastClickLocation;
|
||||
let preciseInput: LocationInput = undefined
|
||||
if (preset.preciseInput !== undefined) {
|
||||
// We uncouple the event source
|
||||
const locationSrc = new UIEventSource({
|
||||
lat: location.data.lat,
|
||||
lon: location.data.lon,
|
||||
zoom: 19
|
||||
});
|
||||
|
||||
let backgroundLayer = undefined;
|
||||
if (preset.preciseInput.preferredBackground) {
|
||||
backgroundLayer = AvailableBaseLayers.SelectBestLayerAccordingTo(locationSrc, new UIEventSource<string | string[]>(preset.preciseInput.preferredBackground))
|
||||
}
|
||||
|
||||
let snapToFeatures: UIEventSource<{ feature: any }[]> = undefined
|
||||
let mapBounds: UIEventSource<BBox> = undefined
|
||||
if (preset.preciseInput.snapToLayers) {
|
||||
snapToFeatures = new UIEventSource<{ feature: any }[]>([])
|
||||
mapBounds = new UIEventSource<BBox>(undefined)
|
||||
}
|
||||
|
||||
|
||||
const tags = TagUtils.KVtoProperties(preset.tags ?? []);
|
||||
preciseInput = new LocationInput({
|
||||
mapBackground: backgroundLayer,
|
||||
centerLocation: locationSrc,
|
||||
snapTo: snapToFeatures,
|
||||
snappedPointTags: tags,
|
||||
maxSnapDistance: preset.preciseInput.maxSnapDistance,
|
||||
bounds: mapBounds
|
||||
})
|
||||
preciseInput.installBounds(0.15, true)
|
||||
preciseInput.SetClass("h-32 rounded-xl overflow-hidden border border-gray").SetStyle("height: 12rem;")
|
||||
|
||||
|
||||
if (preset.preciseInput.snapToLayers) {
|
||||
// We have to snap to certain layers.
|
||||
// Lets fetch them
|
||||
|
||||
let loadedBbox: BBox = undefined
|
||||
mapBounds?.addCallbackAndRunD(bbox => {
|
||||
if (loadedBbox !== undefined && bbox.isContainedIn(loadedBbox)) {
|
||||
// All is already there
|
||||
// return;
|
||||
}
|
||||
|
||||
bbox = bbox.pad(2);
|
||||
loadedBbox = bbox;
|
||||
const allFeatures: { feature: any }[] = []
|
||||
preset.preciseInput.snapToLayers.forEach(layerId => {
|
||||
state.featurePipeline.GetFeaturesWithin(layerId, bbox).forEach(feats => allFeatures.push(...feats.map(f => ({feature: f}))))
|
||||
})
|
||||
snapToFeatures.setData(allFeatures)
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
let confirmButton: BaseUIElement = new SubtleButton(preset.icon(),
|
||||
new Combine([
|
||||
Translations.t.general.add.addNew.Subs({category: preset.name}),
|
||||
Translations.t.general.add.warnVisibleForEveryone.Clone().SetClass("alert")
|
||||
]).SetClass("flex flex-col")
|
||||
).SetClass("font-bold break-words")
|
||||
.onClick(() => {
|
||||
confirm(preset.tags, (preciseInput?.GetValue() ?? location).data, preciseInput?.snappedOnto?.data?.properties?.id);
|
||||
});
|
||||
|
||||
if (preciseInput !== undefined) {
|
||||
confirmButton = new Combine([preciseInput, confirmButton])
|
||||
}
|
||||
|
||||
const openLayerControl =
|
||||
new SubtleButton(
|
||||
Svg.layers_ui(),
|
||||
new Combine([
|
||||
Translations.t.general.add.layerNotEnabled
|
||||
.Subs({layer: preset.layerToAddTo.layerDef.name})
|
||||
.SetClass("alert"),
|
||||
Translations.t.general.add.openLayerControl
|
||||
])
|
||||
)
|
||||
.onClick(() => filterViewIsOpened.setData(true))
|
||||
|
||||
|
||||
const openLayerOrConfirm = new Toggle(
|
||||
confirmButton,
|
||||
openLayerControl,
|
||||
preset.layerToAddTo.isDisplayed
|
||||
)
|
||||
|
||||
const disableFilter = new SubtleButton(
|
||||
new Combine([
|
||||
Svg.filter_ui().SetClass("absolute w-full"),
|
||||
Svg.cross_bottom_right_svg().SetClass("absolute red-svg")
|
||||
]).SetClass("relative"),
|
||||
new Combine(
|
||||
[
|
||||
Translations.t.general.add.disableFiltersExplanation.Clone(),
|
||||
Translations.t.general.add.disableFilters.Clone().SetClass("text-xl")
|
||||
]
|
||||
).SetClass("flex flex-col")
|
||||
).onClick(() => {
|
||||
preset.layerToAddTo.appliedFilters.setData([])
|
||||
cancel()
|
||||
})
|
||||
|
||||
const disableFiltersOrConfirm = new Toggle(
|
||||
openLayerOrConfirm,
|
||||
disableFilter,
|
||||
preset.layerToAddTo.appliedFilters.map(filters => {
|
||||
if (filters === undefined || filters.length === 0) {
|
||||
return true;
|
||||
}
|
||||
for (const filter of filters) {
|
||||
if (filter.selected === 0 && filter.filter.options.length === 1) {
|
||||
return false;
|
||||
}
|
||||
if (filter.selected !== undefined) {
|
||||
const tags = filter.filter.options[filter.selected].osmTags
|
||||
if (tags !== undefined && tags["and"]?.length !== 0) {
|
||||
// This actually doesn't filter anything at all
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
|
||||
})
|
||||
)
|
||||
|
||||
|
||||
const tagInfo = SimpleAddUI.CreateTagInfoFor(preset, state.osmConnection);
|
||||
|
||||
const cancelButton = new SubtleButton(Svg.close_ui(),
|
||||
Translations.t.general.cancel
|
||||
).onClick(cancel)
|
||||
|
||||
return new Combine([
|
||||
state.osmConnection.userDetails.data.dryRun ?
|
||||
Translations.t.general.testing.Clone().SetClass("alert") : undefined,
|
||||
disableFiltersOrConfirm,
|
||||
cancelButton,
|
||||
preset.description,
|
||||
tagInfo
|
||||
|
||||
]).SetClass("flex flex-col")
|
||||
|
||||
}
|
||||
|
||||
private static CreateTagInfoFor(preset: PresetInfo, osmConnection: OsmConnection, optionallyLinkToWiki = true) {
|
||||
public static CreateTagInfoFor(preset: PresetInfo, osmConnection: OsmConnection, optionallyLinkToWiki = true) {
|
||||
const csCount = osmConnection.userDetails.data.csCount;
|
||||
return new Toggle(
|
||||
Translations.t.general.add.presetInfo.Subs({
|
||||
|
@ -329,7 +172,7 @@ export default class SimpleAddUI extends Toggle {
|
|||
|
||||
private static CreatePresetSelectButton(preset: PresetInfo, osmConnection: OsmConnection) {
|
||||
|
||||
const tagInfo = SimpleAddUI.CreateTagInfoFor(preset, osmConnection ,false);
|
||||
const tagInfo = SimpleAddUI.CreateTagInfoFor(preset, osmConnection, false);
|
||||
return new SubtleButton(
|
||||
preset.icon(),
|
||||
new Combine([
|
||||
|
@ -368,7 +211,7 @@ export default class SimpleAddUI extends Toggle {
|
|||
for (const preset of presets) {
|
||||
|
||||
const tags = TagUtils.KVtoProperties(preset.tags ?? []);
|
||||
let icon: () => BaseUIElement = () => layer.layerDef.mapRendering[0]. GenerateLeafletStyle(new UIEventSource<any>(tags), false).html
|
||||
let icon: () => BaseUIElement = () => layer.layerDef.mapRendering[0].GenerateLeafletStyle(new UIEventSource<any>(tags), false).html
|
||||
.SetClass("w-12 h-12 block relative");
|
||||
const presetInfo: PresetInfo = {
|
||||
tags: preset.tags,
|
||||
|
|
|
@ -32,6 +32,7 @@ export class DefaultGuiState {
|
|||
public readonly copyrightViewIsOpened: UIEventSource<boolean>;
|
||||
public readonly welcomeMessageOpenedTab: UIEventSource<number>
|
||||
public readonly allFullScreenStates: UIEventSource<boolean>[] = []
|
||||
static state: DefaultGuiState;
|
||||
|
||||
constructor() {
|
||||
|
||||
|
|
|
@ -96,6 +96,8 @@ export default class LocationInput extends InputElement<Loc> implements MinimapO
|
|||
let min = undefined;
|
||||
let matchedWay = undefined;
|
||||
for (const feature of self._snapTo.data ?? []) {
|
||||
try{
|
||||
|
||||
const nearestPointOnLine = GeoOperations.nearestPoint(feature.feature, [loc.lon, loc.lat])
|
||||
if (min === undefined) {
|
||||
min = nearestPointOnLine
|
||||
|
@ -108,6 +110,9 @@ export default class LocationInput extends InputElement<Loc> implements MinimapO
|
|||
matchedWay = feature.feature;
|
||||
|
||||
}
|
||||
}catch(e){
|
||||
console.log("Snapping to a nearest point failed for ", feature.feature,"due to ", e)
|
||||
}
|
||||
}
|
||||
|
||||
if (min === undefined || min.properties.dist * 1000 > self._maxSnapDistance) {
|
||||
|
|
184
UI/NewPoint/ConfirmLocationOfPoint.ts
Normal file
184
UI/NewPoint/ConfirmLocationOfPoint.ts
Normal file
|
@ -0,0 +1,184 @@
|
|||
import {UIEventSource} from "../../Logic/UIEventSource";
|
||||
import {OsmConnection} from "../../Logic/Osm/OsmConnection";
|
||||
import FeaturePipeline from "../../Logic/FeatureSource/FeaturePipeline";
|
||||
import BaseUIElement from "../BaseUIElement";
|
||||
import LocationInput from "../Input/LocationInput";
|
||||
import AvailableBaseLayers from "../../Logic/Actors/AvailableBaseLayers";
|
||||
import {BBox} from "../../Logic/BBox";
|
||||
import {TagUtils} from "../../Logic/Tags/TagUtils";
|
||||
import {SubtleButton} from "../Base/SubtleButton";
|
||||
import Combine from "../Base/Combine";
|
||||
import Translations from "../i18n/Translations";
|
||||
import Svg from "../../Svg";
|
||||
import Toggle from "../Input/Toggle";
|
||||
import SimpleAddUI, {PresetInfo} from "../BigComponents/SimpleAddUI";
|
||||
|
||||
export default class ConfirmLocationOfPoint extends Combine {
|
||||
|
||||
|
||||
constructor(
|
||||
state: {
|
||||
osmConnection: OsmConnection,
|
||||
featurePipeline: FeaturePipeline
|
||||
},
|
||||
filterViewIsOpened: UIEventSource<boolean>,
|
||||
preset: PresetInfo,
|
||||
confirmText: BaseUIElement,
|
||||
loc: { lon: number, lat: number },
|
||||
confirm: (tags: any[], location: { lat: number, lon: number }, snapOntoWayId: string) => void,
|
||||
cancel: () => void,
|
||||
) {
|
||||
|
||||
let preciseInput: LocationInput = undefined
|
||||
if (preset.preciseInput !== undefined) {
|
||||
// We uncouple the event source
|
||||
const zloc = {...loc, zoom: 19}
|
||||
const locationSrc = new UIEventSource(zloc);
|
||||
|
||||
let backgroundLayer = undefined;
|
||||
if (preset.preciseInput.preferredBackground) {
|
||||
backgroundLayer = AvailableBaseLayers.SelectBestLayerAccordingTo(locationSrc, new UIEventSource<string | string[]>(preset.preciseInput.preferredBackground))
|
||||
}
|
||||
|
||||
let snapToFeatures: UIEventSource<{ feature: any }[]> = undefined
|
||||
let mapBounds: UIEventSource<BBox> = undefined
|
||||
if (preset.preciseInput.snapToLayers && preset.preciseInput.snapToLayers.length > 0) {
|
||||
snapToFeatures = new UIEventSource<{ feature: any }[]>([])
|
||||
mapBounds = new UIEventSource<BBox>(undefined)
|
||||
}
|
||||
|
||||
|
||||
const tags = TagUtils.KVtoProperties(preset.tags ?? []);
|
||||
preciseInput = new LocationInput({
|
||||
mapBackground: backgroundLayer,
|
||||
centerLocation: locationSrc,
|
||||
snapTo: snapToFeatures,
|
||||
snappedPointTags: tags,
|
||||
maxSnapDistance: preset.preciseInput.maxSnapDistance,
|
||||
bounds: mapBounds
|
||||
})
|
||||
preciseInput.installBounds(0.15, true)
|
||||
preciseInput.SetClass("h-32 rounded-xl overflow-hidden border border-gray").SetStyle("height: 12rem;")
|
||||
|
||||
|
||||
if (preset.preciseInput.snapToLayers && preset.preciseInput.snapToLayers.length > 0) {
|
||||
// We have to snap to certain layers.
|
||||
// Lets fetch them
|
||||
|
||||
let loadedBbox: BBox = undefined
|
||||
mapBounds?.addCallbackAndRunD(bbox => {
|
||||
if (loadedBbox !== undefined && bbox.isContainedIn(loadedBbox)) {
|
||||
// All is already there
|
||||
// return;
|
||||
}
|
||||
|
||||
bbox = bbox.pad(2);
|
||||
loadedBbox = bbox;
|
||||
const allFeatures: { feature: any }[] = []
|
||||
preset.preciseInput.snapToLayers.forEach(layerId => {
|
||||
console.log("Snapping to", layerId)
|
||||
state.featurePipeline.GetFeaturesWithin(layerId, bbox)?.forEach(feats => allFeatures.push(...feats.map(f => ({feature: f}))))
|
||||
})
|
||||
console.log("Snapping to", allFeatures)
|
||||
snapToFeatures.setData(allFeatures)
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
let confirmButton: BaseUIElement = new SubtleButton(preset.icon(),
|
||||
new Combine([
|
||||
confirmText,
|
||||
Translations.t.general.add.warnVisibleForEveryone.Clone().SetClass("alert")
|
||||
]).SetClass("flex flex-col")
|
||||
).SetClass("font-bold break-words")
|
||||
.onClick(() => {
|
||||
confirm(preset.tags, (preciseInput?.GetValue()?.data ?? loc), preciseInput?.snappedOnto?.data?.properties?.id);
|
||||
});
|
||||
|
||||
if (preciseInput !== undefined) {
|
||||
confirmButton = new Combine([preciseInput, confirmButton])
|
||||
}
|
||||
|
||||
const openLayerControl =
|
||||
new SubtleButton(
|
||||
Svg.layers_ui(),
|
||||
new Combine([
|
||||
Translations.t.general.add.layerNotEnabled
|
||||
.Subs({layer: preset.layerToAddTo.layerDef.name})
|
||||
.SetClass("alert"),
|
||||
Translations.t.general.add.openLayerControl
|
||||
])
|
||||
)
|
||||
.onClick(() => filterViewIsOpened.setData(true))
|
||||
|
||||
|
||||
const openLayerOrConfirm = new Toggle(
|
||||
confirmButton,
|
||||
openLayerControl,
|
||||
preset.layerToAddTo.isDisplayed
|
||||
)
|
||||
|
||||
const disableFilter = new SubtleButton(
|
||||
new Combine([
|
||||
Svg.filter_ui().SetClass("absolute w-full"),
|
||||
Svg.cross_bottom_right_svg().SetClass("absolute red-svg")
|
||||
]).SetClass("relative"),
|
||||
new Combine(
|
||||
[
|
||||
Translations.t.general.add.disableFiltersExplanation.Clone(),
|
||||
Translations.t.general.add.disableFilters.Clone().SetClass("text-xl")
|
||||
]
|
||||
).SetClass("flex flex-col")
|
||||
).onClick(() => {
|
||||
preset.layerToAddTo.appliedFilters.setData([])
|
||||
cancel()
|
||||
})
|
||||
|
||||
const disableFiltersOrConfirm = new Toggle(
|
||||
openLayerOrConfirm,
|
||||
disableFilter,
|
||||
preset.layerToAddTo.appliedFilters.map(filters => {
|
||||
if (filters === undefined || filters.length === 0) {
|
||||
return true;
|
||||
}
|
||||
for (const filter of filters) {
|
||||
if (filter.selected === 0 && filter.filter.options.length === 1) {
|
||||
return false;
|
||||
}
|
||||
if (filter.selected !== undefined) {
|
||||
const tags = filter.filter.options[filter.selected].osmTags
|
||||
if (tags !== undefined && tags["and"]?.length !== 0) {
|
||||
// This actually doesn't filter anything at all
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
|
||||
})
|
||||
)
|
||||
|
||||
|
||||
const tagInfo = SimpleAddUI.CreateTagInfoFor(preset, state.osmConnection);
|
||||
|
||||
const cancelButton = new SubtleButton(Svg.close_ui(),
|
||||
Translations.t.general.cancel
|
||||
).onClick(cancel)
|
||||
|
||||
super([
|
||||
state.osmConnection.userDetails.data.dryRun ?
|
||||
Translations.t.general.testing.Clone().SetClass("alert") : undefined,
|
||||
disableFiltersOrConfirm,
|
||||
cancelButton,
|
||||
preset.description,
|
||||
tagInfo
|
||||
|
||||
])
|
||||
|
||||
this.SetClass("flex flex-col")
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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 from "./BigComponents/ImportButton";
|
||||
import ImportButton, {ImportButtonSpecialViz} from "./BigComponents/ImportButton";
|
||||
import {Tag} from "../Logic/Tags/Tag";
|
||||
import StaticFeatureSource from "../Logic/FeatureSource/Sources/StaticFeatureSource";
|
||||
import ShowDataMultiLayer from "./ShowDataLayer/ShowDataMultiLayer";
|
||||
|
@ -38,10 +38,13 @@ 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";
|
||||
|
||||
export interface SpecialVisualization {
|
||||
funcName: string,
|
||||
constr: ((state: State, tagSource: UIEventSource<any>, argument: string[]) => BaseUIElement),
|
||||
constr: ((state: State, tagSource: UIEventSource<any>, argument: string[], guistate: DefaultGuiState) => BaseUIElement),
|
||||
docs: string,
|
||||
example?: string,
|
||||
args: { name: string, defaultValue?: string, doc: string }[]
|
||||
|
@ -49,17 +52,7 @@ export interface SpecialVisualization {
|
|||
|
||||
export default class SpecialVisualizations {
|
||||
|
||||
private static tagsToApplyHelpText = `These can either be a tag to add, such as \`amenity=fast_food\` or can use a substitution, e.g. \`addr:housenumber=$number\`.
|
||||
This new point will then have the tags \`amenity=fast_food\` and \`addr:housenumber\` with the value that was saved in \`number\` in the original feature.
|
||||
|
||||
If a value to substitute is undefined, empty string will be used instead.
|
||||
|
||||
This supports multiple values, e.g. \`ref=$source:geometry:type/$source:geometry:ref\`
|
||||
|
||||
Remark that the syntax is slightly different then expected; it uses '$' to note a value to copy, followed by a name (matched with \`[a-zA-Z0-9_:]*\`). Sadly, delimiting with \`{}\` as these already mark the boundaries of the special rendering...
|
||||
|
||||
Note that these values can be prepare with javascript in the theme by using a [calculatedTag](calculatedTags.md#calculating-tags-with-javascript)
|
||||
`
|
||||
static tagsToApplyHelpText = Utils.Special_visualizations_tagsToApplyHelpText
|
||||
public static specialVisualizations: SpecialVisualization[] =
|
||||
[
|
||||
{
|
||||
|
@ -490,79 +483,7 @@ Note that these values can be prepare with javascript in the theme by using a [c
|
|||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
funcName: "import_button",
|
||||
args: [
|
||||
{
|
||||
name: "tags",
|
||||
doc: "The tags to add onto the new object - see specification above"
|
||||
},
|
||||
{
|
||||
name: "text",
|
||||
doc: "The text to show on the button",
|
||||
defaultValue: "Import this data into OpenStreetMap"
|
||||
},
|
||||
{
|
||||
name: "icon",
|
||||
doc: "A nice icon to show in the button",
|
||||
defaultValue: "./assets/svg/addSmall.svg"
|
||||
},
|
||||
{
|
||||
name: "minzoom",
|
||||
doc: "How far the contributor must zoom in before being able to import the point",
|
||||
defaultValue: "18"
|
||||
}],
|
||||
docs: `This button will copy the data from an external dataset into OpenStreetMap. It is only functional in official themes but can be tested in unofficial themes.
|
||||
|
||||
#### Importing a dataset into OpenStreetMap: requirements
|
||||
|
||||
If you want to import a dataset, make sure that:
|
||||
|
||||
1. The dataset to import has a suitable license
|
||||
2. The community has been informed of the import
|
||||
3. All other requirements of the [import guidelines](https://wiki.openstreetmap.org/wiki/Import/Guidelines) have been followed
|
||||
|
||||
There are also some technicalities in your theme to keep in mind:
|
||||
|
||||
1. The new feature will be added and will flow through the program as any other new point as if it came from OSM.
|
||||
This means that there should be a layer which will match the new tags and which will display it.
|
||||
2. The original feature from your geojson layer will gain the tag '_imported=yes'.
|
||||
This should be used to change the appearance or even to hide it (eg by changing the icon size to zero)
|
||||
3. There should be a way for the theme to detect previously imported points, even after reloading.
|
||||
A reference number to the original dataset is an excellent way to do this
|
||||
4. When importing ways, the theme creator is also responsible of avoiding overlapping ways.
|
||||
|
||||
#### Disabled in unofficial themes
|
||||
|
||||
The import button can be tested in an unofficial theme by adding \`test=true\` or \`backend=osm-test\` as [URL-paramter](URL_Parameters.md).
|
||||
The import button will show up then. If in testmode, you can read the changeset-XML directly in the web console.
|
||||
In the case that MapComplete is pointed to the testing grounds, the edit will be made on ${OsmConnection.oauth_configs["osm-test"].url}
|
||||
|
||||
|
||||
#### Specifying which tags to copy or add
|
||||
|
||||
The first argument of the import button takes a \`;\`-seperated list of tags to add.
|
||||
|
||||
${SpecialVisualizations.tagsToApplyHelpText}
|
||||
|
||||
`,
|
||||
constr: (state, tagSource, args) => {
|
||||
if (!state.layoutToUse.official && !(state.featureSwitchIsTesting.data || state.osmConnection._oauth_config.url === OsmConnection.oauth_configs["osm-test"].url)) {
|
||||
return new Combine([new FixedUiElement("The import button is disabled for unofficial themes to prevent accidents.").SetClass("alert"),
|
||||
new FixedUiElement("To test, add <b>test=true</b> or <b>backend=osm-test</b> to the URL. The changeset will be printed in the console. Please open a PR to officialize this theme to actually enable the import button.")])
|
||||
}
|
||||
const rewrittenTags = SpecialVisualizations.generateTagsToApply(args[0], tagSource)
|
||||
const id = tagSource.data.id;
|
||||
const feature = state.allElements.ContainingFeatures.get(id)
|
||||
const minzoom = Number(args[3])
|
||||
const message = args[1]
|
||||
const image = args[2]
|
||||
|
||||
return new ImportButton(
|
||||
image, message, tagSource, rewrittenTags, feature, minzoom, state
|
||||
)
|
||||
}
|
||||
},
|
||||
new ImportButtonSpecialViz(),
|
||||
{
|
||||
funcName: "multi_apply",
|
||||
docs: "A button to apply the tagging of this object onto a list of other features. This is an advanced feature for which you'll need calculatedTags",
|
||||
|
@ -687,7 +608,7 @@ ${SpecialVisualizations.tagsToApplyHelpText}
|
|||
}
|
||||
]
|
||||
|
||||
private static generateTagsToApply(spec: string, tagSource: UIEventSource<any>): UIEventSource<Tag[]> {
|
||||
static generateTagsToApply(spec: string, tagSource: UIEventSource<any>): UIEventSource<Tag[]> {
|
||||
|
||||
const tgsSpec = spec.split(";").map(spec => {
|
||||
const kv = spec.split("=").map(s => s.trim());
|
||||
|
|
|
@ -8,6 +8,7 @@ import {Utils} from "../Utils";
|
|||
import {VariableUiElement} from "./Base/VariableUIElement";
|
||||
import Combine from "./Base/Combine";
|
||||
import BaseUIElement from "./BaseUIElement";
|
||||
import {DefaultGuiState} from "./DefaultGUI";
|
||||
|
||||
export class SubstitutedTranslation extends VariableUiElement {
|
||||
|
||||
|
@ -49,7 +50,7 @@ export class SubstitutedTranslation extends VariableUiElement {
|
|||
}
|
||||
const viz = proto.special;
|
||||
try {
|
||||
return viz.func.constr(State.state, tagsSource, proto.special.args).SetStyle(proto.special.style);
|
||||
return viz.func.constr(State.state, tagsSource, proto.special.args, DefaultGuiState.state).SetStyle(proto.special.style);
|
||||
} catch (e) {
|
||||
console.error("SPECIALRENDERING FAILED for", tagsSource.data?.id, e)
|
||||
return new FixedUiElement(`Could not generate special rendering for ${viz.func}(${viz.args.join(", ")}) ${e}`).SetStyle("alert")
|
||||
|
|
12
Utils.ts
12
Utils.ts
|
@ -15,6 +15,18 @@ export class Utils {
|
|||
private static injectedDownloads = {}
|
||||
private static _download_cache = new Map<string, { promise: Promise<any>, timestamp: number }>()
|
||||
|
||||
public static Special_visualizations_tagsToApplyHelpText = `These can either be a tag to add, such as \`amenity=fast_food\` or can use a substitution, e.g. \`addr:housenumber=$number\`.
|
||||
This new point will then have the tags \`amenity=fast_food\` and \`addr:housenumber\` with the value that was saved in \`number\` in the original feature.
|
||||
|
||||
If a value to substitute is undefined, empty string will be used instead.
|
||||
|
||||
This supports multiple values, e.g. \`ref=$source:geometry:type/$source:geometry:ref\`
|
||||
|
||||
Remark that the syntax is slightly different then expected; it uses '$' to note a value to copy, followed by a name (matched with \`[a-zA-Z0-9_:]*\`). Sadly, delimiting with \`{}\` as these already mark the boundaries of the special rendering...
|
||||
|
||||
Note that these values can be prepare with javascript in the theme by using a [calculatedTag](calculatedTags.md#calculating-tags-with-javascript)
|
||||
`
|
||||
|
||||
static EncodeXmlValue(str) {
|
||||
if (typeof str !== "string") {
|
||||
str = "" + str
|
||||
|
|
12
assets/layers/type_node/type_node.json
Normal file
12
assets/layers/type_node/type_node.json
Normal file
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"id": "type_node",
|
||||
"description": "This is a special meta_layer which exports _every_ point in OSM. This only works if zoomed below the point that the full tile is loaded (and not loaded via Overpass). Note that this point will also contain a property `parent_ways` which contains all the ways this node is part of as a list",
|
||||
"minzoom": 18,
|
||||
"source": {
|
||||
"osmTags": "id~node/.*"
|
||||
},
|
||||
"mapRendering": [],
|
||||
"name": "All OSM Nodes",
|
||||
"title": "OSM node {id}",
|
||||
"tagRendering": [ ]
|
||||
}
|
|
@ -28,7 +28,20 @@
|
|||
"overrideAll": {
|
||||
"minzoom": 18
|
||||
},
|
||||
"trackAllNodes": true,
|
||||
"layers": [
|
||||
{
|
||||
"builtin": "type_node",
|
||||
"isShown": {
|
||||
"render": "no"
|
||||
},
|
||||
"override": {
|
||||
"calculatedTags": [
|
||||
"_is_part_of_building=feat.get('parent_ways')?.some(p => p.building !== undefined && p.building !== '') ?? false",
|
||||
"_is_part_of_landuse=feat.get('parent_ways')?.some(p => (p.landuse !== undefined && p.landuse !== '') || (p.natural !== undefined && p.natural !== '')) ?? false"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "OSM-buildings",
|
||||
"name": "All OSM-buildings",
|
||||
|
@ -413,7 +426,7 @@
|
|||
"all_tags",
|
||||
{
|
||||
"id": "import-button",
|
||||
"render": "{import_button(addr:street=$STRAATNM; addr:housenumber=$HUISNR)}"
|
||||
"render": "{import_button(OSM-buildings, addr:street=$STRAATNM; addr:housenumber=$HUISNR,Import this address,,,OSM-buildings,5)}"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -657,7 +670,7 @@
|
|||
},
|
||||
{
|
||||
"id": "Import-button",
|
||||
"render": "{import_button(building=$building; source:geometry:date=$_grb_date; source:geometry:ref=$_grb_ref, Upload this building to OpenStreetMap)}",
|
||||
"render": "{import_button(OSM-buildings,building=$building; source:geometry:date=$_grb_date; source:geometry:ref=$_grb_ref, Upload this building to OpenStreetMap)}",
|
||||
"mappings": [
|
||||
{
|
||||
"if": "_overlaps_with!=null",
|
||||
|
|
|
@ -93,7 +93,7 @@
|
|||
},
|
||||
{
|
||||
"id": "uk_addresses_import_button",
|
||||
"render": "{import_button(ref:inspireid=$inspireid, Add this address, ./assets/themes/uk_addresses/housenumber_add.svg)}"
|
||||
"render": "{import_button(addresses, ref:inspireid=$inspireid, Add this address, ./assets/themes/uk_addresses/housenumber_add.svg)}"
|
||||
}
|
||||
],
|
||||
"calculatedTags": [
|
||||
|
|
2
index.ts
2
index.ts
|
@ -64,9 +64,11 @@ class Init {
|
|||
|
||||
const guiState = new DefaultGuiState()
|
||||
State.state = new State(layoutToUse);
|
||||
DefaultGuiState.state = guiState;
|
||||
// This 'leaks' the global state via the window object, useful for debugging
|
||||
// @ts-ignore
|
||||
window.mapcomplete_state = State.state;
|
||||
|
||||
new DefaultGUI(State.state, guiState)
|
||||
|
||||
if (encoded !== undefined && encoded.length > 10) {
|
||||
|
|
|
@ -12,8 +12,8 @@ import {Utils} from "../Utils";
|
|||
// It spits out an overview of those to be used to load them
|
||||
|
||||
interface LayersAndThemes {
|
||||
themes: any[],
|
||||
layers: { parsed: any, path: string }[]
|
||||
themes: LayoutConfigJson[],
|
||||
layers: { parsed: LayerConfigJson, path: string }[]
|
||||
}
|
||||
|
||||
|
||||
|
@ -35,7 +35,6 @@ class LayerOverviewUtils {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
writeFiles(lt: LayersAndThemes) {
|
||||
writeFileSync("./assets/generated/known_layers_and_themes.json", JSON.stringify({
|
||||
"layers": lt.layers.map(l => l.parsed),
|
||||
|
@ -43,7 +42,6 @@ class LayerOverviewUtils {
|
|||
}))
|
||||
}
|
||||
|
||||
|
||||
validateLayer(layerJson: LayerConfigJson, path: string, knownPaths: Set<string>, context?: string): string[] {
|
||||
let errorCount = [];
|
||||
if (layerJson["overpassTags"] !== undefined) {
|
||||
|
@ -109,6 +107,8 @@ class LayerOverviewUtils {
|
|||
}
|
||||
|
||||
let themeErrorCount = []
|
||||
// used only for the reports
|
||||
let themeConfigs: LayoutConfig[] = []
|
||||
for (const themeInfo of themeFiles) {
|
||||
const themeFile = themeInfo.parsed
|
||||
const themePath = themeInfo.path
|
||||
|
@ -119,7 +119,7 @@ class LayerOverviewUtils {
|
|||
themeErrorCount.push("The theme " + themeFile.id + " has units defined - these should be defined on the layer instead. (Hint: use overrideAll: { '+units': ... }) ")
|
||||
}
|
||||
if (themeFile["roamingRenderings"] !== undefined) {
|
||||
themeErrorCount.push("Theme " + themeFile.id + " contains an old 'roamingRenderings'. Use an 'overrideAll' instead")
|
||||
themeErrorCount.push("Theme " + themeFile.id + " contains an old 'roamingRenderings'. Use an 'overrideAll' instead")
|
||||
}
|
||||
for (const layer of themeFile.layers) {
|
||||
if (typeof layer === "string") {
|
||||
|
@ -144,17 +144,17 @@ class LayerOverviewUtils {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const referencedLayers = Utils.NoNull([].concat(...themeFile.layers.map(layer => {
|
||||
if(typeof layer === "string"){
|
||||
if (typeof layer === "string") {
|
||||
return layer
|
||||
}
|
||||
if(layer["builtin"] !== undefined){
|
||||
if (layer["builtin"] !== undefined) {
|
||||
return layer["builtin"]
|
||||
}
|
||||
return undefined
|
||||
}).map(layerName => {
|
||||
if(typeof layerName === "string"){
|
||||
if (typeof layerName === "string") {
|
||||
return [layerName]
|
||||
}
|
||||
return layerName
|
||||
|
@ -176,9 +176,9 @@ class LayerOverviewUtils {
|
|||
}
|
||||
const neededLanguages = themeFile["mustHaveLanguage"]
|
||||
if (neededLanguages !== undefined) {
|
||||
console.log("Checking language requerements for ", theme.id, "as it must have", neededLanguages.join(", "))
|
||||
const allTranslations = [].concat(Translation.ExtractAllTranslationsFrom(theme, theme.id),
|
||||
...referencedLayers.map(layerId => Translation.ExtractAllTranslationsFrom(knownLayerIds.get(layerId), theme.id+"->"+layerId)))
|
||||
console.log("Checking language requirements for ", theme.id, "as it must have", neededLanguages.join(", "))
|
||||
const allTranslations = [].concat(Translation.ExtractAllTranslationsFrom(theme, theme.id),
|
||||
...referencedLayers.map(layerId => Translation.ExtractAllTranslationsFrom(knownLayerIds.get(layerId), theme.id + "->" + layerId)))
|
||||
for (const neededLanguage of neededLanguages) {
|
||||
allTranslations
|
||||
.filter(t => t.tr.translations[neededLanguage] === undefined && t.tr.translations["*"] === undefined)
|
||||
|
@ -189,7 +189,7 @@ class LayerOverviewUtils {
|
|||
|
||||
|
||||
}
|
||||
|
||||
themeConfigs.push(theme)
|
||||
} catch (e) {
|
||||
themeErrorCount.push("Could not parse theme " + themeFile["id"] + "due to", e)
|
||||
}
|
||||
|
@ -210,12 +210,11 @@ class LayerOverviewUtils {
|
|||
console.log(msg)
|
||||
console.log("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")
|
||||
|
||||
if (process.argv.indexOf("--report") >= 0) {
|
||||
if (args.indexOf("--report") >= 0) {
|
||||
console.log("Writing report!")
|
||||
writeFileSync("layer_report.txt", errors)
|
||||
}
|
||||
|
||||
if (process.argv.indexOf("--no-fail") < 0) {
|
||||
if (args.indexOf("--no-fail") < 0) {
|
||||
throw msg;
|
||||
}
|
||||
}
|
||||
|
|
146
test.ts
146
test.ts
|
@ -1,13 +1,139 @@
|
|||
import * as wd from "wikidata-sdk"
|
||||
import * as wds from "wikibase-sdk"
|
||||
import {Utils} from "./Utils";
|
||||
import FullNodeDatabaseSource from "./Logic/FeatureSource/TiledFeatureSource/FullNodeDatabaseSource";
|
||||
|
||||
const url = wd.getEntities(["Q42"])
|
||||
console.log(url)
|
||||
Utils.downloadJson(url).then(async (entities) => {
|
||||
//const parsed = wd.parse.wb.entities(entities)["Q42"]
|
||||
console.log(entities)
|
||||
console.log(wds.simplify.entity(entities.entities["Q42"], {
|
||||
timeConverter: 'simple-day'
|
||||
}))
|
||||
|
||||
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)
|
||||
})
|
Loading…
Reference in a new issue