mapcomplete/Logic/FeatureSource/Sources/GeoJsonSource.ts

120 lines
4.4 KiB
TypeScript

/**
* 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";
export default class GeoJsonSource implements FeatureSourceForLayer, Tiled {
public readonly features: UIEventSource<{ feature: any; freshness: Date }[]>;
public readonly name;
public readonly isOsmCache: boolean
private onFail: ((errorMsg: any, url: string) => void) = undefined;
private readonly seenids: Set<string> = new Set<string>()
public readonly layer: FilteredLayer;
public readonly tileIndex
public readonly bbox;
/**
* Only used if the actual source is a tiled geojson.
* A big feature might be contained in multiple tiles.
* However, we only want to load them once. The blacklist thus contains all ids of all features previously seen
* @private
*/
private readonly featureIdBlacklist?: UIEventSource<Set<string>>
public constructor(flayer: FilteredLayer,
zxy?: [number, number, number],
options?: {
featureIdBlacklist?: UIEventSource<Set<string>>
}) {
if (flayer.layerDef.source.geojsonZoomLevel !== undefined && zxy === undefined) {
throw "Dynamic layers are not supported. Use 'DynamicGeoJsonTileSource instead"
}
this.layer = flayer;
this.featureIdBlacklist = options?.featureIdBlacklist
let url = flayer.layerDef.source.geojsonSource.replace("{layer}", flayer.layerDef.id);
if (zxy !== undefined) {
const [z, x, y] = zxy;
url = url
.replace('{z}', "" + z)
.replace('{x}', "" + x)
.replace('{y}', "" + y)
this.tileIndex = Tiles.tile_index(z, x, y)
this.bbox = BBox.fromTile(z, x, y)
} else {
this.tileIndex = Tiles.tile_index(0, 0, 0)
this.bbox = BBox.global;
}
this.name = "GeoJsonSource of " + url;
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;
Utils.downloadJson(url)
.then(json => {
if (json.elements === [] && json.remarks.indexOf("runtime error") > 0) {
self.onFail("Runtime error (timeout)", url)
return;
}
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 (typeof props[key] !== "string") {
// Make sure all the values are string, it crashes stuff otherwise
props[key] = "" + props[key]
}
}
if (props.id === undefined) {
props.id = url + "/" + i;
feature.id = url + "/" + i;
i++;
}
if (self.seenids.has(props.id)) {
skipped++;
continue;
}
self.seenids.add(props.id)
if(self.featureIdBlacklist?.data?.has(props.id)){
continue;
}
let freshness: Date = time;
if (feature.properties["_last_edit:timestamp"] !== undefined) {
freshness = new Date(props["_last_edit:timestamp"])
}
newFeatures.push({feature: feature, freshness: freshness})
}
if (newFeatures.length == 0) {
return;
}
eventSource.setData(eventSource.data.concat(newFeatures))
}).catch(msg => console.error("Could not load geojon layer", url, "due to", msg))
}
}