forked from MapComplete/MapComplete
		
	Merge master
This commit is contained in:
		
						commit
						5e5528eb7d
					
				
					 13 changed files with 160 additions and 126 deletions
				
			
		| 
						 | 
				
			
			@ -489,7 +489,7 @@ export class InitUiElements {
 | 
			
		|||
                        }
 | 
			
		||||
 | 
			
		||||
                        return true
 | 
			
		||||
                    }, [State.state.currentBounds]
 | 
			
		||||
                    }, [State.state.currentBounds, source.layer.isDisplayed]
 | 
			
		||||
                )
 | 
			
		||||
 | 
			
		||||
                new ShowDataLayer(
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -38,8 +38,7 @@ export default class OverpassFeatureSource implements FeatureSource {
 | 
			
		|||
        readonly currentBounds: UIEventSource<BBox>
 | 
			
		||||
    }
 | 
			
		||||
    private readonly _isActive: UIEventSource<boolean>;
 | 
			
		||||
    private readonly onBboxLoaded: (bbox: BBox, date: Date, layers: LayerConfig[]) => void;
 | 
			
		||||
 | 
			
		||||
    private readonly onBboxLoaded: (bbox: BBox, date: Date, layers: LayerConfig[], zoomlevel: number) => void;
 | 
			
		||||
    constructor(
 | 
			
		||||
        state: {
 | 
			
		||||
            readonly locationControl: UIEventSource<Loc>,
 | 
			
		||||
| 
						 | 
				
			
			@ -49,10 +48,11 @@ export default class OverpassFeatureSource implements FeatureSource {
 | 
			
		|||
            readonly overpassMaxZoom: UIEventSource<number>,
 | 
			
		||||
            readonly currentBounds: UIEventSource<BBox>
 | 
			
		||||
        },
 | 
			
		||||
        options?: {
 | 
			
		||||
        options: {
 | 
			
		||||
            padToTiles: UIEventSource<number>,
 | 
			
		||||
            isActive?: UIEventSource<boolean>,
 | 
			
		||||
            relationTracker: RelationsTracker,
 | 
			
		||||
            onBboxLoaded?: (bbox: BBox, date: Date, layers: LayerConfig[]) => void
 | 
			
		||||
            onBboxLoaded?: (bbox: BBox, date: Date, layers: LayerConfig[], zoomlevel: number) => void
 | 
			
		||||
        }) {
 | 
			
		||||
 | 
			
		||||
        this.state = state
 | 
			
		||||
| 
						 | 
				
			
			@ -61,7 +61,7 @@ export default class OverpassFeatureSource implements FeatureSource {
 | 
			
		|||
        this.relationsTracker = options.relationTracker
 | 
			
		||||
        const self = this;
 | 
			
		||||
        state.currentBounds.addCallback(_ => {
 | 
			
		||||
            self.update()
 | 
			
		||||
            self.update(options.padToTiles.data)
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -84,21 +84,21 @@ export default class OverpassFeatureSource implements FeatureSource {
 | 
			
		|||
        return new Overpass(new Or(filters), extraScripts, interpreterUrl, this.state.overpassTimeout, this.relationsTracker);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private update() {
 | 
			
		||||
    private update(paddedZoomLevel: number) {
 | 
			
		||||
        if (!this._isActive.data) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        const self = this;
 | 
			
		||||
        this.updateAsync().then(bboxDate => {
 | 
			
		||||
        this.updateAsync(paddedZoomLevel).then(bboxDate => {
 | 
			
		||||
            if(bboxDate === undefined || self.onBboxLoaded === undefined){
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            const [bbox, date, layers] = bboxDate
 | 
			
		||||
            self.onBboxLoaded(bbox, date, layers)
 | 
			
		||||
            self.onBboxLoaded(bbox, date, layers, paddedZoomLevel)
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private async updateAsync(): Promise<[BBox, Date, LayerConfig[]]> {
 | 
			
		||||
    private async updateAsync(padToZoomLevel: number): Promise<[BBox, Date, LayerConfig[]]> {
 | 
			
		||||
        if (this.runningQuery.data) {
 | 
			
		||||
            console.log("Still running a query, not updating");
 | 
			
		||||
            return undefined;
 | 
			
		||||
| 
						 | 
				
			
			@ -109,7 +109,7 @@ export default class OverpassFeatureSource implements FeatureSource {
 | 
			
		|||
            return undefined;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const bounds = this.state.currentBounds.data?.pad(this.state.layoutToUse.widenFactor)?.expandToTileBounds(14);
 | 
			
		||||
        const bounds = this.state.currentBounds.data?.pad(this.state.layoutToUse.widenFactor)?.expandToTileBounds(padToZoomLevel);
 | 
			
		||||
 | 
			
		||||
        if (bounds === undefined) {
 | 
			
		||||
            return undefined;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -116,16 +116,15 @@ export class BBox {
 | 
			
		|||
        return this.minLat
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pad(factor: number): BBox {
 | 
			
		||||
        const latDiff = this.maxLat - this.minLat
 | 
			
		||||
        const lat = (this.maxLat + this.minLat) / 2
 | 
			
		||||
        const lonDiff = this.maxLon - this.minLon
 | 
			
		||||
        const lon = (this.maxLon + this.minLon) / 2
 | 
			
		||||
    pad(factor: number, maxIncrease = 2): BBox {
 | 
			
		||||
        
 | 
			
		||||
        const latDiff = Math.min(maxIncrease / 2, Math.abs(this.maxLat - this.minLat) * factor)
 | 
			
		||||
        const lonDiff =Math.min(maxIncrease / 2, Math.abs(this.maxLon - this.minLon) * factor)
 | 
			
		||||
        return new BBox([[
 | 
			
		||||
            lon - lonDiff * factor,
 | 
			
		||||
            lat - latDiff * factor
 | 
			
		||||
        ], [lon + lonDiff * factor,
 | 
			
		||||
            lat + latDiff * factor]])
 | 
			
		||||
            this.minLon - lonDiff,
 | 
			
		||||
            this.minLat  - latDiff
 | 
			
		||||
        ], [this.maxLon + lonDiff,
 | 
			
		||||
            this.maxLat + latDiff]])
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    toLeaflet() {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -8,7 +8,7 @@ import SimpleMetaTagger from "../../SimpleMetaTagger";
 | 
			
		|||
 | 
			
		||||
export default class SaveTileToLocalStorageActor {
 | 
			
		||||
    public static readonly storageKey: string = "cached-features";
 | 
			
		||||
    public static readonly formatVersion: string = "1"
 | 
			
		||||
    public static readonly formatVersion: string = "2"
 | 
			
		||||
 | 
			
		||||
    constructor(source: FeatureSourceForLayer, tileIndex: number) {
 | 
			
		||||
        
 | 
			
		||||
| 
						 | 
				
			
			@ -37,6 +37,5 @@ export default class SaveTileToLocalStorageActor {
 | 
			
		|||
        }catch(e){
 | 
			
		||||
            console.error("Could not mark tile ", key, "as visited")
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -58,7 +58,7 @@ export default class FeaturePipeline {
 | 
			
		|||
    private readonly freshnesses = new Map<string, TileFreshnessCalculator>();
 | 
			
		||||
 | 
			
		||||
    private readonly oldestAllowedDate: Date = new Date(new Date().getTime() - 60 * 60 * 24 * 30 * 1000);
 | 
			
		||||
    private readonly osmSourceZoomLevel = 14
 | 
			
		||||
    private readonly osmSourceZoomLevel = 15
 | 
			
		||||
 | 
			
		||||
    constructor(
 | 
			
		||||
        handleFeatureSource: (source: FeatureSourceForLayer & Tiled) => void,
 | 
			
		||||
| 
						 | 
				
			
			@ -147,7 +147,7 @@ export default class FeaturePipeline {
 | 
			
		|||
                    // We split them up into tiles anyway as it is an OSM source
 | 
			
		||||
                    TiledFeatureSource.createHierarchy(src, {
 | 
			
		||||
                        layer: src.layer,
 | 
			
		||||
                        minZoomLevel: 14,
 | 
			
		||||
                        minZoomLevel: this.osmSourceZoomLevel,
 | 
			
		||||
                        dontEnforceMinZoom: true,
 | 
			
		||||
                        registerTile: (tile) => {
 | 
			
		||||
                            new RegisteringAllFromFeatureSourceActor(tile)
 | 
			
		||||
| 
						 | 
				
			
			@ -155,7 +155,7 @@ export default class FeaturePipeline {
 | 
			
		|||
                            tile.features.addCallbackAndRunD(_ => self.newDataLoadedSignal.setData(tile))
 | 
			
		||||
                        }
 | 
			
		||||
                    })
 | 
			
		||||
                }else{
 | 
			
		||||
                } else {
 | 
			
		||||
                    new RegisteringAllFromFeatureSourceActor(src)
 | 
			
		||||
                    perLayerHierarchy.get(id).registerTile(src)
 | 
			
		||||
                    src.features.addCallbackAndRunD(_ => self.newDataLoadedSignal.setData(src))
 | 
			
		||||
| 
						 | 
				
			
			@ -200,7 +200,7 @@ export default class FeaturePipeline {
 | 
			
		|||
        new PerLayerFeatureSourceSplitter(state.filteredLayers,
 | 
			
		||||
            (source) => TiledFeatureSource.createHierarchy(source, {
 | 
			
		||||
                layer: source.layer,
 | 
			
		||||
                minZoomLevel: 14,
 | 
			
		||||
                minZoomLevel: source.layer.layerDef.minzoom,
 | 
			
		||||
                dontEnforceMinZoom: true,
 | 
			
		||||
                maxFeatureCount: state.layoutToUse.clustering.minNeededElements,
 | 
			
		||||
                maxZoomLevel: state.layoutToUse.clustering.maxZoom,
 | 
			
		||||
| 
						 | 
				
			
			@ -235,7 +235,7 @@ export default class FeaturePipeline {
 | 
			
		|||
 | 
			
		||||
 | 
			
		||||
        // Whenever fresh data comes in, we need to update the metatagging
 | 
			
		||||
        self.newDataLoadedSignal.stabilized(1000).addCallback(src => {
 | 
			
		||||
        self.newDataLoadedSignal.stabilized(1000).addCallback(_ => {
 | 
			
		||||
            self.updateAllMetaTagging()
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -276,15 +276,15 @@ export default class FeaturePipeline {
 | 
			
		|||
        const self = this
 | 
			
		||||
        return this.state.currentBounds.map(bbox => {
 | 
			
		||||
            if (bbox === undefined) {
 | 
			
		||||
                return
 | 
			
		||||
                return undefined
 | 
			
		||||
            }
 | 
			
		||||
            if (!isSufficientlyZoomed.data) {
 | 
			
		||||
                return;
 | 
			
		||||
                return undefined;
 | 
			
		||||
            }
 | 
			
		||||
            const osmSourceZoomLevel = self.osmSourceZoomLevel
 | 
			
		||||
            const range = bbox.containingTileRange(osmSourceZoomLevel)
 | 
			
		||||
            const tileIndexes = []
 | 
			
		||||
            if (range.total > 100) {
 | 
			
		||||
            if (range.total >= 100) {
 | 
			
		||||
                // Too much tiles!
 | 
			
		||||
                return []
 | 
			
		||||
            }
 | 
			
		||||
| 
						 | 
				
			
			@ -294,7 +294,7 @@ export default class FeaturePipeline {
 | 
			
		|||
                if (oldestDate !== undefined && oldestDate > this.oldestAllowedDate) {
 | 
			
		||||
                    console.debug("Skipping tile", osmSourceZoomLevel, x, y, "as a decently fresh one is available")
 | 
			
		||||
                    // The cached tiles contain decently fresh data
 | 
			
		||||
                    return;
 | 
			
		||||
                    return undefined;
 | 
			
		||||
                }
 | 
			
		||||
                tileIndexes.push(i)
 | 
			
		||||
            })
 | 
			
		||||
| 
						 | 
				
			
			@ -327,28 +327,30 @@ export default class FeaturePipeline {
 | 
			
		|||
            }
 | 
			
		||||
 | 
			
		||||
            const range = bbox.containingTileRange(zoom)
 | 
			
		||||
            if (range.total > 100) {
 | 
			
		||||
            if (range.total >= 5000) {
 | 
			
		||||
                return false
 | 
			
		||||
            }
 | 
			
		||||
            const self = this;
 | 
			
		||||
            const allFreshnesses = Tiles.MapRange(range, (x, y) => self.freshnessForVisibleLayers(zoom, x, y))
 | 
			
		||||
            return allFreshnesses.some(freshness => freshness === undefined || freshness < this.oldestAllowedDate)
 | 
			
		||||
 | 
			
		||||
        }, [state.locationControl])
 | 
			
		||||
 | 
			
		||||
        const self = this;
 | 
			
		||||
        const updater = new OverpassFeatureSource(state,
 | 
			
		||||
            {
 | 
			
		||||
                padToTiles: state.locationControl.map(l => Math.min(15, l.zoom + 1)),
 | 
			
		||||
                relationTracker: this.relationTracker,
 | 
			
		||||
                isActive: useOsmApi.map(b => !b && overpassIsActive.data, [overpassIsActive]),
 | 
			
		||||
                onBboxLoaded: ((bbox, date, downloadedLayers) => {
 | 
			
		||||
                    Tiles.MapRange(bbox.containingTileRange(self.osmSourceZoomLevel), (x, y) => {
 | 
			
		||||
                onBboxLoaded: (bbox, date, downloadedLayers, paddedToZoomLevel) => {
 | 
			
		||||
                    Tiles.MapRange(bbox.containingTileRange(paddedToZoomLevel), (x, y) => {
 | 
			
		||||
                       const tileIndex =  Tiles.tile_index(paddedToZoomLevel, x, y)
 | 
			
		||||
                        downloadedLayers.forEach(layer => {
 | 
			
		||||
                            SaveTileToLocalStorageActor.MarkVisited(layer.id, Tiles.tile_index(this.osmSourceZoomLevel, x, y), date)
 | 
			
		||||
                            self.freshnesses.get(layer.id).addTileLoad(tileIndex, date)
 | 
			
		||||
                            SaveTileToLocalStorageActor.MarkVisited(layer.id, tileIndex, date)
 | 
			
		||||
                        })
 | 
			
		||||
                    })
 | 
			
		||||
 | 
			
		||||
                })
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,14 +1,16 @@
 | 
			
		|||
import FilteredLayer from "../../../Models/FilteredLayer";
 | 
			
		||||
import {FeatureSourceForLayer, Tiled} from "../FeatureSource";
 | 
			
		||||
import {UIEventSource} from "../../UIEventSource";
 | 
			
		||||
import Loc from "../../../Models/Loc";
 | 
			
		||||
import TileHierarchy from "./TileHierarchy";
 | 
			
		||||
import SaveTileToLocalStorageActor from "../Actors/SaveTileToLocalStorageActor";
 | 
			
		||||
import {Tiles} from "../../../Models/TileRange";
 | 
			
		||||
import {BBox} from "../../BBox";
 | 
			
		||||
 | 
			
		||||
export default class TiledFromLocalStorageSource implements TileHierarchy<FeatureSourceForLayer & Tiled> {
 | 
			
		||||
    public loadedTiles: Map<number, FeatureSourceForLayer & Tiled> = new Map<number, FeatureSourceForLayer & Tiled>();
 | 
			
		||||
    public readonly loadedTiles: Map<number, FeatureSourceForLayer & Tiled> = new Map<number, FeatureSourceForLayer & Tiled>();
 | 
			
		||||
    private readonly layer: FilteredLayer;
 | 
			
		||||
    private readonly handleFeatureSource: (src: FeatureSourceForLayer & Tiled, index: number) => void;
 | 
			
		||||
    private readonly undefinedTiles: Set<number>;
 | 
			
		||||
 | 
			
		||||
    public static GetFreshnesses(layerId: string): Map<number, Date> {
 | 
			
		||||
        const prefix = SaveTileToLocalStorageActor.storageKey + "-" + layerId + "-"
 | 
			
		||||
| 
						 | 
				
			
			@ -29,14 +31,15 @@ export default class TiledFromLocalStorageSource implements TileHierarchy<Featur
 | 
			
		|||
    constructor(layer: FilteredLayer,
 | 
			
		||||
                handleFeatureSource: (src: FeatureSourceForLayer & Tiled, index: number) => void,
 | 
			
		||||
                state: {
 | 
			
		||||
                    locationControl: UIEventSource<Loc>
 | 
			
		||||
                    leafletMap: any
 | 
			
		||||
                    currentBounds: UIEventSource<BBox>
 | 
			
		||||
                }) {
 | 
			
		||||
        this.layer = layer;
 | 
			
		||||
        this.handleFeatureSource = handleFeatureSource;
 | 
			
		||||
 | 
			
		||||
        const undefinedTiles = new Set<number>()
 | 
			
		||||
        
 | 
			
		||||
        this.undefinedTiles = new Set<number>()
 | 
			
		||||
        const prefix = SaveTileToLocalStorageActor.storageKey + "-" + layer.layerDef.id + "-"
 | 
			
		||||
        // @ts-ignore
 | 
			
		||||
        const indexes: number[] = Object.keys(localStorage)
 | 
			
		||||
        const knownTiles: number[] = Object.keys(localStorage)
 | 
			
		||||
            .filter(key => {
 | 
			
		||||
                return key.startsWith(prefix) && !key.endsWith("-time") && !key.endsWith("-format");
 | 
			
		||||
            })
 | 
			
		||||
| 
						 | 
				
			
			@ -45,8 +48,8 @@ export default class TiledFromLocalStorageSource implements TileHierarchy<Featur
 | 
			
		|||
            })
 | 
			
		||||
            .filter(i => !isNaN(i))
 | 
			
		||||
 | 
			
		||||
        console.debug("Layer", layer.layerDef.id, "has following tiles in available in localstorage", indexes.map(i => Tiles.tile_from_index(i).join("/")).join(", "))
 | 
			
		||||
        for (const index of indexes) {
 | 
			
		||||
        console.debug("Layer", layer.layerDef.id, "has following tiles in available in localstorage", knownTiles.map(i => Tiles.tile_from_index(i).join("/")).join(", "))
 | 
			
		||||
        for (const index of knownTiles) {
 | 
			
		||||
 | 
			
		||||
            const prefix = SaveTileToLocalStorageActor.storageKey + "-" + layer.layerDef.id + "-" + index;
 | 
			
		||||
            const version = localStorage.getItem(prefix + "-format")
 | 
			
		||||
| 
						 | 
				
			
			@ -55,78 +58,54 @@ export default class TiledFromLocalStorageSource implements TileHierarchy<Featur
 | 
			
		|||
                localStorage.removeItem(prefix)
 | 
			
		||||
                localStorage.removeItem(prefix+"-time")
 | 
			
		||||
                localStorage.removeItem(prefix+"-format")
 | 
			
		||||
                undefinedTiles.add(index)
 | 
			
		||||
              this.  undefinedTiles.add(index)
 | 
			
		||||
                console.log("Dropped old format tile", prefix)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const zLevels = indexes.map(i => i % 100)
 | 
			
		||||
        const indexesSet = new Set(indexes)
 | 
			
		||||
        const maxZoom = Math.max(...zLevels)
 | 
			
		||||
        const minZoom = Math.min(...zLevels)
 | 
			
		||||
        const self = this;
 | 
			
		||||
        const self = this
 | 
			
		||||
        state.currentBounds.map(bounds => {
 | 
			
		||||
 | 
			
		||||
        const neededTiles = state.locationControl.map(
 | 
			
		||||
            location => {
 | 
			
		||||
                if (!layer.isDisplayed.data) {
 | 
			
		||||
                    // No need to download! - the layer is disabled
 | 
			
		||||
                    return undefined;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (location.zoom < layer.layerDef.minzoom) {
 | 
			
		||||
                    // No need to download! - the layer is disabled
 | 
			
		||||
                    return undefined;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // Yup, this is cheating to just get the bounds here
 | 
			
		||||
                const bounds = state.leafletMap.data?.getBounds()
 | 
			
		||||
                if (bounds === undefined) {
 | 
			
		||||
                    // We'll retry later
 | 
			
		||||
                    return undefined
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                const needed = []
 | 
			
		||||
                for (let z = minZoom; z <= maxZoom; z++) {
 | 
			
		||||
 | 
			
		||||
                    const tileRange = Tiles.TileRangeBetween(z, bounds.getNorth(), bounds.getEast(), bounds.getSouth(), bounds.getWest())
 | 
			
		||||
                    const neededZ = Tiles.MapRange(tileRange, (x, y) => Tiles.tile_index(z, x, y))
 | 
			
		||||
                        .filter(i => !self.loadedTiles.has(i) && !undefinedTiles.has(i) && indexesSet.has(i))
 | 
			
		||||
                    needed.push(...neededZ)
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (needed.length === 0) {
 | 
			
		||||
                    return undefined
 | 
			
		||||
                }
 | 
			
		||||
                return needed
 | 
			
		||||
            if(bounds === undefined){
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            , [layer.isDisplayed, state.leafletMap]).stabilized(50);
 | 
			
		||||
            for (const knownTile of knownTiles) {
 | 
			
		||||
                
 | 
			
		||||
        neededTiles.addCallbackAndRunD(neededIndexes => {
 | 
			
		||||
            for (const neededIndex of neededIndexes) {
 | 
			
		||||
                // We load the features from localStorage
 | 
			
		||||
                try {
 | 
			
		||||
                    const key = SaveTileToLocalStorageActor.storageKey + "-" + layer.layerDef.id + "-" + neededIndex
 | 
			
		||||
                    const data = localStorage.getItem(key)
 | 
			
		||||
                    const features = JSON.parse(data)
 | 
			
		||||
                    const src = {
 | 
			
		||||
                        layer: layer,
 | 
			
		||||
                        features: new UIEventSource<{ feature: any; freshness: Date }[]>(features),
 | 
			
		||||
                        name: "FromLocalStorage(" + key + ")",
 | 
			
		||||
                        tileIndex: neededIndex,
 | 
			
		||||
                        bbox: BBox.fromTileIndex(neededIndex)
 | 
			
		||||
                    }
 | 
			
		||||
                    handleFeatureSource(src, neededIndex)
 | 
			
		||||
                    self.loadedTiles.set(neededIndex, src)
 | 
			
		||||
                } catch (e) {
 | 
			
		||||
                    console.error("Could not load data tile from local storage due to", e)
 | 
			
		||||
                    undefinedTiles.add(neededIndex)
 | 
			
		||||
                if(this.loadedTiles.has(knownTile)){
 | 
			
		||||
                    continue;
 | 
			
		||||
                }
 | 
			
		||||
                if(this.undefinedTiles.has(knownTile)){
 | 
			
		||||
                    continue;
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
                if(!bounds.overlapsWith(BBox.fromTileIndex(knownTile))){
 | 
			
		||||
                    continue;
 | 
			
		||||
                }
 | 
			
		||||
                self.loadTile(knownTile)
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    private loadTile( neededIndex: number){
 | 
			
		||||
        try {
 | 
			
		||||
            const key = SaveTileToLocalStorageActor.storageKey + "-" + this.layer.layerDef.id + "-" + neededIndex
 | 
			
		||||
            const data = localStorage.getItem(key)
 | 
			
		||||
            const features = JSON.parse(data)
 | 
			
		||||
            const src = {
 | 
			
		||||
                layer: this.layer,
 | 
			
		||||
                features: new UIEventSource<{ feature: any; freshness: Date }[]>(features),
 | 
			
		||||
                name: "FromLocalStorage(" + key + ")",
 | 
			
		||||
                tileIndex: neededIndex,
 | 
			
		||||
                bbox: BBox.fromTileIndex(neededIndex)
 | 
			
		||||
            }
 | 
			
		||||
            this.handleFeatureSource(src, neededIndex)
 | 
			
		||||
            this.loadedTiles.set(neededIndex, src)
 | 
			
		||||
        } catch (e) {
 | 
			
		||||
            console.error("Could not load data tile from local storage due to", e)
 | 
			
		||||
            this.undefinedTiles.add(neededIndex)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -2,7 +2,7 @@ import {Utils} from "../Utils";
 | 
			
		|||
 | 
			
		||||
export default class Constants {
 | 
			
		||||
 | 
			
		||||
    public static vNumber = "0.10.3";
 | 
			
		||||
    public static vNumber = "0.10.5";
 | 
			
		||||
    public static ImgurApiKey = '7070e7167f0a25a'
 | 
			
		||||
    public static readonly mapillary_client_token_v3 = 'TXhLaWthQ1d4RUg0czVxaTVoRjFJZzowNDczNjUzNmIyNTQyYzI2'
 | 
			
		||||
    public static readonly mapillary_client_token_v4 = "MLY|4441509239301885|b40ad2d3ea105435bd40c7e76993ae85"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -92,7 +92,7 @@ export default class LayoutConfig {
 | 
			
		|||
                throw "Widenfactor too small"
 | 
			
		||||
            }else{
 | 
			
		||||
                // Unofficial themes get away with this
 | 
			
		||||
                console.warn("Detected a very small widenfactor, bumping this above 1.")
 | 
			
		||||
                console.warn("Detected a very small widenfactor for theme ", this.id ,", bumping this above 1.")
 | 
			
		||||
                json.widenFactor = json.widenFactor + 1
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -15,7 +15,7 @@ export class Tiles {
 | 
			
		|||
    public static MapRange<T>(tileRange: TileRange, f: (x: number, y: number) => T): T[] {
 | 
			
		||||
        const result: T[] = []
 | 
			
		||||
        const total = tileRange.total
 | 
			
		||||
        if(total > 5000){
 | 
			
		||||
        if(total > 100000){
 | 
			
		||||
            throw "Tilerange too big"
 | 
			
		||||
        }
 | 
			
		||||
        for (let x = tileRange.xstart; x <= tileRange.xend; x++) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -50,13 +50,11 @@ export default class ThemeIntroductionPanel extends Combine {
 | 
			
		|||
            )
 | 
			
		||||
 | 
			
		||||
        super([
 | 
			
		||||
            layout.description.Clone(),
 | 
			
		||||
            "<br/><br/>",
 | 
			
		||||
            layout.description.Clone().SetClass("blcok mb-4"),
 | 
			
		||||
            toTheMap,
 | 
			
		||||
            loginStatus,
 | 
			
		||||
            layout.descriptionTail?.Clone(),
 | 
			
		||||
            "<br/>",
 | 
			
		||||
            languagePicker,
 | 
			
		||||
            loginStatus.SetClass("block"),
 | 
			
		||||
            layout.descriptionTail?.Clone().SetClass("block mt-4"),
 | 
			
		||||
            languagePicker.SetClass("block mt-4"),
 | 
			
		||||
            ...layout.CustomCodeSnippets()
 | 
			
		||||
        ])
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -14,7 +14,10 @@
 | 
			
		|||
    "nl": "Een kaart om toeristisch relevante info op aan te duiden"
 | 
			
		||||
  },
 | 
			
		||||
  "description": {
 | 
			
		||||
    "nl": "Op deze kaart kan je info zien die relevant is voor toerisme, zoals:<br/><ul><li>Eetgelegenheden</li><li>Cafés en bars</li><li>(Fiets)oplaadpunten</li><li>Fietspompen, fietserverhuur en fietswinkels</li><li>Uitkijktorens</li><li>...</li></ul> Zie je fouten op de kaart? Dan kan je zelf makkelijk aanpasingen maken, die zichtbaar zijn voor iedereen. Hiervoor dien je een gratis OpenStreetMap account voor te maken.<br/><br/>Met de steun van Toerisme Vlaanderen<img src='./assets/themes/toerisme_vlaanderen/logo.png' />"
 | 
			
		||||
    "nl": "Op deze kaart kan je info zien die relevant is voor toerisme, zoals:<br/><ul><li>Eetgelegenheden</li><li>Cafés en bars</li><li>(Fiets)oplaadpunten</li><li>Fietspompen, fietserverhuur en fietswinkels</li><li>Uitkijktorens</li><li>...</li></ul> Zie je fouten op de kaart? Dan kan je zelf makkelijk aanpasingen maken, die zichtbaar zijn voor iedereen. Hiervoor dien je een gratis OpenStreetMap account voor te maken."
 | 
			
		||||
  },
 | 
			
		||||
  "descriptionTail": {
 | 
			
		||||
    "nl": "Met de steun van Toerisme Vlaanderen<img style='height:5rem; width: auto;' src='./assets/themes/toerisme_vlaanderen/logo.png' />"
 | 
			
		||||
  },
 | 
			
		||||
  "icon": "./assets/svg/star.svg",
 | 
			
		||||
  "startZoom": 8,
 | 
			
		||||
| 
						 | 
				
			
			@ -28,16 +31,38 @@
 | 
			
		|||
        "cafe_pub"
 | 
			
		||||
      ],
 | 
			
		||||
      "override": {
 | 
			
		||||
        "minzoom": 16
 | 
			
		||||
        "minzoom": 17
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "charging_station",
 | 
			
		||||
    "toilet",
 | 
			
		||||
    "bench",
 | 
			
		||||
    "waste_basket",
 | 
			
		||||
    "bike_repair_station",
 | 
			
		||||
    "binocular",
 | 
			
		||||
    "observation_tower"
 | 
			
		||||
    {
 | 
			
		||||
      "builtin": [
 | 
			
		||||
        "bench",
 | 
			
		||||
        "picnic_table",
 | 
			
		||||
        "waste_basket"
 | 
			
		||||
      ],
 | 
			
		||||
      "override": {
 | 
			
		||||
        "minzoom": 19
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "builtin": [
 | 
			
		||||
        "charging_station",
 | 
			
		||||
        "toilet",
 | 
			
		||||
        "bike_repair_station"
 | 
			
		||||
      ],
 | 
			
		||||
      "override": {
 | 
			
		||||
        "minzoom": 14
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "builtin": [
 | 
			
		||||
        "binocular",
 | 
			
		||||
        "observation_tower"
 | 
			
		||||
      ],
 | 
			
		||||
      "override": {
 | 
			
		||||
        "minzoom": 10
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  ],
 | 
			
		||||
  "hideFromOverview": true
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -20,5 +20,20 @@
 | 
			
		|||
        "uploadingMultiple": "A enviar {count} imagens…",
 | 
			
		||||
        "uploadingPicture": "A enviar a sua imagem…",
 | 
			
		||||
        "addPicture": "Adicionar imagem"
 | 
			
		||||
    },
 | 
			
		||||
    "index": {
 | 
			
		||||
        "#": "Estes textos são mostrados acima dos botões do tema quando nenhum tema é carregado",
 | 
			
		||||
        "title": "Bem-vindo(a) ao MapComplete",
 | 
			
		||||
        "intro": "O MapComplete é um visualizador e editor do OpenStreetMap, que mostra informações sobre um tema específico.",
 | 
			
		||||
        "pickTheme": "Escolha um tema abaixo para começar."
 | 
			
		||||
    },
 | 
			
		||||
    "delete": {
 | 
			
		||||
        "reasons": {
 | 
			
		||||
            "notFound": "Não foi possível encontrar este elemento"
 | 
			
		||||
        },
 | 
			
		||||
        "explanations": {
 | 
			
		||||
            "selectReason": "Por favor, selecione a razão porque este elemento deve ser eliminado",
 | 
			
		||||
            "hardDelete": "Este ponto será eliminado no OpenStreetMap. Pode ser recuperado por um contribuidor com experiência"
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -61,6 +61,23 @@
 | 
			
		|||
                }
 | 
			
		||||
            },
 | 
			
		||||
            "question": "Este lugar é acessível a utilizadores de cadeiras de rodas?"
 | 
			
		||||
        },
 | 
			
		||||
        "dog-access": {
 | 
			
		||||
            "mappings": {
 | 
			
		||||
                "0": {
 | 
			
		||||
                    "then": "Os cães são permitidos"
 | 
			
		||||
                },
 | 
			
		||||
                "1": {
 | 
			
		||||
                    "then": "Os cães <b>não</b> são permitidos"
 | 
			
		||||
                },
 | 
			
		||||
                "2": {
 | 
			
		||||
                    "then": "Os cães são permitidos, mas têm de ser presos pela trela"
 | 
			
		||||
                },
 | 
			
		||||
                "3": {
 | 
			
		||||
                    "then": "Os cães são permitidos e podem correr livremente"
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            "question": "Os cães são permitidos neste estabelecimento?"
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue