forked from MapComplete/MapComplete
Reformat all files with prettier
This commit is contained in:
parent
e22d189376
commit
b541d3eab4
382 changed files with 50893 additions and 35566 deletions
|
@ -1,52 +1,52 @@
|
|||
/**
|
||||
* Applies geometry changes from 'Changes' onto every feature of a featureSource
|
||||
*/
|
||||
import {Changes} from "../../Osm/Changes";
|
||||
import {UIEventSource} from "../../UIEventSource";
|
||||
import {FeatureSourceForLayer, IndexedFeatureSource} from "../FeatureSource";
|
||||
import FilteredLayer from "../../../Models/FilteredLayer";
|
||||
import {ChangeDescription, ChangeDescriptionTools} from "../../Osm/Actions/ChangeDescription";
|
||||
|
||||
import { Changes } from "../../Osm/Changes"
|
||||
import { UIEventSource } from "../../UIEventSource"
|
||||
import { FeatureSourceForLayer, IndexedFeatureSource } from "../FeatureSource"
|
||||
import FilteredLayer from "../../../Models/FilteredLayer"
|
||||
import { ChangeDescription, ChangeDescriptionTools } from "../../Osm/Actions/ChangeDescription"
|
||||
|
||||
export default class ChangeGeometryApplicator implements FeatureSourceForLayer {
|
||||
public readonly features: UIEventSource<{ feature: any; freshness: Date }[]> = new UIEventSource<{ feature: any; freshness: Date }[]>([]);
|
||||
public readonly name: string;
|
||||
public readonly features: UIEventSource<{ feature: any; freshness: Date }[]> =
|
||||
new UIEventSource<{ feature: any; freshness: Date }[]>([])
|
||||
public readonly name: string
|
||||
public readonly layer: FilteredLayer
|
||||
private readonly source: IndexedFeatureSource;
|
||||
private readonly changes: Changes;
|
||||
private readonly source: IndexedFeatureSource
|
||||
private readonly changes: Changes
|
||||
|
||||
constructor(source: (IndexedFeatureSource & FeatureSourceForLayer), changes: Changes) {
|
||||
this.source = source;
|
||||
this.changes = changes;
|
||||
constructor(source: IndexedFeatureSource & FeatureSourceForLayer, changes: Changes) {
|
||||
this.source = source
|
||||
this.changes = changes
|
||||
this.layer = source.layer
|
||||
|
||||
this.name = "ChangesApplied(" + source.name + ")"
|
||||
this.features = new UIEventSource<{ feature: any; freshness: Date }[]>(undefined)
|
||||
|
||||
const self = this;
|
||||
source.features.addCallbackAndRunD(_ => self.update())
|
||||
|
||||
changes.allChanges.addCallbackAndRunD(_ => self.update())
|
||||
const self = this
|
||||
source.features.addCallbackAndRunD((_) => self.update())
|
||||
|
||||
changes.allChanges.addCallbackAndRunD((_) => self.update())
|
||||
}
|
||||
|
||||
private update() {
|
||||
const upstreamFeatures = this.source.features.data
|
||||
const upstreamIds = this.source.containedIds.data
|
||||
const changesToApply = this.changes.allChanges.data
|
||||
?.filter(ch =>
|
||||
const changesToApply = this.changes.allChanges.data?.filter(
|
||||
(ch) =>
|
||||
// Does upsteram have this element? If not, we skip
|
||||
upstreamIds.has(ch.type + "/" + ch.id) &&
|
||||
// Are any (geometry) changes defined?
|
||||
ch.changes !== undefined &&
|
||||
// Ignore new elements, they are handled by the NewGeometryFromChangesFeatureSource
|
||||
ch.id > 0)
|
||||
ch.id > 0
|
||||
)
|
||||
|
||||
if (changesToApply === undefined || changesToApply.length === 0) {
|
||||
// No changes to apply!
|
||||
// Pass the original feature and lets continue our day
|
||||
this.features.setData(upstreamFeatures);
|
||||
return;
|
||||
this.features.setData(upstreamFeatures)
|
||||
return
|
||||
}
|
||||
|
||||
const changesPerId = new Map<string, ChangeDescription[]>()
|
||||
|
@ -58,27 +58,32 @@ export default class ChangeGeometryApplicator implements FeatureSourceForLayer {
|
|||
changesPerId.set(key, [ch])
|
||||
}
|
||||
}
|
||||
const newFeatures: { feature: any, freshness: Date }[] = []
|
||||
const newFeatures: { feature: any; freshness: Date }[] = []
|
||||
for (const feature of upstreamFeatures) {
|
||||
const changesForFeature = changesPerId.get(feature.feature.properties.id)
|
||||
if (changesForFeature === undefined) {
|
||||
// No changes for this element
|
||||
newFeatures.push(feature)
|
||||
continue;
|
||||
continue
|
||||
}
|
||||
|
||||
// Allright! We have a feature to rewrite!
|
||||
const copy = {
|
||||
...feature
|
||||
...feature,
|
||||
}
|
||||
// We only apply the last change as that one'll have the latest geometry
|
||||
const change = changesForFeature[changesForFeature.length - 1]
|
||||
copy.feature.geometry = ChangeDescriptionTools.getGeojsonGeometry(change)
|
||||
console.log("Applying a geometry change onto:", feature,"The change is:", change,"which becomes:", copy)
|
||||
console.log(
|
||||
"Applying a geometry change onto:",
|
||||
feature,
|
||||
"The change is:",
|
||||
change,
|
||||
"which becomes:",
|
||||
copy
|
||||
)
|
||||
newFeatures.push(copy)
|
||||
}
|
||||
this.features.setData(newFeatures)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,99 +1,112 @@
|
|||
import {UIEventSource} from "../../UIEventSource";
|
||||
import FeatureSource, {FeatureSourceForLayer, IndexedFeatureSource, Tiled} from "../FeatureSource";
|
||||
import FilteredLayer from "../../../Models/FilteredLayer";
|
||||
import {Tiles} from "../../../Models/TileRange";
|
||||
import {BBox} from "../../BBox";
|
||||
import { UIEventSource } from "../../UIEventSource"
|
||||
import FeatureSource, { FeatureSourceForLayer, IndexedFeatureSource, Tiled } from "../FeatureSource"
|
||||
import FilteredLayer from "../../../Models/FilteredLayer"
|
||||
import { Tiles } from "../../../Models/TileRange"
|
||||
import { BBox } from "../../BBox"
|
||||
|
||||
|
||||
export default class FeatureSourceMerger implements FeatureSourceForLayer, Tiled, IndexedFeatureSource {
|
||||
|
||||
public features: UIEventSource<{ feature: any; freshness: Date }[]> = new UIEventSource<{ feature: any; freshness: Date }[]>([]);
|
||||
public readonly name;
|
||||
export default class FeatureSourceMerger
|
||||
implements FeatureSourceForLayer, Tiled, IndexedFeatureSource
|
||||
{
|
||||
public features: UIEventSource<{ feature: any; freshness: Date }[]> = new UIEventSource<
|
||||
{ feature: any; freshness: Date }[]
|
||||
>([])
|
||||
public readonly name
|
||||
public readonly layer: FilteredLayer
|
||||
public readonly tileIndex: number;
|
||||
public readonly bbox: BBox;
|
||||
public readonly containedIds: UIEventSource<Set<string>> = new UIEventSource<Set<string>>(new Set())
|
||||
private readonly _sources: UIEventSource<FeatureSource[]>;
|
||||
public readonly tileIndex: number
|
||||
public readonly bbox: BBox
|
||||
public readonly containedIds: UIEventSource<Set<string>> = new UIEventSource<Set<string>>(
|
||||
new Set()
|
||||
)
|
||||
private readonly _sources: UIEventSource<FeatureSource[]>
|
||||
/**
|
||||
* Merges features from different featureSources for a single layer
|
||||
* Uses the freshest feature available in the case multiple sources offer data with the same identifier
|
||||
*/
|
||||
constructor(layer: FilteredLayer, tileIndex: number, bbox: BBox, sources: UIEventSource<FeatureSource[]>) {
|
||||
this.tileIndex = tileIndex;
|
||||
this.bbox = bbox;
|
||||
this._sources = sources;
|
||||
this.layer = layer;
|
||||
this.name = "FeatureSourceMerger(" + layer.layerDef.id + ", " + Tiles.tile_from_index(tileIndex).join(",") + ")"
|
||||
const self = this;
|
||||
constructor(
|
||||
layer: FilteredLayer,
|
||||
tileIndex: number,
|
||||
bbox: BBox,
|
||||
sources: UIEventSource<FeatureSource[]>
|
||||
) {
|
||||
this.tileIndex = tileIndex
|
||||
this.bbox = bbox
|
||||
this._sources = sources
|
||||
this.layer = layer
|
||||
this.name =
|
||||
"FeatureSourceMerger(" +
|
||||
layer.layerDef.id +
|
||||
", " +
|
||||
Tiles.tile_from_index(tileIndex).join(",") +
|
||||
")"
|
||||
const self = this
|
||||
|
||||
const handledSources = new Set<FeatureSource>();
|
||||
const handledSources = new Set<FeatureSource>()
|
||||
|
||||
sources.addCallbackAndRunD(sources => {
|
||||
let newSourceRegistered = false;
|
||||
sources.addCallbackAndRunD((sources) => {
|
||||
let newSourceRegistered = false
|
||||
for (let i = 0; i < sources.length; i++) {
|
||||
let source = sources[i];
|
||||
let source = sources[i]
|
||||
if (handledSources.has(source)) {
|
||||
continue
|
||||
}
|
||||
handledSources.add(source)
|
||||
newSourceRegistered = true
|
||||
source.features.addCallback(() => {
|
||||
self.Update();
|
||||
});
|
||||
self.Update()
|
||||
})
|
||||
if (newSourceRegistered) {
|
||||
self.Update();
|
||||
self.Update()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
private Update() {
|
||||
|
||||
let somethingChanged = false;
|
||||
const all: Map<string, { feature: any, freshness: Date }> = new Map<string, { feature: any; freshness: Date }>();
|
||||
let somethingChanged = false
|
||||
const all: Map<string, { feature: any; freshness: Date }> = new Map<
|
||||
string,
|
||||
{ feature: any; freshness: Date }
|
||||
>()
|
||||
// We seed the dictionary with the previously loaded features
|
||||
const oldValues = this.features.data ?? [];
|
||||
const oldValues = this.features.data ?? []
|
||||
for (const oldValue of oldValues) {
|
||||
all.set(oldValue.feature.id, oldValue)
|
||||
}
|
||||
|
||||
for (const source of this._sources.data) {
|
||||
if (source?.features?.data === undefined) {
|
||||
continue;
|
||||
continue
|
||||
}
|
||||
for (const f of source.features.data) {
|
||||
const id = f.feature.properties.id;
|
||||
const id = f.feature.properties.id
|
||||
if (!all.has(id)) {
|
||||
// This is a new feature
|
||||
somethingChanged = true;
|
||||
all.set(id, f);
|
||||
continue;
|
||||
somethingChanged = true
|
||||
all.set(id, f)
|
||||
continue
|
||||
}
|
||||
|
||||
// This value has been seen already, either in a previous run or by a previous datasource
|
||||
// Let's figure out if something changed
|
||||
const oldV = all.get(id);
|
||||
const oldV = all.get(id)
|
||||
if (oldV.freshness < f.freshness) {
|
||||
// Jup, this feature is fresher
|
||||
all.set(id, f);
|
||||
somethingChanged = true;
|
||||
all.set(id, f)
|
||||
somethingChanged = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!somethingChanged) {
|
||||
// We don't bother triggering an update
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
const newList = [];
|
||||
const newList = []
|
||||
all.forEach((value, _) => {
|
||||
newList.push(value)
|
||||
})
|
||||
this.containedIds.setData(new Set(all.keys()))
|
||||
this.features.setData(newList);
|
||||
this.features.setData(newList)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,34 +1,35 @@
|
|||
import {Store, UIEventSource} from "../../UIEventSource";
|
||||
import FilteredLayer, {FilterState} from "../../../Models/FilteredLayer";
|
||||
import {FeatureSourceForLayer, Tiled} from "../FeatureSource";
|
||||
import {BBox} from "../../BBox";
|
||||
import {ElementStorage} from "../../ElementStorage";
|
||||
import {TagsFilter} from "../../Tags/TagsFilter";
|
||||
import {OsmFeature} from "../../../Models/OsmFeature";
|
||||
import { Store, UIEventSource } from "../../UIEventSource"
|
||||
import FilteredLayer, { FilterState } from "../../../Models/FilteredLayer"
|
||||
import { FeatureSourceForLayer, Tiled } from "../FeatureSource"
|
||||
import { BBox } from "../../BBox"
|
||||
import { ElementStorage } from "../../ElementStorage"
|
||||
import { TagsFilter } from "../../Tags/TagsFilter"
|
||||
import { OsmFeature } from "../../../Models/OsmFeature"
|
||||
|
||||
export default class FilteringFeatureSource implements FeatureSourceForLayer, Tiled {
|
||||
public features: UIEventSource<{ feature: any; freshness: Date }[]> =
|
||||
new UIEventSource<{ feature: any; freshness: Date }[]>([]);
|
||||
public readonly name;
|
||||
public readonly layer: FilteredLayer;
|
||||
public features: UIEventSource<{ feature: any; freshness: Date }[]> = new UIEventSource<
|
||||
{ feature: any; freshness: Date }[]
|
||||
>([])
|
||||
public readonly name
|
||||
public readonly layer: FilteredLayer
|
||||
public readonly tileIndex: number
|
||||
public readonly bbox: BBox
|
||||
private readonly upstream: FeatureSourceForLayer;
|
||||
private readonly upstream: FeatureSourceForLayer
|
||||
private readonly state: {
|
||||
locationControl: Store<{ zoom: number }>;
|
||||
selectedElement: Store<any>,
|
||||
globalFilters: Store<{ filter: FilterState }[]>,
|
||||
locationControl: Store<{ zoom: number }>
|
||||
selectedElement: Store<any>
|
||||
globalFilters: Store<{ filter: FilterState }[]>
|
||||
allElements: ElementStorage
|
||||
};
|
||||
private readonly _alreadyRegistered = new Set<UIEventSource<any>>();
|
||||
}
|
||||
private readonly _alreadyRegistered = new Set<UIEventSource<any>>()
|
||||
private readonly _is_dirty = new UIEventSource(false)
|
||||
private previousFeatureSet: Set<any> = undefined;
|
||||
private previousFeatureSet: Set<any> = undefined
|
||||
|
||||
constructor(
|
||||
state: {
|
||||
locationControl: Store<{ zoom: number }>,
|
||||
selectedElement: Store<any>,
|
||||
allElements: ElementStorage,
|
||||
locationControl: Store<{ zoom: number }>
|
||||
selectedElement: Store<any>
|
||||
allElements: ElementStorage
|
||||
globalFilters: Store<{ filter: FilterState }[]>
|
||||
},
|
||||
tileIndex,
|
||||
|
@ -41,92 +42,95 @@ export default class FilteringFeatureSource implements FeatureSourceForLayer, Ti
|
|||
this.upstream = upstream
|
||||
this.state = state
|
||||
|
||||
this.layer = upstream.layer;
|
||||
const layer = upstream.layer;
|
||||
const self = this;
|
||||
this.layer = upstream.layer
|
||||
const layer = upstream.layer
|
||||
const self = this
|
||||
upstream.features.addCallback(() => {
|
||||
self.update();
|
||||
});
|
||||
|
||||
|
||||
layer.appliedFilters.addCallback(_ => {
|
||||
self.update()
|
||||
})
|
||||
|
||||
this._is_dirty.stabilized(1000).addCallbackAndRunD(dirty => {
|
||||
layer.appliedFilters.addCallback((_) => {
|
||||
self.update()
|
||||
})
|
||||
|
||||
this._is_dirty.stabilized(1000).addCallbackAndRunD((dirty) => {
|
||||
if (dirty) {
|
||||
self.update()
|
||||
}
|
||||
})
|
||||
|
||||
metataggingUpdated?.addCallback(_ => {
|
||||
metataggingUpdated?.addCallback((_) => {
|
||||
self._is_dirty.setData(true)
|
||||
})
|
||||
|
||||
state.globalFilters.addCallback(_ => {
|
||||
|
||||
state.globalFilters.addCallback((_) => {
|
||||
self.update()
|
||||
})
|
||||
|
||||
this.update();
|
||||
this.update()
|
||||
}
|
||||
|
||||
private update() {
|
||||
const self = this;
|
||||
const layer = this.upstream.layer;
|
||||
const features: { feature: OsmFeature; freshness: Date }[] = (this.upstream.features.data ?? []);
|
||||
const includedFeatureIds = new Set<string>();
|
||||
const globalFilters = self.state.globalFilters.data.map(f => f.filter);
|
||||
const self = this
|
||||
const layer = this.upstream.layer
|
||||
const features: { feature: OsmFeature; freshness: Date }[] =
|
||||
this.upstream.features.data ?? []
|
||||
const includedFeatureIds = new Set<string>()
|
||||
const globalFilters = self.state.globalFilters.data.map((f) => f.filter)
|
||||
const newFeatures = (features ?? []).filter((f) => {
|
||||
|
||||
self.registerCallback(f.feature)
|
||||
|
||||
const isShown: TagsFilter = layer.layerDef.isShown;
|
||||
const tags = f.feature.properties;
|
||||
if (isShown !== undefined && !isShown.matchesProperties(tags) ) {
|
||||
return false;
|
||||
const isShown: TagsFilter = layer.layerDef.isShown
|
||||
const tags = f.feature.properties
|
||||
if (isShown !== undefined && !isShown.matchesProperties(tags)) {
|
||||
return false
|
||||
}
|
||||
|
||||
const tagsFilter = Array.from(layer.appliedFilters?.data?.values() ?? [])
|
||||
for (const filter of tagsFilter) {
|
||||
const neededTags: TagsFilter = filter?.currentFilter
|
||||
if (neededTags !== undefined && !neededTags.matchesProperties(f.feature.properties)) {
|
||||
if (
|
||||
neededTags !== undefined &&
|
||||
!neededTags.matchesProperties(f.feature.properties)
|
||||
) {
|
||||
// Hidden by the filter on the layer itself - we want to hide it no matter what
|
||||
return false;
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
for (const filter of globalFilters) {
|
||||
const neededTags: TagsFilter = filter?.currentFilter
|
||||
if (neededTags !== undefined && !neededTags.matchesProperties(f.feature.properties)) {
|
||||
if (
|
||||
neededTags !== undefined &&
|
||||
!neededTags.matchesProperties(f.feature.properties)
|
||||
) {
|
||||
// Hidden by the filter on the layer itself - we want to hide it no matter what
|
||||
return false;
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
includedFeatureIds.add(f.feature.properties.id)
|
||||
return true;
|
||||
});
|
||||
return true
|
||||
})
|
||||
|
||||
const previousSet = this.previousFeatureSet;
|
||||
const previousSet = this.previousFeatureSet
|
||||
this._is_dirty.setData(false)
|
||||
|
||||
// Is there any difference between the two sets?
|
||||
if (previousSet !== undefined && previousSet.size === includedFeatureIds.size) {
|
||||
// The size of the sets is the same - they _might_ be identical
|
||||
const newItemFound = Array.from(includedFeatureIds).some(id => !previousSet.has(id))
|
||||
const newItemFound = Array.from(includedFeatureIds).some((id) => !previousSet.has(id))
|
||||
if (!newItemFound) {
|
||||
// We know that:
|
||||
// We know that:
|
||||
// - The sets have the same size
|
||||
// - Every item from the new set has been found in the old set
|
||||
// which means they are identical!
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Something new has been found!
|
||||
this.features.setData(newFeatures);
|
||||
|
||||
this.features.setData(newFeatures)
|
||||
}
|
||||
|
||||
private registerCallback(feature: any) {
|
||||
|
@ -139,11 +143,10 @@ export default class FilteringFeatureSource implements FeatureSourceForLayer, Ti
|
|||
}
|
||||
this._alreadyRegistered.add(src)
|
||||
|
||||
const self = this;
|
||||
const self = this
|
||||
// Add a callback as a changed tag migh change the filter
|
||||
src.addCallbackAndRunD(_ => {
|
||||
src.addCallbackAndRunD((_) => {
|
||||
self._is_dirty.setData(true)
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,168 +1,163 @@
|
|||
/**
|
||||
* Fetches a geojson file somewhere and passes it along
|
||||
*/
|
||||
import {UIEventSource} from "../../UIEventSource";
|
||||
import FilteredLayer from "../../../Models/FilteredLayer";
|
||||
import {Utils} from "../../../Utils";
|
||||
import {FeatureSourceForLayer, Tiled} from "../FeatureSource";
|
||||
import {Tiles} from "../../../Models/TileRange";
|
||||
import {BBox} from "../../BBox";
|
||||
import {GeoOperations} from "../../GeoOperations";
|
||||
|
||||
import { UIEventSource } from "../../UIEventSource"
|
||||
import FilteredLayer from "../../../Models/FilteredLayer"
|
||||
import { Utils } from "../../../Utils"
|
||||
import { FeatureSourceForLayer, Tiled } from "../FeatureSource"
|
||||
import { Tiles } from "../../../Models/TileRange"
|
||||
import { BBox } from "../../BBox"
|
||||
import { GeoOperations } from "../../GeoOperations"
|
||||
|
||||
export default class GeoJsonSource implements FeatureSourceForLayer, Tiled {
|
||||
|
||||
public readonly features: UIEventSource<{ feature: any; freshness: Date }[]>;
|
||||
public readonly state = new UIEventSource<undefined | {error: string} | "loaded">(undefined)
|
||||
public readonly name;
|
||||
public readonly features: UIEventSource<{ feature: any; freshness: Date }[]>
|
||||
public readonly state = new UIEventSource<undefined | { error: string } | "loaded">(undefined)
|
||||
public readonly name
|
||||
public readonly isOsmCache: boolean
|
||||
public readonly layer: FilteredLayer;
|
||||
public readonly layer: FilteredLayer
|
||||
public readonly tileIndex
|
||||
public readonly bbox;
|
||||
private readonly seenids: Set<string>;
|
||||
private readonly idKey ?: string;
|
||||
|
||||
public constructor(flayer: FilteredLayer,
|
||||
zxy?: [number, number, number] | BBox,
|
||||
options?: {
|
||||
featureIdBlacklist?: Set<string>
|
||||
}) {
|
||||
public readonly bbox
|
||||
private readonly seenids: Set<string>
|
||||
private readonly idKey?: string
|
||||
|
||||
public constructor(
|
||||
flayer: FilteredLayer,
|
||||
zxy?: [number, number, number] | BBox,
|
||||
options?: {
|
||||
featureIdBlacklist?: Set<string>
|
||||
}
|
||||
) {
|
||||
if (flayer.layerDef.source.geojsonZoomLevel !== undefined && zxy === undefined) {
|
||||
throw "Dynamic layers are not supported. Use 'DynamicGeoJsonTileSource instead"
|
||||
}
|
||||
|
||||
this.layer = flayer;
|
||||
this.layer = flayer
|
||||
this.idKey = flayer.layerDef.source.idKey
|
||||
this.seenids = options?.featureIdBlacklist ?? new Set<string>()
|
||||
let url = flayer.layerDef.source.geojsonSource.replace("{layer}", flayer.layerDef.id);
|
||||
let url = flayer.layerDef.source.geojsonSource.replace("{layer}", flayer.layerDef.id)
|
||||
if (zxy !== undefined) {
|
||||
let tile_bbox: BBox;
|
||||
let tile_bbox: BBox
|
||||
if (zxy instanceof BBox) {
|
||||
tile_bbox = zxy;
|
||||
tile_bbox = zxy
|
||||
} else {
|
||||
const [z, x, y] = zxy;
|
||||
tile_bbox = BBox.fromTile(z, x, y);
|
||||
const [z, x, y] = zxy
|
||||
tile_bbox = BBox.fromTile(z, x, y)
|
||||
|
||||
this.tileIndex = Tiles.tile_index(z, x, y)
|
||||
this.bbox = BBox.fromTile(z, x, y)
|
||||
url = url
|
||||
.replace('{z}', "" + z)
|
||||
.replace('{x}', "" + x)
|
||||
.replace('{y}', "" + y)
|
||||
.replace("{z}", "" + z)
|
||||
.replace("{x}", "" + x)
|
||||
.replace("{y}", "" + y)
|
||||
}
|
||||
let bounds: { minLat: number, maxLat: number, minLon: number, maxLon: number } = tile_bbox
|
||||
let bounds: { minLat: number; maxLat: number; minLon: number; maxLon: number } =
|
||||
tile_bbox
|
||||
if (this.layer.layerDef.source.mercatorCrs) {
|
||||
bounds = tile_bbox.toMercator()
|
||||
}
|
||||
|
||||
url = url
|
||||
.replace('{y_min}', "" + bounds.minLat)
|
||||
.replace('{y_max}', "" + bounds.maxLat)
|
||||
.replace('{x_min}', "" + bounds.minLon)
|
||||
.replace('{x_max}', "" + bounds.maxLon)
|
||||
|
||||
|
||||
.replace("{y_min}", "" + bounds.minLat)
|
||||
.replace("{y_max}", "" + bounds.maxLat)
|
||||
.replace("{x_min}", "" + bounds.minLon)
|
||||
.replace("{x_max}", "" + bounds.maxLon)
|
||||
} else {
|
||||
this.tileIndex = Tiles.tile_index(0, 0, 0)
|
||||
this.bbox = BBox.global;
|
||||
this.bbox = BBox.global
|
||||
}
|
||||
|
||||
this.name = "GeoJsonSource of " + url;
|
||||
this.name = "GeoJsonSource of " + url
|
||||
|
||||
this.isOsmCache = flayer.layerDef.source.isOsmCacheLayer;
|
||||
this.isOsmCache = flayer.layerDef.source.isOsmCacheLayer
|
||||
this.features = new UIEventSource<{ feature: any; freshness: Date }[]>([])
|
||||
this.LoadJSONFrom(url)
|
||||
}
|
||||
|
||||
|
||||
private LoadJSONFrom(url: string) {
|
||||
const eventSource = this.features;
|
||||
const self = this;
|
||||
const eventSource = this.features
|
||||
const self = this
|
||||
Utils.downloadJsonCached(url, 60 * 60)
|
||||
.then(json => {
|
||||
.then((json) => {
|
||||
self.state.setData("loaded")
|
||||
// TODO: move somewhere else, just for testing
|
||||
// Check for maproulette data
|
||||
if (url.startsWith("https://maproulette.org/api/v2/tasks/box/")) {
|
||||
console.log("MapRoulette data detected")
|
||||
const data = json;
|
||||
let maprouletteFeatures: any[] = [];
|
||||
data.forEach(element => {
|
||||
const data = json
|
||||
let maprouletteFeatures: any[] = []
|
||||
data.forEach((element) => {
|
||||
maprouletteFeatures.push({
|
||||
type: "Feature",
|
||||
geometry: {
|
||||
type: "Point",
|
||||
coordinates: [element.point.lng, element.point.lat]
|
||||
coordinates: [element.point.lng, element.point.lat],
|
||||
},
|
||||
properties: {
|
||||
// Map all properties to the feature
|
||||
...element,
|
||||
}
|
||||
});
|
||||
});
|
||||
json.features = maprouletteFeatures;
|
||||
},
|
||||
})
|
||||
})
|
||||
json.features = maprouletteFeatures
|
||||
}
|
||||
|
||||
if (json.features === undefined || json.features === null) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
if (self.layer.layerDef.source.mercatorCrs) {
|
||||
json = GeoOperations.GeoJsonToWGS84(json)
|
||||
}
|
||||
|
||||
const time = new Date();
|
||||
const newFeatures: { feature: any, freshness: Date } [] = []
|
||||
let i = 0;
|
||||
let skipped = 0;
|
||||
const time = new Date()
|
||||
const newFeatures: { feature: any; freshness: Date }[] = []
|
||||
let i = 0
|
||||
let skipped = 0
|
||||
for (const feature of json.features) {
|
||||
const props = feature.properties
|
||||
for (const key in props) {
|
||||
|
||||
if(props[key] === null){
|
||||
if (props[key] === null) {
|
||||
delete props[key]
|
||||
}
|
||||
|
||||
|
||||
if (typeof props[key] !== "string") {
|
||||
// Make sure all the values are string, it crashes stuff otherwise
|
||||
props[key] = JSON.stringify(props[key])
|
||||
}
|
||||
}
|
||||
|
||||
if(self.idKey !== undefined){
|
||||
if (self.idKey !== undefined) {
|
||||
props.id = props[self.idKey]
|
||||
}
|
||||
|
||||
|
||||
if (props.id === undefined) {
|
||||
props.id = url + "/" + i;
|
||||
feature.id = url + "/" + i;
|
||||
i++;
|
||||
props.id = url + "/" + i
|
||||
feature.id = url + "/" + i
|
||||
i++
|
||||
}
|
||||
if (self.seenids.has(props.id)) {
|
||||
skipped++;
|
||||
continue;
|
||||
skipped++
|
||||
continue
|
||||
}
|
||||
self.seenids.add(props.id)
|
||||
|
||||
let freshness: Date = time;
|
||||
let freshness: Date = time
|
||||
if (feature.properties["_last_edit:timestamp"] !== undefined) {
|
||||
freshness = new Date(props["_last_edit:timestamp"])
|
||||
}
|
||||
|
||||
newFeatures.push({feature: feature, freshness: freshness})
|
||||
newFeatures.push({ feature: feature, freshness: freshness })
|
||||
}
|
||||
|
||||
if (newFeatures.length == 0) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
eventSource.setData(eventSource.data.concat(newFeatures))
|
||||
|
||||
}).catch(msg => {
|
||||
console.debug("Could not load geojson layer", url, "due to", msg);
|
||||
self.state.setData({error: msg})
|
||||
})
|
||||
})
|
||||
.catch((msg) => {
|
||||
console.debug("Could not load geojson layer", url, "due to", msg)
|
||||
self.state.setData({ error: msg })
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,50 +1,50 @@
|
|||
import {Changes} from "../../Osm/Changes";
|
||||
import {OsmNode, OsmObject, OsmRelation, OsmWay} from "../../Osm/OsmObject";
|
||||
import FeatureSource from "../FeatureSource";
|
||||
import {UIEventSource} from "../../UIEventSource";
|
||||
import {ChangeDescription} from "../../Osm/Actions/ChangeDescription";
|
||||
import {ElementStorage} from "../../ElementStorage";
|
||||
import {OsmId, OsmTags} from "../../../Models/OsmFeature";
|
||||
import { Changes } from "../../Osm/Changes"
|
||||
import { OsmNode, OsmObject, OsmRelation, OsmWay } from "../../Osm/OsmObject"
|
||||
import FeatureSource from "../FeatureSource"
|
||||
import { UIEventSource } from "../../UIEventSource"
|
||||
import { ChangeDescription } from "../../Osm/Actions/ChangeDescription"
|
||||
import { ElementStorage } from "../../ElementStorage"
|
||||
import { OsmId, OsmTags } from "../../../Models/OsmFeature"
|
||||
|
||||
export class NewGeometryFromChangesFeatureSource implements FeatureSource {
|
||||
// This class name truly puts the 'Java' into 'Javascript'
|
||||
|
||||
/**
|
||||
* A feature source containing exclusively new elements.
|
||||
*
|
||||
*
|
||||
* These elements are probably created by the 'SimpleAddUi' which generates a new point, but the import functionality might create a line or polygon too.
|
||||
* Other sources of new points are e.g. imports from nodes
|
||||
*/
|
||||
public readonly features: UIEventSource<{ feature: any; freshness: Date }[]> = new UIEventSource<{ feature: any; freshness: Date }[]>([]);
|
||||
public readonly name: string = "newFeatures";
|
||||
public readonly features: UIEventSource<{ feature: any; freshness: Date }[]> =
|
||||
new UIEventSource<{ feature: any; freshness: Date }[]>([])
|
||||
public readonly name: string = "newFeatures"
|
||||
|
||||
constructor(changes: Changes, allElementStorage: ElementStorage, backendUrl: string) {
|
||||
const seenChanges = new Set<ChangeDescription>()
|
||||
const features = this.features.data
|
||||
const self = this
|
||||
|
||||
const seenChanges = new Set<ChangeDescription>();
|
||||
const features = this.features.data;
|
||||
const self = this;
|
||||
|
||||
changes.pendingChanges.stabilized(100).addCallbackAndRunD(changes => {
|
||||
changes.pendingChanges.stabilized(100).addCallbackAndRunD((changes) => {
|
||||
if (changes.length === 0) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
const now = new Date();
|
||||
let somethingChanged = false;
|
||||
const now = new Date()
|
||||
let somethingChanged = false
|
||||
|
||||
function add(feature) {
|
||||
feature.id = feature.properties.id
|
||||
features.push({
|
||||
feature: feature,
|
||||
freshness: now
|
||||
freshness: now,
|
||||
})
|
||||
somethingChanged = true;
|
||||
somethingChanged = true
|
||||
}
|
||||
|
||||
for (const change of changes) {
|
||||
if (seenChanges.has(change)) {
|
||||
// Already handled
|
||||
continue;
|
||||
continue
|
||||
}
|
||||
seenChanges.add(change)
|
||||
|
||||
|
@ -60,35 +60,32 @@ export class NewGeometryFromChangesFeatureSource implements FeatureSource {
|
|||
// For this, we introspect the change
|
||||
if (allElementStorage.has(change.type + "/" + change.id)) {
|
||||
// The current point already exists, we don't have to do anything here
|
||||
continue;
|
||||
continue
|
||||
}
|
||||
console.debug("Detected a reused point")
|
||||
// The 'allElementsStore' does _not_ have this point yet, so we have to create it
|
||||
OsmObject.DownloadObjectAsync(change.type + "/" + change.id).then(feat => {
|
||||
OsmObject.DownloadObjectAsync(change.type + "/" + change.id).then((feat) => {
|
||||
console.log("Got the reused point:", feat)
|
||||
for (const kv of change.tags) {
|
||||
feat.tags[kv.k] = kv.v
|
||||
}
|
||||
const geojson = feat.asGeoJson();
|
||||
const geojson = feat.asGeoJson()
|
||||
allElementStorage.addOrGetElement(geojson)
|
||||
self.features.data.push({feature: geojson, freshness: new Date()})
|
||||
self.features.data.push({ feature: geojson, freshness: new Date() })
|
||||
self.features.ping()
|
||||
})
|
||||
continue
|
||||
|
||||
|
||||
} else if (change.id < 0 && change.changes === undefined) {
|
||||
// The geometry is not described - not a new point
|
||||
if (change.id < 0) {
|
||||
console.error("WARNING: got a new point without geometry!")
|
||||
}
|
||||
continue;
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
const tags: OsmTags = {
|
||||
id: <OsmId> (change.type + "/" + change.id)
|
||||
id: <OsmId>(change.type + "/" + change.id),
|
||||
}
|
||||
for (const kv of change.tags) {
|
||||
tags[kv.k] = kv.v
|
||||
|
@ -104,30 +101,31 @@ export class NewGeometryFromChangesFeatureSource implements FeatureSource {
|
|||
n.lon = change.changes["lon"]
|
||||
const geojson = n.asGeoJson()
|
||||
add(geojson)
|
||||
break;
|
||||
break
|
||||
case "way":
|
||||
const w = new OsmWay(change.id)
|
||||
w.tags = tags
|
||||
w.nodes = change.changes["nodes"]
|
||||
w.coordinates = change.changes["coordinates"].map(([lon, lat]) => [lat, lon])
|
||||
w.coordinates = change.changes["coordinates"].map(([lon, lat]) => [
|
||||
lat,
|
||||
lon,
|
||||
])
|
||||
add(w.asGeoJson())
|
||||
break;
|
||||
break
|
||||
case "relation":
|
||||
const r = new OsmRelation(change.id)
|
||||
r.tags = tags
|
||||
r.members = change.changes["members"]
|
||||
add(r.asGeoJson())
|
||||
break;
|
||||
break
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Could not generate a new geometry to render on screen for:", e)
|
||||
}
|
||||
|
||||
}
|
||||
if (somethingChanged) {
|
||||
self.features.ping()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,34 +2,36 @@
|
|||
* Every previously added point is remembered, but new points are added.
|
||||
* Data coming from upstream will always overwrite a previous value
|
||||
*/
|
||||
import FeatureSource, {Tiled} from "../FeatureSource";
|
||||
import {Store, UIEventSource} from "../../UIEventSource";
|
||||
import {BBox} from "../../BBox";
|
||||
import FeatureSource, { Tiled } from "../FeatureSource"
|
||||
import { Store, UIEventSource } from "../../UIEventSource"
|
||||
import { BBox } from "../../BBox"
|
||||
|
||||
export default class RememberingSource implements FeatureSource, Tiled {
|
||||
|
||||
public readonly features: Store<{ feature: any, freshness: Date }[]>;
|
||||
public readonly name;
|
||||
public readonly features: Store<{ feature: any; freshness: Date }[]>
|
||||
public readonly name
|
||||
public readonly tileIndex: number
|
||||
public readonly bbox: BBox
|
||||
|
||||
constructor(source: FeatureSource & Tiled) {
|
||||
const self = this;
|
||||
this.name = "RememberingSource of " + source.name;
|
||||
const self = this
|
||||
this.name = "RememberingSource of " + source.name
|
||||
this.tileIndex = source.tileIndex
|
||||
this.bbox = source.bbox;
|
||||
this.bbox = source.bbox
|
||||
|
||||
const empty = [];
|
||||
const featureSource = new UIEventSource<{feature: any, freshness: Date}[]>(empty)
|
||||
const empty = []
|
||||
const featureSource = new UIEventSource<{ feature: any; freshness: Date }[]>(empty)
|
||||
this.features = featureSource
|
||||
source.features.addCallbackAndRunD(features => {
|
||||
const oldFeatures = self.features?.data ?? empty;
|
||||
source.features.addCallbackAndRunD((features) => {
|
||||
const oldFeatures = self.features?.data ?? empty
|
||||
// Then new ids
|
||||
const ids = new Set<string>(features.map(f => f.feature.properties.id + f.feature.geometry.type));
|
||||
const ids = new Set<string>(
|
||||
features.map((f) => f.feature.properties.id + f.feature.geometry.type)
|
||||
)
|
||||
// the old data
|
||||
const oldData = oldFeatures.filter(old => !ids.has(old.feature.properties.id + old.feature.geometry.type))
|
||||
const oldData = oldFeatures.filter(
|
||||
(old) => !ids.has(old.feature.properties.id + old.feature.geometry.type)
|
||||
)
|
||||
featureSource.setData([...features, ...oldData])
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,50 +1,57 @@
|
|||
/**
|
||||
* This feature source helps the ShowDataLayer class: it introduces the necessary extra features and indicates with what renderConfig it should be rendered.
|
||||
*/
|
||||
import {Store} from "../../UIEventSource";
|
||||
import {GeoOperations} from "../../GeoOperations";
|
||||
import FeatureSource from "../FeatureSource";
|
||||
import PointRenderingConfig from "../../../Models/ThemeConfig/PointRenderingConfig";
|
||||
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig";
|
||||
import LineRenderingConfig from "../../../Models/ThemeConfig/LineRenderingConfig";
|
||||
import { Store } from "../../UIEventSource"
|
||||
import { GeoOperations } from "../../GeoOperations"
|
||||
import FeatureSource from "../FeatureSource"
|
||||
import PointRenderingConfig from "../../../Models/ThemeConfig/PointRenderingConfig"
|
||||
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"
|
||||
import LineRenderingConfig from "../../../Models/ThemeConfig/LineRenderingConfig"
|
||||
|
||||
export default class RenderingMultiPlexerFeatureSource {
|
||||
public readonly features: Store<(any & { pointRenderingIndex: number | undefined, lineRenderingIndex: number | undefined })[]>;
|
||||
private readonly pointRenderings: { rendering: PointRenderingConfig; index: number }[];
|
||||
private centroidRenderings: { rendering: PointRenderingConfig; index: number }[];
|
||||
private projectedCentroidRenderings: { rendering: PointRenderingConfig; index: number }[];
|
||||
private startRenderings: { rendering: PointRenderingConfig; index: number }[];
|
||||
private endRenderings: { rendering: PointRenderingConfig; index: number }[];
|
||||
private hasCentroid: boolean;
|
||||
private lineRenderObjects: LineRenderingConfig[];
|
||||
public readonly features: Store<
|
||||
(any & {
|
||||
pointRenderingIndex: number | undefined
|
||||
lineRenderingIndex: number | undefined
|
||||
})[]
|
||||
>
|
||||
private readonly pointRenderings: { rendering: PointRenderingConfig; index: number }[]
|
||||
private centroidRenderings: { rendering: PointRenderingConfig; index: number }[]
|
||||
private projectedCentroidRenderings: { rendering: PointRenderingConfig; index: number }[]
|
||||
private startRenderings: { rendering: PointRenderingConfig; index: number }[]
|
||||
private endRenderings: { rendering: PointRenderingConfig; index: number }[]
|
||||
private hasCentroid: boolean
|
||||
private lineRenderObjects: LineRenderingConfig[]
|
||||
|
||||
|
||||
private inspectFeature(feat, addAsPoint: (feat, rendering, centerpoint: [number, number]) => void, withIndex: any[]){
|
||||
private inspectFeature(
|
||||
feat,
|
||||
addAsPoint: (feat, rendering, centerpoint: [number, number]) => void,
|
||||
withIndex: any[]
|
||||
) {
|
||||
if (feat.geometry.type === "Point") {
|
||||
|
||||
for (const rendering of this.pointRenderings) {
|
||||
withIndex.push({
|
||||
...feat,
|
||||
pointRenderingIndex: rendering.index
|
||||
pointRenderingIndex: rendering.index,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
// This is a a line: add the centroids
|
||||
let centerpoint: [number, number] = undefined;
|
||||
let projectedCenterPoint : [number, number] = undefined
|
||||
if(this.hasCentroid){
|
||||
centerpoint = GeoOperations.centerpointCoordinates(feat)
|
||||
if(this.projectedCentroidRenderings.length > 0){
|
||||
projectedCenterPoint = <[number,number]> GeoOperations.nearestPoint(feat, centerpoint).geometry.coordinates
|
||||
let centerpoint: [number, number] = undefined
|
||||
let projectedCenterPoint: [number, number] = undefined
|
||||
if (this.hasCentroid) {
|
||||
centerpoint = GeoOperations.centerpointCoordinates(feat)
|
||||
if (this.projectedCentroidRenderings.length > 0) {
|
||||
projectedCenterPoint = <[number, number]>(
|
||||
GeoOperations.nearestPoint(feat, centerpoint).geometry.coordinates
|
||||
)
|
||||
}
|
||||
}
|
||||
for (const rendering of this.centroidRenderings) {
|
||||
addAsPoint(feat, rendering, centerpoint)
|
||||
}
|
||||
|
||||
|
||||
if (feat.geometry.type === "LineString") {
|
||||
|
||||
for (const rendering of this.projectedCentroidRenderings) {
|
||||
addAsPoint(feat, rendering, projectedCenterPoint)
|
||||
}
|
||||
|
@ -58,73 +65,69 @@ export default class RenderingMultiPlexerFeatureSource {
|
|||
const coordinate = coordinates[coordinates.length - 1]
|
||||
addAsPoint(feat, rendering, coordinate)
|
||||
}
|
||||
|
||||
}else{
|
||||
} else {
|
||||
for (const rendering of this.projectedCentroidRenderings) {
|
||||
addAsPoint(feat, rendering, centerpoint)
|
||||
}
|
||||
}
|
||||
|
||||
// AT last, add it 'as is' to what we should render
|
||||
// AT last, add it 'as is' to what we should render
|
||||
for (let i = 0; i < this.lineRenderObjects.length; i++) {
|
||||
withIndex.push({
|
||||
...feat,
|
||||
lineRenderingIndex: i
|
||||
lineRenderingIndex: i,
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
constructor(upstream: FeatureSource, layer: LayerConfig) {
|
||||
|
||||
const pointRenderObjects: { rendering: PointRenderingConfig, index: number }[] = layer.mapRendering.map((r, i) => ({
|
||||
rendering: r,
|
||||
index: i
|
||||
}))
|
||||
this.pointRenderings = pointRenderObjects.filter(r => r.rendering.location.has("point"))
|
||||
this.centroidRenderings = pointRenderObjects.filter(r => r.rendering.location.has("centroid"))
|
||||
this.projectedCentroidRenderings = pointRenderObjects.filter(r => r.rendering.location.has("projected_centerpoint"))
|
||||
this.startRenderings = pointRenderObjects.filter(r => r.rendering.location.has("start"))
|
||||
this.endRenderings = pointRenderObjects.filter(r => r.rendering.location.has("end"))
|
||||
this.hasCentroid = this.centroidRenderings.length > 0 || this.projectedCentroidRenderings.length > 0
|
||||
const pointRenderObjects: { rendering: PointRenderingConfig; index: number }[] =
|
||||
layer.mapRendering.map((r, i) => ({
|
||||
rendering: r,
|
||||
index: i,
|
||||
}))
|
||||
this.pointRenderings = pointRenderObjects.filter((r) => r.rendering.location.has("point"))
|
||||
this.centroidRenderings = pointRenderObjects.filter((r) =>
|
||||
r.rendering.location.has("centroid")
|
||||
)
|
||||
this.projectedCentroidRenderings = pointRenderObjects.filter((r) =>
|
||||
r.rendering.location.has("projected_centerpoint")
|
||||
)
|
||||
this.startRenderings = pointRenderObjects.filter((r) => r.rendering.location.has("start"))
|
||||
this.endRenderings = pointRenderObjects.filter((r) => r.rendering.location.has("end"))
|
||||
this.hasCentroid =
|
||||
this.centroidRenderings.length > 0 || this.projectedCentroidRenderings.length > 0
|
||||
this.lineRenderObjects = layer.lineRendering
|
||||
|
||||
this.features = upstream.features.map(
|
||||
features => {
|
||||
if (features === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
||||
const withIndex: any[] = [];
|
||||
|
||||
function addAsPoint(feat, rendering, coordinate) {
|
||||
const patched = {
|
||||
...feat,
|
||||
pointRenderingIndex: rendering.index
|
||||
}
|
||||
patched.geometry = {
|
||||
type: "Point",
|
||||
coordinates: coordinate
|
||||
}
|
||||
withIndex.push(patched)
|
||||
}
|
||||
|
||||
|
||||
for (const f of features) {
|
||||
const feat = f.feature;
|
||||
if(feat === undefined){
|
||||
continue
|
||||
}
|
||||
this.inspectFeature(feat, addAsPoint, withIndex)
|
||||
}
|
||||
|
||||
|
||||
return withIndex;
|
||||
this.features = upstream.features.map((features) => {
|
||||
if (features === undefined) {
|
||||
return undefined
|
||||
}
|
||||
);
|
||||
|
||||
const withIndex: any[] = []
|
||||
|
||||
function addAsPoint(feat, rendering, coordinate) {
|
||||
const patched = {
|
||||
...feat,
|
||||
pointRenderingIndex: rendering.index,
|
||||
}
|
||||
patched.geometry = {
|
||||
type: "Point",
|
||||
coordinates: coordinate,
|
||||
}
|
||||
withIndex.push(patched)
|
||||
}
|
||||
|
||||
for (const f of features) {
|
||||
const feat = f.feature
|
||||
if (feat === undefined) {
|
||||
continue
|
||||
}
|
||||
this.inspectFeature(feat, addAsPoint, withIndex)
|
||||
}
|
||||
|
||||
return withIndex
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,21 +1,24 @@
|
|||
import {UIEventSource} from "../../UIEventSource";
|
||||
import FilteredLayer from "../../../Models/FilteredLayer";
|
||||
import {FeatureSourceForLayer, Tiled} from "../FeatureSource";
|
||||
import {BBox} from "../../BBox";
|
||||
import { UIEventSource } from "../../UIEventSource"
|
||||
import FilteredLayer from "../../../Models/FilteredLayer"
|
||||
import { FeatureSourceForLayer, Tiled } from "../FeatureSource"
|
||||
import { BBox } from "../../BBox"
|
||||
|
||||
export default class SimpleFeatureSource implements FeatureSourceForLayer, Tiled {
|
||||
public readonly features: UIEventSource<{ feature: any; freshness: Date }[]>;
|
||||
public readonly name: string = "SimpleFeatureSource";
|
||||
public readonly layer: FilteredLayer;
|
||||
public readonly bbox: BBox = BBox.global;
|
||||
public readonly tileIndex: number;
|
||||
public readonly features: UIEventSource<{ feature: any; freshness: Date }[]>
|
||||
public readonly name: string = "SimpleFeatureSource"
|
||||
public readonly layer: FilteredLayer
|
||||
public readonly bbox: BBox = BBox.global
|
||||
public readonly tileIndex: number
|
||||
|
||||
constructor(layer: FilteredLayer, tileIndex: number, featureSource?: UIEventSource<{ feature: any; freshness: Date }[]> ) {
|
||||
constructor(
|
||||
layer: FilteredLayer,
|
||||
tileIndex: number,
|
||||
featureSource?: UIEventSource<{ feature: any; freshness: Date }[]>
|
||||
) {
|
||||
this.name = "SimpleFeatureSource(" + layer.layerDef.id + ")"
|
||||
this.layer = layer
|
||||
this.tileIndex = tileIndex ?? 0;
|
||||
this.tileIndex = tileIndex ?? 0
|
||||
this.bbox = BBox.fromTileIndex(this.tileIndex)
|
||||
this.features = featureSource ?? new UIEventSource<{ feature: any; freshness: Date }[]>([]);
|
||||
this.features = featureSource ?? new UIEventSource<{ feature: any; freshness: Date }[]>([])
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,62 +1,90 @@
|
|||
import FeatureSource, {FeatureSourceForLayer, Tiled} from "../FeatureSource";
|
||||
import {ImmutableStore, Store, UIEventSource} from "../../UIEventSource";
|
||||
import {stat} from "fs";
|
||||
import FilteredLayer from "../../../Models/FilteredLayer";
|
||||
import {BBox} from "../../BBox";
|
||||
import {Feature} from "@turf/turf";
|
||||
import FeatureSource, { FeatureSourceForLayer, Tiled } from "../FeatureSource"
|
||||
import { ImmutableStore, Store, UIEventSource } from "../../UIEventSource"
|
||||
import { stat } from "fs"
|
||||
import FilteredLayer from "../../../Models/FilteredLayer"
|
||||
import { BBox } from "../../BBox"
|
||||
import { Feature } from "@turf/turf"
|
||||
|
||||
/**
|
||||
* A simple, read only feature store.
|
||||
*/
|
||||
export default class StaticFeatureSource implements FeatureSource {
|
||||
public readonly features: Store<{ feature: any; freshness: Date }[]>;
|
||||
public readonly features: Store<{ feature: any; freshness: Date }[]>
|
||||
public readonly name: string
|
||||
|
||||
constructor(features: Store<{ feature: Feature, freshness: Date }[]>, name = "StaticFeatureSource") {
|
||||
constructor(
|
||||
features: Store<{ feature: Feature; freshness: Date }[]>,
|
||||
name = "StaticFeatureSource"
|
||||
) {
|
||||
if (features === undefined) {
|
||||
throw "Static feature source received undefined as source"
|
||||
}
|
||||
this.name = name;
|
||||
this.features = features;
|
||||
this.name = name
|
||||
this.features = features
|
||||
}
|
||||
|
||||
public static fromGeojsonAndDate(features: { feature: Feature, freshness: Date }[], name = "StaticFeatureSourceFromGeojsonAndDate"): StaticFeatureSource {
|
||||
return new StaticFeatureSource(new ImmutableStore(features), name);
|
||||
public static fromGeojsonAndDate(
|
||||
features: { feature: Feature; freshness: Date }[],
|
||||
name = "StaticFeatureSourceFromGeojsonAndDate"
|
||||
): StaticFeatureSource {
|
||||
return new StaticFeatureSource(new ImmutableStore(features), name)
|
||||
}
|
||||
|
||||
|
||||
public static fromGeojson(geojson: Feature[], name = "StaticFeatureSourceFromGeojson"): StaticFeatureSource {
|
||||
const now = new Date();
|
||||
return StaticFeatureSource.fromGeojsonAndDate(geojson.map(feature => ({feature, freshness: now})), name);
|
||||
public static fromGeojson(
|
||||
geojson: Feature[],
|
||||
name = "StaticFeatureSourceFromGeojson"
|
||||
): StaticFeatureSource {
|
||||
const now = new Date()
|
||||
return StaticFeatureSource.fromGeojsonAndDate(
|
||||
geojson.map((feature) => ({ feature, freshness: now })),
|
||||
name
|
||||
)
|
||||
}
|
||||
|
||||
public static fromGeojsonStore(geojson: Store<Feature[]>, name = "StaticFeatureSourceFromGeojson"): StaticFeatureSource {
|
||||
const now = new Date();
|
||||
const mapped : Store<{feature: Feature, freshness: Date}[]> = geojson.map(features => features.map(feature => ({feature, freshness: now})))
|
||||
return new StaticFeatureSource(mapped, name);
|
||||
public static fromGeojsonStore(
|
||||
geojson: Store<Feature[]>,
|
||||
name = "StaticFeatureSourceFromGeojson"
|
||||
): StaticFeatureSource {
|
||||
const now = new Date()
|
||||
const mapped: Store<{ feature: Feature; freshness: Date }[]> = geojson.map((features) =>
|
||||
features.map((feature) => ({ feature, freshness: now }))
|
||||
)
|
||||
return new StaticFeatureSource(mapped, name)
|
||||
}
|
||||
|
||||
static fromDateless(featureSource: Store<{ feature: Feature }[]>, name = "StaticFeatureSourceFromDateless") {
|
||||
const now = new Date();
|
||||
return new StaticFeatureSource(featureSource.map(features => features.map(feature => ({
|
||||
feature: feature.feature,
|
||||
freshness: now
|
||||
}))), name);
|
||||
static fromDateless(
|
||||
featureSource: Store<{ feature: Feature }[]>,
|
||||
name = "StaticFeatureSourceFromDateless"
|
||||
) {
|
||||
const now = new Date()
|
||||
return new StaticFeatureSource(
|
||||
featureSource.map((features) =>
|
||||
features.map((feature) => ({
|
||||
feature: feature.feature,
|
||||
freshness: now,
|
||||
}))
|
||||
),
|
||||
name
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export class TiledStaticFeatureSource extends StaticFeatureSource implements Tiled, FeatureSourceForLayer{
|
||||
export class TiledStaticFeatureSource
|
||||
extends StaticFeatureSource
|
||||
implements Tiled, FeatureSourceForLayer
|
||||
{
|
||||
public readonly bbox: BBox = BBox.global
|
||||
public readonly tileIndex: number
|
||||
public readonly layer: FilteredLayer
|
||||
|
||||
public readonly bbox: BBox = BBox.global;
|
||||
public readonly tileIndex: number;
|
||||
public readonly layer: FilteredLayer;
|
||||
|
||||
constructor(features: Store<{ feature: any, freshness: Date }[]>, layer: FilteredLayer ,tileIndex : number = 0) {
|
||||
super(features);
|
||||
this.tileIndex = tileIndex ;
|
||||
this.layer= layer;
|
||||
constructor(
|
||||
features: Store<{ feature: any; freshness: Date }[]>,
|
||||
layer: FilteredLayer,
|
||||
tileIndex: number = 0
|
||||
) {
|
||||
super(features)
|
||||
this.tileIndex = tileIndex
|
||||
this.layer = layer
|
||||
this.bbox = BBox.fromTileIndex(this.tileIndex)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue