Download button: take advantage of MVT server, download button will now attempt to download everything

This commit is contained in:
Pieter Vander Vennet 2024-02-21 16:35:49 +01:00
parent bccda67e1c
commit e4eb8d6b52
21 changed files with 453 additions and 353 deletions

View file

@ -1,11 +1,11 @@
import { ImmutableStore, Store } from "../../UIEventSource"
import DynamicTileSource from "./DynamicTileSource"
import { UpdatableDynamicTileSource } from "./DynamicTileSource"
import { Utils } from "../../../Utils"
import GeoJsonSource from "../Sources/GeoJsonSource"
import { BBox } from "../../BBox"
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"
export default class DynamicGeoJsonTileSource extends DynamicTileSource {
export default class DynamicGeoJsonTileSource extends UpdatableDynamicTileSource {
private static whitelistCache = new Map<string, any>()
constructor(
@ -65,7 +65,7 @@ export default class DynamicGeoJsonTileSource extends DynamicTileSource {
const blackList = new Set<string>()
super(
new ImmutableStore(source.geojsonZoomLevel),
new ImmutableStore(source.geojsonZoomLevel),
layer.minzoom,
(zxy) => {
if (whitelist !== undefined) {

View file

@ -1,78 +1,16 @@
import { Store } from "../../UIEventSource"
import DynamicTileSource from "./DynamicTileSource"
import { UpdatableDynamicTileSource } from "./DynamicTileSource"
import { Utils } from "../../../Utils"
import { BBox } from "../../BBox"
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"
import MvtSource from "../Sources/MvtSource"
import { Tiles } from "../../../Models/TileRange"
import Constants from "../../../Models/Constants"
import FeatureSourceMerger from "../Sources/FeatureSourceMerger"
import { UpdatableFeatureSourceMerger } from "../Sources/FeatureSourceMerger"
import { LineSourceMerger } from "./LineSourceMerger"
import { PolygonSourceMerger } from "./PolygonSourceMerger"
class PolygonMvtSource extends PolygonSourceMerger{
constructor( layer: LayerConfig,
mapProperties: {
zoom: Store<number>
bounds: Store<BBox>
},
options?: {
isActive?: Store<boolean>
}) {
const roundedZoom = mapProperties.zoom.mapD(z => Math.min(Math.floor(z/2)*2, 14))
super(
roundedZoom,
layer.minzoom,
(zxy) => {
const [z, x, y] = Tiles.tile_from_index(zxy)
const url = Utils.SubstituteKeys(Constants.VectorTileServer,
{
z, x, y, layer: layer.id,
type: "polygons",
})
return new MvtSource(url, x, y, z)
},
mapProperties,
{
isActive: options?.isActive,
})
}
}
class LineMvtSource extends LineSourceMerger{
constructor( layer: LayerConfig,
mapProperties: {
zoom: Store<number>
bounds: Store<BBox>
},
options?: {
isActive?: Store<boolean>
}) {
const roundedZoom = mapProperties.zoom.mapD(z => Math.min(Math.floor(z/2)*2, 14))
super(
roundedZoom,
layer.minzoom,
(zxy) => {
const [z, x, y] = Tiles.tile_from_index(zxy)
const url = Utils.SubstituteKeys(Constants.VectorTileServer,
{
z, x, y, layer: layer.id,
type: "lines",
})
return new MvtSource(url, x, y, z)
},
mapProperties,
{
isActive: options?.isActive,
})
}
}
class PointMvtSource extends DynamicTileSource {
class PolygonMvtSource extends PolygonSourceMerger {
constructor(
layer: LayerConfig,
mapProperties: {
@ -81,31 +19,32 @@ class PointMvtSource extends DynamicTileSource {
},
options?: {
isActive?: Store<boolean>
},
}
) {
const roundedZoom = mapProperties.zoom.mapD(z => Math.min(Math.floor(z/2)*2, 14))
const roundedZoom = mapProperties.zoom.mapD((z) => Math.min(Math.floor(z / 2) * 2, 14))
super(
roundedZoom,
layer.minzoom,
(zxy) => {
const [z, x, y] = Tiles.tile_from_index(zxy)
const url = Utils.SubstituteKeys(Constants.VectorTileServer,
{
z, x, y, layer: layer.id,
type: "pois",
})
const url = Utils.SubstituteKeys(Constants.VectorTileServer, {
z,
x,
y,
layer: layer.id,
type: "polygons",
})
return new MvtSource(url, x, y, z)
},
mapProperties,
{
isActive: options?.isActive,
},
}
)
}
}
export default class DynamicMvtileSource extends FeatureSourceMerger {
class LineMvtSource extends LineSourceMerger {
constructor(
layer: LayerConfig,
mapProperties: {
@ -114,13 +53,80 @@ export default class DynamicMvtileSource extends FeatureSourceMerger {
},
options?: {
isActive?: Store<boolean>
}
) {
const roundedZoom = mapProperties.zoom.mapD((z) => Math.min(Math.floor(z / 2) * 2, 14))
super(
roundedZoom,
layer.minzoom,
(zxy) => {
const [z, x, y] = Tiles.tile_from_index(zxy)
const url = Utils.SubstituteKeys(Constants.VectorTileServer, {
z,
x,
y,
layer: layer.id,
type: "lines",
})
return new MvtSource(url, x, y, z)
},
mapProperties,
{
isActive: options?.isActive,
}
)
}
}
class PointMvtSource extends UpdatableDynamicTileSource {
constructor(
layer: LayerConfig,
mapProperties: {
zoom: Store<number>
bounds: Store<BBox>
},
options?: {
isActive?: Store<boolean>
}
) {
const roundedZoom = mapProperties.zoom.mapD((z) => Math.min(Math.floor(z / 2) * 2, 14))
super(
roundedZoom,
layer.minzoom,
(zxy) => {
const [z, x, y] = Tiles.tile_from_index(zxy)
const url = Utils.SubstituteKeys(Constants.VectorTileServer, {
z,
x,
y,
layer: layer.id,
type: "pois",
})
return new MvtSource(url, x, y, z)
},
mapProperties,
{
isActive: options?.isActive,
}
)
}
}
export default class DynamicMvtileSource extends UpdatableFeatureSourceMerger {
constructor(
layer: LayerConfig,
mapProperties: {
zoom: Store<number>
bounds: Store<BBox>
},
options?: {
isActive?: Store<boolean>
}
) {
super(
new PointMvtSource(layer, mapProperties, options),
new LineMvtSource(layer, mapProperties, options),
new PolygonMvtSource(layer, mapProperties, options)
)
}
}

View file

@ -1,7 +1,7 @@
import { Store, Stores } from "../../UIEventSource"
import { Tiles } from "../../../Models/TileRange"
import { BBox } from "../../BBox"
import { FeatureSource } from "../FeatureSource"
import { FeatureSource, UpdatableFeatureSource } from "../FeatureSource"
import FeatureSourceMerger from "../Sources/FeatureSourceMerger"
/***
@ -11,6 +11,12 @@ import FeatureSourceMerger from "../Sources/FeatureSourceMerger"
export default class DynamicTileSource<
Src extends FeatureSource = FeatureSource
> extends FeatureSourceMerger<Src> {
private readonly loadedTiles = new Set<number>()
private readonly zDiff: number
private readonly zoomlevel: Store<number>
private readonly constructSource: (tileIndex: number) => Src
private readonly bounds: Store<BBox>
/**
*
* @param zoomlevel If {z} is specified in the source, the 'zoomlevel' will be used as zoomlevel to download from
@ -33,52 +39,86 @@ export default class DynamicTileSource<
}
) {
super()
const loadedTiles = new Set<number>()
const zDiff = options?.zDiff ?? 0
this.constructSource = constructSource
this.zoomlevel = zoomlevel
this.zDiff = options?.zDiff ?? 0
this.bounds = mapProperties.bounds
const neededTiles: Store<number[]> = Stores.ListStabilized(
mapProperties.bounds
.mapD(
(bounds) => {
if (options?.isActive && !options?.isActive.data) {
return undefined
}
.mapD(() => {
if (options?.isActive && !options?.isActive.data) {
return undefined
}
if (mapProperties.zoom.data < minzoom) {
return undefined
}
const z = Math.floor(zoomlevel.data) + zDiff
const tileRange = Tiles.TileRangeBetween(
z,
bounds.getNorth(),
bounds.getEast(),
bounds.getSouth(),
bounds.getWest()
)
if (tileRange.total > 500) {
console.warn(
"Got a really big tilerange, bounds and location might be out of sync"
)
return undefined
}
const needed = Tiles.MapRange(tileRange, (x, y) =>
Tiles.tile_index(z, x, y)
).filter((i) => !loadedTiles.has(i))
if (needed.length === 0) {
return undefined
}
return needed
},
[options?.isActive, mapProperties.zoom]
)
if (mapProperties.zoom.data < minzoom) {
return undefined
}
return this.getNeededTileIndices()
}, [options?.isActive, mapProperties.zoom])
.stabilized(250)
)
neededTiles.addCallbackAndRunD((neededIndexes) => {
for (const neededIndex of neededIndexes) {
loadedTiles.add(neededIndex)
super.addSource(constructSource(neededIndex))
}
})
neededTiles.addCallbackAndRunD((neededIndexes) => this.downloadTiles(neededIndexes))
}
protected downloadTiles(neededIndexes: number[]): Src[] {
const sources: Src[] = []
for (const neededIndex of neededIndexes) {
this.loadedTiles.add(neededIndex)
const src = this.constructSource(neededIndex)
super.addSource(src)
sources.push(src)
}
return sources
}
protected getNeededTileIndices() {
const bounds = this.bounds.data
const z = Math.floor(this.zoomlevel.data) + this.zDiff
const tileRange = Tiles.TileRangeBetween(
z,
bounds.getNorth(),
bounds.getEast(),
bounds.getSouth(),
bounds.getWest()
)
if (tileRange.total > 500) {
console.warn("Got a really big tilerange, bounds and location might be out of sync")
return []
}
const needed = Tiles.MapRange(tileRange, (x, y) => Tiles.tile_index(z, x, y)).filter(
(i) => !this.loadedTiles.has(i)
)
if (needed.length === 0) {
return []
}
return needed
}
}
export class UpdatableDynamicTileSource<Src extends UpdatableFeatureSource = UpdatableFeatureSource>
extends DynamicTileSource<Src>
implements UpdatableFeatureSource
{
constructor(
zoomlevel: Store<number>,
minzoom: number,
constructSource: (tileIndex: number) => Src,
mapProperties: {
bounds: Store<BBox>
zoom: Store<number>
},
options?: {
isActive?: Store<boolean>
zDiff?: number
}
) {
super(zoomlevel, minzoom, constructSource, mapProperties, options)
}
async updateAsync() {
const sources = super.downloadTiles(super.getNeededTileIndices())
await Promise.all(sources.map((src) => src.updateAsync()))
}
}

View file

@ -1,30 +1,31 @@
import { FeatureSourceForTile } from "../FeatureSource"
import { FeatureSourceForTile, UpdatableFeatureSource } from "../FeatureSource"
import { Store } from "../../UIEventSource"
import { BBox } from "../../BBox"
import { Utils } from "../../../Utils"
import { Feature, LineString, MultiLineString, Position } from "geojson"
import { Tiles } from "../../../Models/TileRange"
import { Feature, MultiLineString, Position } from "geojson"
import { GeoOperations } from "../../GeoOperations"
import DynamicTileSource from "./DynamicTileSource"
import { UpdatableDynamicTileSource } from "./DynamicTileSource"
/**
* The PolygonSourceMerger receives various small pieces of bigger polygons and stitches them together.
* This is used to reconstruct polygons of vector tiles
*/
export class LineSourceMerger extends DynamicTileSource<FeatureSourceForTile> {
export class LineSourceMerger extends UpdatableDynamicTileSource<
FeatureSourceForTile & UpdatableFeatureSource
> {
private readonly _zoomlevel: Store<number>
constructor(
zoomlevel: Store<number>,
minzoom: number,
constructSource: (tileIndex: number) => FeatureSourceForTile,
constructSource: (tileIndex: number) => FeatureSourceForTile & UpdatableFeatureSource,
mapProperties: {
bounds: Store<BBox>
zoom: Store<number>
},
options?: {
isActive?: Store<boolean>
},
}
) {
super(zoomlevel, minzoom, constructSource, mapProperties, options)
this._zoomlevel = zoomlevel
@ -35,33 +36,30 @@ export class LineSourceMerger extends DynamicTileSource<FeatureSourceForTile> {
const all: Map<string, Feature<MultiLineString>> = new Map()
const currentZoom = this._zoomlevel?.data ?? 0
for (const source of sources) {
if(source.z != currentZoom){
if (source.z != currentZoom) {
continue
}
const bboxCoors = Tiles.tile_bounds_lon_lat(source.z, source.x, source.y)
const bboxGeo = new BBox(bboxCoors).asGeoJson({})
for (const f of source.features.data) {
const id = f.properties.id
const coordinates : Position[][] = []
if(f.geometry.type === "LineString"){
const coordinates: Position[][] = []
if (f.geometry.type === "LineString") {
coordinates.push(f.geometry.coordinates)
}else if(f.geometry.type === "MultiLineString"){
} else if (f.geometry.type === "MultiLineString") {
coordinates.push(...f.geometry.coordinates)
}else {
} else {
console.error("Invalid geometry type:", f.geometry.type)
continue
}
const oldV = all.get(id)
if(!oldV){
all.set(id, {
type: "Feature",
properties: f.properties,
geometry:{
type:"MultiLineString",
coordinates
}
})
if (!oldV) {
all.set(id, {
type: "Feature",
properties: f.properties,
geometry: {
type: "MultiLineString",
coordinates,
},
})
continue
}
oldV.geometry.coordinates.push(...coordinates)
@ -70,11 +68,13 @@ export class LineSourceMerger extends DynamicTileSource<FeatureSourceForTile> {
const keys = Array.from(all.keys())
for (const key of keys) {
all.set(key, <any> GeoOperations.attemptLinearize(<Feature<MultiLineString>>all.get(key)))
all.set(
key,
<any>GeoOperations.attemptLinearize(<Feature<MultiLineString>>all.get(key))
)
}
const newList = Array.from(all.values())
this.features.setData(newList)
this._featuresById.setData(all)
}
}

View file

@ -1,27 +1,29 @@
import { FeatureSourceForTile } from "../FeatureSource"
import { FeatureSourceForTile, UpdatableFeatureSource } from "../FeatureSource"
import { Store } from "../../UIEventSource"
import { BBox } from "../../BBox"
import { Utils } from "../../../Utils"
import { Feature } from "geojson"
import { GeoOperations } from "../../GeoOperations"
import DynamicTileSource from "./DynamicTileSource"
import DynamicTileSource, { UpdatableDynamicTileSource } from "./DynamicTileSource"
/**
* The PolygonSourceMerger receives various small pieces of bigger polygons and stitches them together.
* This is used to reconstruct polygons of vector tiles
*/
export class PolygonSourceMerger extends DynamicTileSource<FeatureSourceForTile> {
export class PolygonSourceMerger extends UpdatableDynamicTileSource<
FeatureSourceForTile & UpdatableFeatureSource
> {
constructor(
zoomlevel: Store<number>,
minzoom: number,
constructSource: (tileIndex: number) => FeatureSourceForTile,
constructSource: (tileIndex: number) => FeatureSourceForTile & UpdatableFeatureSource,
mapProperties: {
bounds: Store<BBox>
zoom: Store<number>
},
options?: {
isActive?: Store<boolean>
},
}
) {
super(zoomlevel, minzoom, constructSource, mapProperties, options)
}
@ -69,5 +71,4 @@ export class PolygonSourceMerger extends DynamicTileSource<FeatureSourceForTile>
this.features.setData(newList)
this._featuresById.setData(all)
}
}

View file

@ -14,6 +14,10 @@ export class SummaryTileSourceRewriter implements FeatureSource {
private filteredLayers: FilteredLayer[]
public readonly features: Store<Feature[]> = this._features
private readonly _summarySource: SummaryTileSource
private readonly _totalNumberOfFeatures: UIEventSource<number> = new UIEventSource<number>(
undefined
)
public readonly totalNumberOfFeatures: Store<number> = this._totalNumberOfFeatures
constructor(
summarySource: SummaryTileSource,
filteredLayers: ReadonlyMap<string, FilteredLayer>
@ -31,6 +35,7 @@ export class SummaryTileSourceRewriter implements FeatureSource {
}
private update() {
let fullTotal = 0
const newFeatures: Feature[] = []
const layersToCount = this.filteredLayers.filter((fl) => fl.isDisplayed.data)
const bitmap = layersToCount.map((l) => (l.isDisplayed.data ? "1" : "0")).join("")
@ -42,10 +47,17 @@ export class SummaryTileSourceRewriter implements FeatureSource {
}
newFeatures.push({
...f,
properties: { ...f.properties, id: f.properties.id + bitmap, total: newTotal },
properties: {
...f.properties,
id: f.properties.id + bitmap,
total: newTotal,
total_metric: Utils.numberWithMetrixPrefix(newTotal),
},
})
fullTotal += newTotal
}
this._features.setData(newFeatures)
this._totalNumberOfFeatures.setData(fullTotal)
}
}
@ -94,7 +106,7 @@ export class SummaryTileSource extends DynamicTileSource {
}
const lat = counts["lat"]
const lon = counts["lon"]
const total = Utils.numberWithMetrixPrefix(Number(counts["total"]))
const total = Number(counts["total"])
const tileBbox = new BBox(Tiles.tile_bounds_lon_lat(z, x, y))
if (!tileBbox.contains([lon, lat])) {
console.error(
@ -116,6 +128,7 @@ export class SummaryTileSource extends DynamicTileSource {
summary: "yes",
...counts,
total,
total_metric: Utils.numberWithMetrixPrefix(total),
layers: layersSummed,
},
geometry: {