Merge branch 'develop' into feature/move-flox

This commit is contained in:
Pieter Vander Vennet 2021-10-13 01:29:29 +02:00
commit 11d5ccf93f
35 changed files with 773 additions and 243 deletions

View file

@ -26,12 +26,16 @@ The following values are always calculated, by default, by MapComplete and are a
The latitude and longitude of the point (or centerpoint in the case of a way/area)
### _surface, _surface:ha
The surface area of the feature, in square meters and in hectare. Not set on points and ways
This is a lazy metatag and is only calculated when needed
### _length, _length:km
@ -40,6 +44,8 @@ The surface area of the feature, in square meters and in hectare. Not set on poi
The total length of a feature in meters (and in kilometers, rounded to one decimal for '_length:km'). For a surface, the length of the perimeter
### Theme-defined keys
@ -47,6 +53,8 @@ The total length of a feature in meters (and in kilometers, rounded to one decim
If 'units' is defined in the layoutConfig, then this metatagger will rewrite the specified keys to have the canonical form (e.g. `1meter` will be rewritten to `1m`)
### _country
@ -54,18 +62,15 @@ If 'units' is defined in the layoutConfig, then this metatagger will rewrite the
The country code of the property (with latlon2country)
### _isOpen, _isOpen:description
If 'opening_hours' is present, it will add the current state of the feature (being 'yes' or 'no')
### _width:needed, _width:needed:no_pedestrians, _width:difference
Legacy for a specific project calculating the needed width for safe traffic on a road. Only activated if 'width:carriageway' is present
This is a lazy metatag and is only calculated when needed
### _direction:numerical, _direction:leftright
@ -75,6 +80,8 @@ Legacy for a specific project calculating the needed width for safe traffic on a
_direction:numerical is a normalized, numerical direction based on 'camera:direction' or on 'direction'; it is only present if a valid direction is found (e.g. 38.5 or NE). _direction:leftright is either 'left' or 'right', which is left-looking on the map or 'right-looking' on the map
### _now:date, _now:datetime, _loaded:date, _loaded:_datetime
@ -82,6 +89,8 @@ _direction:numerical is a normalized, numerical direction based on 'camera:direc
Adds the time that the data got loaded - pretty much the time of downloading from overpass. The format is YYYY-MM-DD hh:mm, aka 'sortable' aka ISO-8601-but-not-entirely
### _last_edit:contributor, _last_edit:contributor:uid, _last_edit:changeset, _last_edit:timestamp, _version_number
@ -89,6 +98,8 @@ Adds the time that the data got loaded - pretty much the time of downloading fro
Information about the last edit of this object.
Calculating tags with Javascript
----------------------------------
@ -140,15 +151,15 @@ Some advanced functions are available on **feat** as well:
- distanceTo
- overlapWith
- closest
- closestn
- memberships
- score
### distanceTo
Calculates the distance between the feature and a specified point in kilometer. The input should either be a pair of coordinates, a geojson feature or the ID of an object
0. longitude
1. latitude
0. feature OR featureID OR longitude
1. undefined OR latitude
### overlapWith
@ -160,10 +171,21 @@ For example to get all objects which overlap or embed from a layer, use `_contai
### closest
Given either a list of geojson features or a single layer name, gives the single object which is nearest to the feature. In the case of ways/polygons, only the centerpoint is considered.
Given either a list of geojson features or a single layer name, gives the single object which is nearest to the feature. In the case of ways/polygons, only the centerpoint is considered. Returns a single geojson feature or undefined if nothing is found (or not yet laoded)
0. list of features
### closestn
Given either a list of geojson features or a single layer name, gives the n closest objects which are nearest to the feature (excluding the feature itself). In the case of ways/polygons, only the centerpoint is considered. Returns a list of `{feat: geojson, distance:number}` the empty list if nothing is found (or not yet loaded)
If a 'unique tag key' is given, the tag with this key will only appear once (e.g. if 'name' is given, all features will have a different name)
0. list of features or layer name
1. amount of features
2. unique tag key (optional)
3. maxDistanceInMeters (optional)
### memberships
Gives a list of `{role: string, relation: Relation}`-objects, containing all the relations that this feature is part of.
@ -171,12 +193,4 @@ For example to get all objects which overlap or embed from a layer, use `_contai
For example: `_part_of_walking_routes=feat.memberships().map(r => r.relation.tags.name).join(';')`
### score
Given the path of an aspected routing json file, will calculate the score. This score is wrapped in a UIEventSource, so for further calculations, use `.map(score => ...)`
For example: `_comfort_score=feat.score('https://raw.githubusercontent.com/pietervdvn/AspectedRouting/master/Examples/bicycle/aspects/bicycle.comfort.json')`
0. path
Generated from SimpleMetaTagger, ExtraFunction

View file

@ -5,11 +5,6 @@
### all_tags
Prints all key-value pairs of the object - used for debugging
name | default | description
------ | --------- | -------------
#### Example usage
`{all_tags()}`
@ -20,11 +15,10 @@ name | default | description
name | default | description
------ | --------- | -------------
image key/prefix | image | The keys given to the images, e.g. if <span class='literal-code'>image</span> is given, the first picture URL will be added as <span class='literal-code'>image</span>, the second as <span class='literal-code'>image:0</span>, the third as <span class='literal-code'>image:1</span>, etc...
smart search | true | Also include images given via 'Wikidata', 'wikimedia_commons' and 'mapillary
#### Example usage
`{image_carousel(image,true)}`
`{image_carousel(image)}`
### image_upload
Creates a button where a user can upload an image to IMGUR
@ -36,6 +30,17 @@ image-key | image | Image tag to add the URL to (or image-tag:0, image-tag:1 whe
#### Example usage
`{image_upload(image)}`
### wikipedia
A box showing the corresponding wikipedia article - based on the wikidata tag
name | default | description
------ | --------- | -------------
keyToShowWikipediaFor | wikidata | Use the wikidata entry from this key to show the wikipedia article for
#### Example usage
`{wikipedia()}` is a basic example, `{wikipedia(name:etymology:wikidata)}` to show the wikipedia page of whom the feature was named after. Also remember that these can be styled, e.g. `{wikipedia():max-height: 10rem}` to limit the height
### minimap
A small map showing the selected feature. Note that no styling is applied, wrap this in a div
@ -128,16 +133,25 @@ If you want to import a dataset, make sure that:
1. The dataset to import has a suitable license
2. The community has been informed of the import
3. The theme will filter out duplicate nodes4. All other requirements of the [import guidelines](https://wiki.openstreetmap.org/wiki/Import/Guidelines) have been followed
3. All other requirements of the [import guidelines](https://wiki.openstreetmap.org/wiki/Import/Guidelines) have been followed
There are also some technicalities in your theme to keep in mind:
1. The new point will be added and will flow through the program as any other new point as if it came from OSM.
This means that there should be a layer which will match the new tags and which will display it.
2. The original point from your geojson layer will gain the tag '_imported=yes'.
This should be used to change the appearance or even to hide it (eg by changing the icon size to zero)
3. There should be a way for the theme to detect previously imported points, even after reloading.
A reference number to the original dataset is an excellen way to do this
name | default | description
------ | --------- | -------------
tags | undefined | Tags to copy-specification. This contains one or more pairs (seperated by a `;`), e.g. `amenity=fast_food; addr:housenumber={number}`. This new point will then have the tags `amenity=fast_food` and `addr:housenumber` with the value that was saved in `number` in the original feature. (Hint: prepare these values, e.g. with calculatedTags)
tags | undefined | Tags to copy-specification. This contains one or more pairs (seperated by a `;`), e.g. `amenity=fast_food; addr:housenumber=$number`. This new point will then have the tags `amenity=fast_food` and `addr:housenumber` with the value that was saved in `number` in the original feature. (Hint: prepare these values, e.g. with calculatedTags)
text | Import this data into OpenStreetMap | The text to show on the button
icon | ./assets/svg/addSmall.svg | A nice icon to show in the button
minzoom | 18 | How far the contributor must zoom in before being able to import the point
#### Example usage
`{import_button(,Import this data into OpenStreetMap,./assets/svg/addSmall.svg)}`
Generated from UI/SpecialVisualisations.ts
`{import_button(,Import this data into OpenStreetMap,./assets/svg/addSmall.svg,18)}` Generated from UI/SpecialVisualisations.ts

View file

@ -161,7 +161,7 @@ Finally, the URL-hash is the part after the `#`. It is `node/1234` in this case.
overpassUrl
-------------
Point mapcomplete to a different overpass-instance. Example: https://overpass-api.de/api/interpreter The default value is _https://overpass-api.de/api/interpreter_
Point mapcomplete to a different overpass-instance. Example: https://overpass-api.de/api/interpreter The default value is _https://overpass-api.de/api/interpreter,https://overpass.kumi.systems/api/interpreter,https://overpass.openstreetmap.ru/cgi/interpreter_
overpassTimeout

View file

@ -426,10 +426,10 @@ export class InitUiElements {
const clustering = State.state.layoutToUse.clustering
const clusterCounter = TileHierarchyAggregator.createHierarchy()
new ShowDataLayer({
features: clusterCounter.getCountsForZoom(State.state.locationControl, State.state.layoutToUse.clustering.minNeededElements),
features: clusterCounter.getCountsForZoom(clustering, State.state.locationControl, State.state.layoutToUse.clustering.minNeededElements),
leafletMap: State.state.leafletMap,
layerToShow: ShowTileInfo.styling,
enablePopups: false
@ -440,7 +440,7 @@ export class InitUiElements {
clusterCounter.addTile(source)
const clustering = State.state.layoutToUse.clustering
// Do show features indicates if the 'showDataLayer' should be shown
const doShowFeatures = source.features.map(
f => {
const z = State.state.locationControl.data.zoom
@ -449,12 +449,24 @@ export class InitUiElements {
return false;
}
const bounds = State.state.currentBounds.data
if(bounds === undefined){
// Map is not yet displayed
return false;
}
if (!source.bbox.overlapsWith(bounds)) {
// Not within range -> features are hidden
return false
}
if (z < source.layer.layerDef.minzoom) {
// Layer is always hidden for this zoom level
return false;
}
if (z >= clustering.maxZoom) {
if (z > clustering.maxZoom) {
return true
}
@ -473,23 +485,15 @@ export class InitUiElements {
}
if (clusterCounter.getTile(Tiles.tile_index(tileZ, tileX, tileY))?.totalValue > clustering.minNeededElements) {
// To much elements
return false
}
}
const bounds = State.state.currentBounds.data
if(bounds === undefined){
// Map is not yet displayed
return false;
}
if (!source.bbox.overlapsWith(bounds)) {
// Not within range
return false
}
return true
}, [State.state.currentBounds]
}, [State.state.currentBounds, source.layer.isDisplayed]
)
new ShowDataLayer(

View file

@ -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;

View file

@ -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() {

View file

@ -4,11 +4,10 @@
* Technically, more an Actor then a featuresource, but it fits more neatly this ay
*/
import {FeatureSourceForLayer} from "../FeatureSource";
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 +36,5 @@ export default class SaveTileToLocalStorageActor {
}catch(e){
console.error("Could not mark tile ", key, "as visited")
}
}
}

View file

@ -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)
})
})
})
}
});

View file

@ -21,7 +21,9 @@ export default class DynamicGeoJsonTileSource extends DynamicTileSource {
throw "Invalid layer: geojsonSource expected"
}
const whitelistUrl = source.geojsonSource.replace("{z}_{x}_{y}.geojson", "overview.json")
const whitelistUrl = source.geojsonSource
.replace("{z}", ""+source.geojsonZoomLevel)
.replace("{x}_{y}.geojson", "overview.json")
.replace("{layer}",layer.layerDef.id)
let whitelist = undefined
@ -46,7 +48,8 @@ export default class DynamicGeoJsonTileSource extends DynamicTileSource {
if(whitelist !== undefined){
const isWhiteListed = whitelist.get(zxy[1])?.has(zxy[2])
if(!isWhiteListed){
return undefined;
console.log("Not whitelisted:",zxy, isWhiteListed, whitelist)
// return undefined;
}
}

View file

@ -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)
}
}
}

View file

@ -209,7 +209,7 @@ export default class SimpleMetaTagger {
configurable: true,
get: () => {
delete feature.properties._isOpen
feature.properties._isOpen = ""
feature.properties._isOpen = undefined
const tagsSource = State.state.allElements.getEventSourceById(feature.properties.id);
tagsSource.addCallbackAndRunD(tags => {
if (tags.opening_hours === undefined || tags._country === undefined) {
@ -230,7 +230,8 @@ export default class SimpleMetaTagger {
const oldNextChange = tags["_isOpen:nextTrigger"] ?? 0;
if (oldNextChange > (new Date()).getTime() &&
tags["_isOpen:oldvalue"] === tags["opening_hours"]) {
tags["_isOpen:oldvalue"] === tags["opening_hours"]
&& tags["_isOpen"] !== undefined) {
// Already calculated and should not yet be triggered
return false;
}
@ -267,7 +268,7 @@ export default class SimpleMetaTagger {
}
})
return feature.properties["_isOpen"]
return undefined
}
})

View file

@ -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"

View file

@ -1,6 +1,5 @@
import {TagRenderingConfigJson} from "./TagRenderingConfigJson";
import {LayerConfigJson} from "./LayerConfigJson";
import UnitConfigJson from "./UnitConfigJson";
/**
* Defines the entire theme.
@ -253,7 +252,7 @@ export interface LayoutConfigJson {
* If set to [[lat0, lon0], [lat1, lon1]], the map will not scroll outside of those bounds.
* Off by default, which will enable panning to the entire world
*/
lockLocation?: boolean | [[number, number], [number, number]];
lockLocation?: boolean | [[number, number], [number, number]] | number[][];
enableUserBadge?: boolean;
enableShareScreen?: boolean;

View file

@ -30,7 +30,7 @@ export default class LayoutConfig {
public layers: LayerConfig[];
public readonly clustering?: {
maxZoom: number,
minNeededElements: number
minNeededElements: number,
};
public readonly hideFromOverview: boolean;
public lockLocation: boolean | [[number, number], [number, number]];
@ -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
}
}
@ -139,12 +139,17 @@ export default class LayoutConfig {
this.clustering = {
maxZoom: 16,
minNeededElements: 25
minNeededElements: 25,
};
if (json.clustering) {
if(json.clustering === false){
this.clustering = {
maxZoom: 0,
minNeededElements: 100000,
};
}else if (json.clustering) {
this.clustering = {
maxZoom: json.clustering.maxZoom ?? 18,
minNeededElements: json.clustering.minNeededElements ?? 25
minNeededElements: json.clustering.minNeededElements ?? 25,
}
}
@ -153,7 +158,7 @@ export default class LayoutConfig {
if (json.hideInOverview) {
throw "The json for " + this.id + " contains a 'hideInOverview'. Did you mean hideFromOverview instead?"
}
this.lockLocation = json.lockLocation ?? undefined;
this.lockLocation = <[[number, number], [number, number]]> json.lockLocation ?? undefined;
this.enableUserBadge = json.enableUserBadge ?? true;
this.enableShareScreen = json.enableShareScreen ?? true;
this.enableMoreQuests = json.enableMoreQuests ?? true;

View file

@ -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++) {

View file

@ -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()
])

View file

@ -22,7 +22,7 @@ export class TileHierarchyAggregator implements FeatureSource {
public readonly name;
private readonly featuresStatic = []
private readonly featureProperties: { count: string, tileId: string, id: string };
private readonly featureProperties: { count: string, kilocount: string, tileId: string, id: string };
private constructor(parent: TileHierarchyAggregator, z: number, x: number, y: number) {
this._parent = parent;
@ -36,7 +36,8 @@ export class TileHierarchyAggregator implements FeatureSource {
const totals = {
id: ""+this._tileIndex,
tileId: ""+this._tileIndex,
count: ""+0
count: `0`,
kilocount: "0"
}
this.featureProperties = totals
@ -108,6 +109,7 @@ export class TileHierarchyAggregator implements FeatureSource {
this.features.setData(TileHierarchyAggregator.empty)
} else {
this.featureProperties.count = "" + total;
this.featureProperties.kilocount = "" +Math.floor(total/1000);
this.features.data = this.featuresStatic
this.features.ping()
}
@ -153,13 +155,18 @@ export class TileHierarchyAggregator implements FeatureSource {
}
}
getCountsForZoom(locationControl: UIEventSource<{ zoom : number }>, cutoff: number = 0) : FeatureSource{
getCountsForZoom(clusteringConfig: {maxZoom: number}, locationControl: UIEventSource<{ zoom : number }>, cutoff: number = 0) : FeatureSource{
const self = this
const empty = []
return new StaticFeatureSource(
locationControl.map(loc => {
const features = []
const targetZoom = loc.zoom
if(targetZoom > clusteringConfig.maxZoom){
return empty
}
const features = []
self.visitSubTiles(aggr => {
if(aggr.totalValue < cutoff) {
return false

View file

@ -55,7 +55,11 @@ export default class SpecialVisualizations {
if (!tags.hasOwnProperty(key)) {
continue
}
parts.push([key, tags[key] ?? "<b>undefined</b>"]);
let v = tags[key]
if(v === ""){
v = "<b>empty string</b>"
}
parts.push([key, v ?? "<b>undefined</b>"]);
}
for(const key of calculatedTags){

View file

@ -78,18 +78,6 @@ export class Utils {
return res;
}
static DoEvery(millis: number, f: (() => void)) {
if (Utils.runningFromConsole) {
return;
}
window.setTimeout(
function () {
f();
Utils.DoEvery(millis, f);
}
, millis)
}
public static NoNull<T>(array: T[]): T[] {
const ls: T[] = [];
for (const t of array) {
@ -440,5 +428,19 @@ export class Utils {
window.setTimeout(resolve, timeMillis);
})
}
public static toHumanTime(seconds): string{
seconds = Math.floor(seconds)
let minutes = Math.floor(seconds / 60)
seconds = seconds % 60
let hours = Math.floor(minutes / 60)
minutes = minutes % 60
let days = Math.floor(hours / 24)
hours = hours % 24
if(days > 0){
return days+"days"+" "+hours+"h"
}
return hours+":"+Utils.TwoDigits(minutes)+":"+Utils.TwoDigits(seconds)
}
}

View file

@ -28,8 +28,8 @@
"render": "<div class='rounded-full text-xl font-bold' style='width: 2rem; height: 2rem; background: white'>{count}</div>",
"mappings": [
{
"if": "count>99",
"then": "<div class='rounded-full text-xl font-bold flex flex-col' style='width: 2.5rem; height: 2.5rem; background: white'>&gt;99</div>"
"if": "count>1000",
"then": "<div class='rounded-full text-xl font-bold flex flex-col' style='width: 2.5rem; height: 2.5rem; background: white'>{kilocount}K</div>"
}
]
}

View file

@ -156,35 +156,40 @@
"dog-access": {
"question": {
"en": "Are dogs allowed in this business?",
"nl": "Zijn honden toegelaten in deze zaak?"
"nl": "Zijn honden toegelaten in deze zaak?",
"pt": "Os cães são permitidos neste estabelecimento?"
},
"mappings": [
{
"if": "dog=yes",
"then": {
"en": "Dogs are allowed",
"nl": "honden zijn toegelaten"
"nl": "honden zijn toegelaten",
"pt": "Os cães são permitidos"
}
},
{
"if": "dog=no",
"then": {
"en": "Dogs are <b>not</b> allowed",
"nl": "honden zijn <b>niet</b> toegelaten"
"nl": "honden zijn <b>niet</b> toegelaten",
"pt": "Os cães <b>não</b> são permitidos"
}
},
{
"if": "dog=leashed",
"then": {
"en": "Dogs are allowed, but they have to be leashed",
"nl": "honden zijn <b>enkel aan de leiband</b> welkom"
"nl": "honden zijn <b>enkel aan de leiband</b> welkom",
"pt": "Os cães são permitidos, mas têm de ser presos pela trela"
}
},
{
"if": "dog=unleashed",
"then": {
"en": "Dogs are allowed and can run around freely",
"nl": "honden zijn welkom en mogen vrij rondlopen"
"nl": "honden zijn welkom en mogen vrij rondlopen",
"pt": "Os cães são permitidos e podem correr livremente"
}
}
]
@ -247,7 +252,8 @@
"en": "Which methods of payment are accepted here?",
"nl": "Welke betaalmiddelen worden hier geaccepteerd?",
"pt": "Que métodos de pagamento são aceites aqui?",
"pt_BR": "Quais métodos de pagamento são aceitos aqui?"
"pt_BR": "Quais métodos de pagamento são aceitos aqui?",
"id": "Metode pembayaran manakah yang di terima disini?"
},
"multiAnswer": true,
"mappings": [
@ -258,7 +264,8 @@
"en": "Cash is accepted here",
"nl": "Cash geld wordt hier aanvaard",
"pt": "Aceitam pagamento com dinheiro aqui",
"pt_BR": "Dinheiro é aceito aqui"
"pt_BR": "Dinheiro é aceito aqui",
"id": "Disini menerima pembayaran tunai"
}
},
{
@ -268,7 +275,8 @@
"en": "Payment cards are accepted here",
"nl": "Betalen met bankkaarten kan hier",
"pt": "Aceitam pagamento com cartões bancários aqui",
"pt_BR": "Cartões de pagamento são aceitos aqui"
"pt_BR": "Cartões de pagamento são aceitos aqui",
"id": "Disini menerima pembayaran dengan kartu"
}
}
]
@ -292,7 +300,8 @@
"pl": "Na jakim poziomie znajduje się ta funkcja?",
"pt_BR": "Em que nível esse recurso está localizado?",
"ru": "На каком этаже находится этот объект?",
"pt": "Em que nível se encontra este elemento?"
"pt": "Em que nível se encontra este elemento?",
"id": "Pada tingkat apa fitur ini diletakkan?"
},
"render": {
"en": "Located on the {level}th floor",
@ -369,7 +378,8 @@
"fr": "Premier étage",
"pl": "Znajduje się na pierwszym piętrze",
"sv": "Ligger på första våningen",
"pt": "Está no primeiro andar"
"pt": "Está no primeiro andar",
"id": "Berlokasi di lantai pertama"
}
}
]

View file

@ -32,8 +32,8 @@
"enablePdfDownload": true,
"enableDownload": true,
"hideFromOverview": true,
"clustering": {
"#": "Disable clustering for this theme",
"clustering": {
"maxZoom": 0
},
"layers": [
@ -112,7 +112,7 @@
"geoJsonZoomLevel": 12,
"isOsmCache": true
},
"minzoom": "13",
"minzoom": 10,
"icon": {
"render": "circle:#FE6F32;./assets/themes/natuurpunt/trail.svg",
"mappings": [

View file

@ -0,0 +1,25 @@
[
{
"path": "post_office.svg",
"license": "CC BY-SA 4.0",
"authors": [
"https://github.com/emojione/emojione/graphs/contributors"
],
"sources": [
"https://commons.wikimedia.org/wiki/File:Emojione_BW_1F3E4.svg",
"https://github.com/emojione/emojione/blob/2.2.7/assets/svg_bw/1f3e4.svg"
]
},
{
"path": "postbox.svg",
"license": "CC BY 4.0",
"authors": [
"Vincent Le Moign",
"https://twitter.com/webalys"
],
"sources": [
"https://upload.wikimedia.org/wikipedia/commons/6/6d/726-postbox.svg",
"http://emoji.streamlineicons.com"
]
}
]

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" enable-background="new 0 0 64 64"><path d="m6.219 22.625h3.75v5.625h-3.75z"/><path d="m11.844 22.625h3.75v5.625h-3.75z"/><path d="m20.281 22.625h3.75v5.625h-3.75z"/><path d="m25.906 22.625h3.75v5.625h-3.75z"/><path d="m34.344 22.625h3.75v5.625h-3.75z"/><path d="m39.969 22.625h3.75v5.625h-3.75z"/><path d="m48.406 22.625h3.75v5.625h-3.75z"/><path d="m54.031 22.625h3.75v5.625h-3.75z"/><path d="m6.219 33.875h3.75v5.625h-3.75z"/><path d="m11.844 33.875h3.75v5.625h-3.75z"/><path d="m20.281 33.875h3.75v5.625h-3.75z"/><path d="m25.906 33.875h3.75v5.625h-3.75z"/><path d="m34.344 33.875h3.75v5.625h-3.75z"/><path d="m39.969 33.875h3.75v5.625h-3.75z"/><path d="m48.406 33.875h3.75v5.625h-3.75z"/><path d="m54.031 33.875h3.75v5.625h-3.75z"/><path d="m16.063 48.875h5.625v5.625h-5.625z"/><path d="m27.313 47h3.75v4.688h-3.75z"/><path d="m42.313 48.875h5.625v5.625h-5.625z"/><path d="m49.813 48.875h5.625v5.625h-5.625z"/><path d="m8.563 48.875h5.625v5.625h-5.625z"/><path d="m32.938 47h3.75v4.688h-3.75z"/><path d="m30.146 11.078c.163.041.365.054.642.054 1.018 0 1.646-.553 1.646-1.481 0-.835-.54-1.333-1.496-1.333-.389 0-.651.04-.791.081v2.679z"/><path d="m37.529 11.401c0 1.778.778 3.031 2.061 3.031 1.295 0 2.035-1.32 2.035-3.085 0-1.63-.728-3.03-2.048-3.03-1.295 0-2.048 1.319-2.048 3.084"/><path d="m62 17.938v-13.125c0-.516-.422-.938-.938-.938h-.938v-1.875h-56.249v1.875h-.937c-.516 0-.938.422-.938.938v13.125c0 .516.422.938.938.938v39.374c-.516 0-.938.422-.938.938v1.875c0 .515.422.937.938.937h58.125c.515 0 .937-.422.937-.937v-1.875c0-.516-.422-.938-.938-.938v-39.375c.516 0 .938-.422.938-.937m-57.187 12.187v-9.375h54.375v9.375h-54.375m54.375 1.875v9.375h-54.375v-9.375h54.375m-18.75 26.25v-11.25h-1.875v11.25h-1.875v-4.688h-3.75v4.688h-1.875v-4.688h-3.75v4.688h-1.875v-11.25h-1.875v11.25h-1.875v-1.875h-15v-9.375h15v-3.75h20.625v3.75h15v9.375h-15v1.875h-1.875m11.303-49.69v-1.725h6.509v1.725h-2.312v7.354h-1.922v-7.354h-2.275m-4.38 3.542c-1.407-.525-2.323-1.361-2.323-2.68 0-1.549 1.206-2.735 3.203-2.735.954 0 1.658.215 2.161.458l-.427 1.657c-.339-.176-.943-.431-1.771-.431-.831 0-1.231.404-1.231.875 0 .579.478.835 1.57 1.28 1.496.592 2.199 1.428 2.199 2.707 0 1.522-1.095 2.815-3.418 2.815-.967 0-1.923-.269-2.399-.554l.389-1.697c.517.284 1.308.567 2.124.567.879 0 1.344-.392 1.344-.983-.001-.566-.402-.888-1.421-1.279m-3.714-.821c0 2.976-1.683 4.782-4.158 4.782-2.513 0-3.982-2.034-3.982-4.621 0-2.721 1.621-4.754 4.12-4.754 2.602 0 4.02 2.087 4.02 4.593m-9.316-1.684c0 .889-.276 1.643-.779 2.155-.652.66-1.62.957-2.751.957-.252 0-.479-.015-.654-.041v3.246h-1.897v-8.957c.591-.107 1.419-.188 2.587-.188 1.183 0 2.023.243 2.59.728.54.457.904 1.211.904 2.1m-10.934-1.927h1.103c0 5.152-4.197 9.33-9.375 9.33s-9.375-4.178-9.375-9.33h4.413c0 .95.205 1.85.567 2.666.184-2.563 2.324-4.586 4.945-4.586 2.743 0 4.965 2.211 4.965 4.938 0 1.078-.352 2.072-.94 2.885 2.189-1.074 3.697-3.311 3.697-5.903"/><path d="m15.676 7.395c-1.823 0-3.309 1.476-3.309 3.293 0 1.819 1.485 3.293 3.309 3.293 1.827 0 3.31-1.474 3.31-3.293-.001-1.817-1.483-3.293-3.31-3.293"/></svg>

After

Width:  |  Height:  |  Size: 3 KiB

View file

@ -0,0 +1,143 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 21.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Icons" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 48 48" style="enable-background:new 0 0 48 48;" xml:space="preserve">
<style type="text/css">
.st0{opacity:0.15;fill:#45413C;}
.st1{fill:#BF8256;}
.st2{fill:#915E3A;}
.st3{fill:none;stroke:#45413C;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
.st4{fill:#DEA47A;}
.st5{fill:#DAEDF7;stroke:#45413C;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
.st6{fill:#BF8256;stroke:#45413C;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
.st7{fill:#FF6242;}
.st8{fill:#FF866E;}
.st9{fill:#DEBB7E;}
.st10{fill:#B89558;}
.st11{fill:#656769;}
.st12{fill:#525252;}
.st13{fill:#E04122;}
.st14{fill:#FFFFFF;}
.st15{fill:#F0F0F0;}
.st16{fill:#00B8F0;}
.st17{fill:#4ACFFF;}
.st18{fill:#C0DCEB;}
.st19{fill:#8CA4B8;}
.st20{fill:#FF6242;stroke:#45413C;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
.st21{fill:#87898C;}
.st22{fill:#E0E0E0;}
.st23{fill:#E8F4FA;stroke:#45413C;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
.st24{fill:#656769;stroke:#45413C;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
.st25{fill:#DAEDF7;}
.st26{fill:#E8F4FA;}
.st27{fill:#ADC4D9;stroke:#45413C;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
.st28{fill:#87898C;stroke:#45413C;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
.st29{fill:#BDBEC0;}
.st30{fill:#FFFFFF;stroke:#45413C;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
.st31{fill:#ADC4D9;}
.st32{fill:none;stroke:#00AED9;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
.st33{fill:#FFFACF;}
.st34{fill:#FFE500;}
.st35{fill:#915E3A;stroke:#45413C;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
.st36{fill:#FFAA54;}
.st37{fill:#627B8C;stroke:#45413C;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
.st38{fill:#FFFEF2;}
.st39{fill:#FFFCE5;stroke:#45413C;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
.st40{fill:#00F5BC;}
.st41{fill:#FFFCE5;}
.st42{fill:#FFFEF2;stroke:#45413C;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
.st43{fill:#8CFFE4;}
.st44{fill:#FFF5E3;}
.st45{fill:#F7E5C6;}
.st46{fill:#F7E5C6;stroke:#45413C;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
.st47{fill:#F0D5A8;}
.st48{fill:#FF87AF;stroke:#45413C;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
.st49{fill:#45413C;}
.st50{fill:#BDBEC0;stroke:#45413C;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
.st51{fill:#E0E0E0;stroke:#45413C;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
.st52{fill:#DEBB7E;stroke:#45413C;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
.st53{fill:#F5EBFF;stroke:#45413C;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
.st54{fill:#BF8DF2;stroke:#45413C;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
.st55{fill:#E4FFD1;stroke:#45413C;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
.st56{fill:#F0FFE5;}
.st57{fill:#C8FFA1;stroke:#45413C;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
.st58{fill:#6DD627;stroke:#45413C;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
.st59{fill:#E5FEFF;}
.st60{fill:#FFAA54;stroke:#45413C;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
.st61{fill:#9CEB60;stroke:#45413C;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
.st62{fill:#FFF48C;}
.st63{fill:#00B8F0;stroke:#45413C;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
.st64{fill:none;stroke:#4F4B45;stroke-linejoin:round;stroke-miterlimit:10;}
.st65{fill:#FFFFFF;stroke:#4F4B45;stroke-linejoin:round;stroke-miterlimit:10;}
.st66{fill:#FFF5E3;stroke:#4F4B45;stroke-linejoin:round;stroke-miterlimit:10;}
.st67{fill:#F7E5C6;stroke:#4F4B45;stroke-linejoin:round;stroke-miterlimit:10;}
.st68{fill:#6DD627;}
.st69{fill:#EBCB00;}
.st70{fill:#46B000;}
.st71{fill:none;stroke:#E0E0E0;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
.st72{fill:#9CEB60;}
.st73{fill:#FFCC99;}
.st74{fill:#BF8DF2;}
.st75{fill:#9F5AE5;}
.st76{fill:#DABFF5;}
.st77{fill:#F0F0F0;stroke:#45413C;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
.st78{fill:#E5F8FF;}
.st79{fill:#B8ECFF;}
.st80{fill:#D9FDFF;}
.st81{fill:#C0DCEB;stroke:#45413C;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
.st82{fill:#46B000;stroke:#45413C;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
.st83{fill:none;stroke:#FFFFFF;stroke-miterlimit:10;}
.st84{fill:none;stroke:#FFFFFF;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
.st85{fill:#FFE500;stroke:#45413C;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
.st86{fill:#80DDFF;stroke:#45413C;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
.st87{fill:#FFFFFF;stroke:#45413C;stroke-linejoin:round;stroke-miterlimit:10;}
.st88{fill:#009FD9;stroke:#45413C;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
.st89{fill:#E04122;stroke:#45413C;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
.st90{fill:#009FD9;}
.st91{fill:#FFFFFF;stroke:#45413C;stroke-miterlimit:10;}
.st92{fill:#009FD9;stroke:#45413C;stroke-miterlimit:10;}
.st93{fill:#FF8A14;stroke:#45413C;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
.st94{fill:none;stroke:#009FD9;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
.st95{fill:#525252;stroke:#45413C;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
.st96{fill:none;stroke:#46B000;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
.st97{fill:none;stroke:#E04122;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
.st98{fill:#6DD627;stroke:#45413C;stroke-miterlimit:10;}
.st99{fill:none;stroke:#45413C;stroke-linejoin:round;stroke-miterlimit:10;}
.st100{fill:#46B000;stroke:#45413C;stroke-miterlimit:10;}
.st101{fill:#00DFEB;}
.st102{fill:#00AD85;}
.st103{fill:#E04122;stroke:#45413C;stroke-miterlimit:10;}
</style>
<g id="XMLID_15623_">
<ellipse id="XMLID_15640_" class="st0" cx="24" cy="44.4" rx="12.3" ry="1.6"/>
<path id="XMLID_15639_" class="st7" d="M34,33.9H14c-1.8,0-3.2-1.4-3.2-3.2V6.1c0-1.8,1.4-3.2,3.2-3.2h20c1.8,0,3.2,1.4,3.2,3.2
v24.6C37.2,32.5,35.8,33.9,34,33.9z"/>
<path id="XMLID_15638_" class="st8" d="M34,2.9H14c-1.8,0-3.2,1.4-3.2,3.2v2.5c0-1.8,1.4-3.2,3.2-3.2h20c1.8,0,3.2,1.4,3.2,3.2V6.1
C37.2,4.3,35.8,2.9,34,2.9z"/>
<path id="XMLID_15637_" class="st3" d="M34,33.9H14c-1.8,0-3.2-1.4-3.2-3.2V6.1c0-1.8,1.4-3.2,3.2-3.2h20c1.8,0,3.2,1.4,3.2,3.2
v24.6C37.2,32.5,35.8,33.9,34,33.9z"/>
<path id="XMLID_15636_" class="st9" d="M26.2,33.9h-4.5v9.5c0,0.6,0.5,1.1,1.1,1.1h2.3c0.6,0,1.1-0.5,1.1-1.1V33.9z"/>
<rect id="XMLID_15635_" x="21.8" y="33.9" class="st10" width="4.5" height="2.7"/>
<path id="XMLID_15634_" class="st3" d="M26.2,33.9h-4.5v9.5c0,0.6,0.5,1.1,1.1,1.1h2.3c0.6,0,1.1-0.5,1.1-1.1V33.9z"/>
<path id="XMLID_15633_" class="st11" d="M32.5,14.5h-17c-0.6,0-1.1-0.5-1.1-1.1v-1.3c0-0.6,0.5-1.1,1.1-1.1h17
c0.6,0,1.1,0.5,1.1,1.1v1.3C33.6,14.1,33.1,14.5,32.5,14.5z"/>
<path id="XMLID_15632_" class="st12" d="M32.5,11.1h-17c-0.6,0-1.1,0.5-1.1,1.1v1.3c0-0.6,0.5-1.1,1.1-1.1h17
c0.6,0,1.1,0.5,1.1,1.1v-1.3C33.6,11.5,33.1,11.1,32.5,11.1z"/>
<path id="XMLID_15631_" class="st3" d="M32.5,14.5h-17c-0.6,0-1.1-0.5-1.1-1.1v-1.3c0-0.6,0.5-1.1,1.1-1.1h17
c0.6,0,1.1,0.5,1.1,1.1v1.3C33.6,14.1,33.1,14.5,32.5,14.5z"/>
<path class="st13" d="M33.9,6.6H14.1c-0.6,0-1.1,0.5-1.1,1.1c0,0.6,0.5,1.1,1.1,1.1h19.8c0.6,0,1.1-0.5,1.1-1.1
C35,7.1,34.5,6.6,33.9,6.6z"/>
<path id="XMLID_15630_" class="st7" d="M14.1,7.7h19.8c0.4,0,0.8,0.2,1,0.6C35,8.1,35,7.9,35,7.7c0-0.6-0.5-1.1-1.1-1.1H14.1
c-0.6,0-1.1,0.5-1.1,1.1c0,0.2,0.1,0.4,0.1,0.6C13.3,7.9,13.7,7.7,14.1,7.7z"/>
<path class="st3" d="M33.9,6.6H14.1c-0.6,0-1.1,0.5-1.1,1.1c0,0.6,0.5,1.1,1.1,1.1h19.8c0.6,0,1.1-0.5,1.1-1.1
C35,7.1,34.5,6.6,33.9,6.6z"/>
<path id="XMLID_15629_" class="st14" d="M33.6,21.2h-9.8c-0.4,0-0.8-0.2-1-0.6l-4.3-9.5h11.3l4.2,9.3
C34.2,20.8,33.9,21.2,33.6,21.2z"/>
<polygon id="XMLID_15628_" class="st15" points="30.9,13.3 29.8,11.1 18.5,11.1 19.6,13.3 "/>
<path id="XMLID_15627_" class="st3" d="M33.6,21.2h-9.8c-0.4,0-0.8-0.2-1-0.6l-4.3-9.5h11.3l4.2,9.3C34.2,20.8,33.9,21.2,33.6,21.2
z"/>
<polyline id="XMLID_15626_" class="st3" points="34,20.9 26.5,14.7 27.3,11.1 "/>
<line id="XMLID_15625_" class="st3" x1="27.6" y1="15.7" x2="23.2" y2="21"/>
<line id="XMLID_15624_" class="st3" x1="26.7" y1="13.7" x2="21.5" y2="11.1"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 8.8 KiB

View file

@ -0,0 +1,174 @@
{
"id": "postboxes",
"title": {
"en": "Postbox and Post Office Map"
},
"shortDescription": {
"en": "A map showing postboxes and post offices"
},
"description": {
"en": "On this map you can find and add data of post offices and post boxes. You can use this map to find where you can mail your next postcard! :)<br/>Spotted an error or is a post box missing? You can edit this map with a free OpenStreetMap account. "
},
"language": [
"en"
],
"maintainer": "",
"icon": "./assets/themes/postboxes/postbox.svg",
"version": "0",
"startLat": 53.5511,
"startLon": 9.9937,
"startZoom": 13,
"widenFactor": 1.5,
"defaultBackgroundId": "CartoDB.Voyager",
"clustering": {
"maxZoom": 14,
"minNeededElements": 100
},
"layers": [
{
"id": "postboxes",
"name": {
"en": "Postboxes"
},
"minzoom": 12,
"source": {
"osmTags": "amenity=post_box"
},
"title": {
"render": {
"en": "Postbox"
}
},
"description": {
"en": "The layer showing postboxes."
},
"tagRenderings": [
"images",
{
"id": "minimap",
"render": "{minimap(18): height: 5rem; overflow: hidden; border-radius:3rem; }"
}
],
"icon": {
"render": "./assets/themes/postboxes/postbox.svg"
},
"width": {
"render": "1"
},
"iconSize": {
"render": "40,40,bottom"
},
"color": {
"render": "#DADADA"
},
"presets": [
{
"tags": [
"amenity=post_box"
],
"title": {
"en": "postbox"
}
}
],
"wayHandling": 2,
"deletion": {
"softDeletionTags": {
"and": [
"amenity=",
"razed:amenity=post_box"
]
}
}
},
{
"id": "postoffices",
"name": {
"en": "Post offices"
},
"minzoom": 12,
"source": {
"osmTags": "amenity=post_office"
},
"title": {
"render": {
"en": "Post Office"
}
},
"description": {
"en": "A layer showing post offices."
},
"tagRenderings": [
"images",
{
"id": "minimap",
"render": "{minimap(18): height: 5rem; overflow: hidden; border-radius:3rem; }"
},
{
"render": {
"en": "Opening Hours: {opening_hours_table()}"
},
"freeform": {
"key": "opening_hours",
"type": "opening_hours"
},
"question": {
"en": "What are the opening hours for this post office?"
},
"mappings": [
{
"if": "opening_hours=24/7",
"then": {
"en": "24/7 opened (including holidays)"
}
}
],
"id": "OH"
}
],
"icon": {
"render": "square:white;./assets/themes/postboxes/post_office.svg"
},
"iconOverlays": [
{
"if": "opening_hours~*",
"then": "isOpen",
"badge": true
}
],
"width": {
"render": "1"
},
"iconSize": {
"render": "40,40,bottom"
},
"color": {
"render": "#DADADA"
},
"presets": [
{
"tags": [
"amenity=post_office"
],
"title": {
"en": "Post Office"
}
}
],
"wayHandling": 2,
"filter": [
{
"id": "is_open",
"options": [
{
"question": {
"en": "Currently open"
},
"osmTags": "_isOpen=yes"
}
]
}
]
}
]
}

View file

@ -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
}

View file

@ -63,7 +63,6 @@ if (path !== "index.html" && path !== "") {
defaultLayout = QueryParameters.GetQueryParameter("layout", defaultLayout, "The layout to load into MapComplete").data;
let layoutToUse: LayoutConfig = AllKnownLayouts.allKnownLayouts.get(defaultLayout.toLowerCase());
const userLayoutParam = QueryParameters.GetQueryParameter("userlayout", "false", "If not 'false', a custom (non-official) theme is loaded. This custom layout can be done in multiple ways: \n\n- The hash of the URL contains a base64-encoded .json-file containing the theme definition\n- The hash of the URL contains a lz-compressed .json-file, as generated by the custom theme generator\n- The parameter itself is an URL, in which case that URL will be downloaded. It should point to a .json of a theme");
// Workaround/legacy to keep the old paramters working as I renamed some of them

View file

@ -36,16 +36,6 @@
"splitTitle": "Choose on the map where to split this road",
"hasBeenSplit": "This way has been split"
},
"move": {
"loginToMove": "You must be logged in to move a point",
"inviteToMove": "Move this point",
"selectReason": "Why do you move this object?",
"reasonRelocation": "The object has been relocated to a totally different location",
"reasonInaccurate": "The location of this object is inaccurate and should be moved a few meter",
"onlyPoints": "Only points can be moved",
"isWay": "This feature is a way",
"cancel": "Cancel move"
},
"delete": {
"delete": "Delete",
"cancel": "Cancel",
@ -130,7 +120,7 @@
},
"morescreen": {
"intro": "<h3>More thematic maps?</h3>Do you enjoy collecting geodata? <br/>There are more themes available.",
"requestATheme": "If you want a custom-built quest, request it in the issue tracker",
"requestATheme": "If you want a custom-built theme, request it in the issue tracker",
"streetcomplete": "Another, similar application is <a href='https://play.google.com/store/apps/details?id=de.westnordost.streetcomplete' class='underline hover:text-blue-800' class='underline hover:text-blue-800' target='_blank'>StreetComplete</a>.",
"createYourOwnTheme": "Create your own MapComplete theme from scratch"
},
@ -231,7 +221,7 @@
},
"wikipedia": {
"wikipediaboxTitle": "Wikipedia",
"failed":"Loading the wikipedia entry failed",
"failed": "Loading the wikipedia entry failed",
"loading": "Loading Wikipedia...",
"noWikipediaPage": "This wikidata item has no corresponding wikipedia page yet."
}
@ -256,5 +246,15 @@
"tos": "If you create a review, you agree to <a href='https://mangrove.reviews/terms' target='_blank'>the TOS and privacy policy of Mangrove.reviews</a>",
"attribution": "Reviews are powered by <a href='https://mangrove.reviews/' target='_blank'>Mangrove Reviews</a> and are available under <a href='https://mangrove.reviews/terms#8-licensing-of-content' target='_blank'>CC-BY 4.0</a>.",
"plz_login": "Login to leave a review"
},
"move": {
"loginToMove": "You must be logged in to move a point",
"inviteToMove": "Move this point",
"selectReason": "Why do you move this object?",
"reasonRelocation": "The object has been relocated to a totally different location",
"reasonInaccurate": "The location of this object is inaccurate and should be moved a few meter",
"onlyPoints": "Only points can be moved",
"isWay": "This feature is a way",
"cancel": "Cancel move"
}
}

View file

@ -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"
}
}
}

View file

@ -3,6 +3,25 @@
"email": {
"question": "Apa alamat surel dari {name}?"
},
"level": {
"mappings": {
"3": {
"then": "Berlokasi di lantai pertama"
}
},
"question": "Pada tingkat apa fitur ini diletakkan?"
},
"payment-options": {
"mappings": {
"0": {
"then": "Disini menerima pembayaran tunai"
},
"1": {
"then": "Disini menerima pembayaran dengan kartu"
}
},
"question": "Metode pembayaran manakah yang di terima disini?"
},
"phone": {
"question": "Nomor telepon dari {name|?"
},

View file

@ -3,6 +3,23 @@
"description": {
"question": "Ainda há algo de relevante que não tenha podido dar nas perguntas anteriores? Adicione-o aqui.<br/><span style='font-size: small'>Não repita factos já declarados</span>"
},
"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?"
},
"email": {
"question": "Qual é o endereço de e-mail de {name}?"
},

View file

@ -1371,5 +1371,56 @@
"description": "On this map, you'll find waste baskets near you. If a waste basket is missing on this map, you can add it yourself",
"shortDescription": "A map with waste baskets",
"title": "Waste Basket"
},
"postboxes": {
"description": "On this map you can find and add data of post offices and post boxes. You can use this map to find where you can mail your next postcard! :)<br/>Spotted an error or is a post box missing? You can edit this map with a free OpenStreetMap account. ",
"layers": {
"0": {
"description": "The layer showing postboxes.",
"name": "Postboxes",
"presets": {
"0": {
"title": "postbox"
}
},
"title": {
"render": "Postbox"
}
},
"1": {
"description": "A layer showing post offices.",
"filter": {
"0": {
"options": {
"0": {
"question": "Currently open"
}
}
}
},
"name": "Post offices",
"presets": {
"0": {
"title": "Post Office"
}
},
"tagRenderings": {
"OH": {
"mappings": {
"0": {
"then": "24/7 opened (including holidays)"
}
},
"question": "What are the opening hours for this post office?",
"render": "Opening Hours: {opening_hours_table()}"
}
},
"title": {
"render": "Post Office"
}
}
},
"shortDescription": "A map showing postboxes and post offices",
"title": "Postbox and Post Office Map"
}
}

View file

@ -1025,6 +1025,7 @@
},
"toerisme_vlaanderen": {
"description": "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' />",
"descriptionTail": "Met de steun van Toerisme Vlaanderen<img style='height:5rem; width: auto;' src='./assets/themes/toerisme_vlaanderen/logo.png' />",
"shortDescription": "Een kaart om toeristisch relevante info op aan te duiden",
"title": "Toeristisch relevante info"
},

View file

@ -20,6 +20,7 @@ import FeatureSource, {FeatureSourceForLayer} from "../Logic/FeatureSource/Featu
import StaticFeatureSource from "../Logic/FeatureSource/Sources/StaticFeatureSource";
import TiledFeatureSource from "../Logic/FeatureSource/TiledFeatureSource/TiledFeatureSource";
import Constants from "../Models/Constants";
import {GeoOperations} from "../Logic/GeoOperations";
ScriptUtils.fixUtils()
@ -73,6 +74,7 @@ async function downloadRaw(targetdir: string, r: TileRange, theme: LayoutConfig,
let downloaded = 0
let failed = 0
let skipped = 0
const startTime = new Date().getTime()
for (let x = r.xstart; x <= r.xend; x++) {
for (let y = r.ystart; y <= r.yend; y++) {
downloaded++;
@ -82,7 +84,11 @@ async function downloadRaw(targetdir: string, r: TileRange, theme: LayoutConfig,
skipped++
continue;
}
console.log("x:", (x - r.xstart), "/", (r.xend - r.xstart), "; y:", (y - r.ystart), "/", (r.yend - r.ystart), "; total: ", downloaded, "/", r.total, "failed: ", failed, "skipped: ", skipped)
const runningSeconds = (new Date().getTime() - startTime) / 1000
const resting = failed + (r.total - downloaded)
const perTile= (runningSeconds / (downloaded - skipped))
const estimated =Math.floor(resting * perTile)
console.log("total: ", downloaded, "/", r.total, "failed: ", failed, "skipped: ", skipped, "running time: ",Utils.toHumanTime(runningSeconds)+"s", "estimated left: ", Utils.toHumanTime(estimated), "("+Math.floor(perTile)+"s/tile)")
const boundsArr = Tiles.tile_bounds(r.zoomlevel, x, y)
const bounds = {
@ -91,7 +97,7 @@ async function downloadRaw(targetdir: string, r: TileRange, theme: LayoutConfig,
east: Math.max(boundsArr[0][1], boundsArr[1][1]),
west: Math.min(boundsArr[0][1], boundsArr[1][1])
}
const overpass = createOverpassObject(theme, relationTracker, Constants.defaultOverpassUrls[(downloaded + failed) % Constants.defaultOverpassUrls.length])
const overpass = createOverpassObject(theme, relationTracker, Constants.defaultOverpassUrls[(failed) % Constants.defaultOverpassUrls.length])
const url = overpass.buildQuery("[bbox:" + bounds.south + "," + bounds.west + "," + bounds.north + "," + bounds.east + "]")
try {
@ -170,11 +176,11 @@ function loadAllTiles(targetdir: string, r: TileRange, theme: LayoutConfig, extr
/**
* Load all the tiles into memory from disk
*/
function postProcess(allFeatures: FeatureSource, theme: LayoutConfig, relationsTracker: RelationsTracker, targetdir: string) {
function sliceToTiles(allFeatures: FeatureSource, theme: LayoutConfig, relationsTracker: RelationsTracker, targetdir: string, pointsOnlyLayers: string[]) {
function handleLayer(source: FeatureSourceForLayer) {
const layer = source.layer.layerDef;
const targetZoomLevel = layer.source.geojsonZoomLevel ?? 0
const layerId = layer.id
if (layer.source.isOsmCacheLayer !== true) {
return;
@ -200,13 +206,10 @@ function postProcess(allFeatures: FeatureSource, theme: LayoutConfig, relationsT
// At this point, we have all the features of the entire area.
// However, we want to export them per tile of a fixed size, so we use a dynamicTileSOurce to split it up
TiledFeatureSource.createHierarchy(source, {
minZoomLevel: 14,
maxZoomLevel: 14,
minZoomLevel: targetZoomLevel,
maxZoomLevel: targetZoomLevel,
maxFeatureCount: undefined,
registerTile: tile => {
if (tile.z < 12) {
return;
}
if (tile.features.data.length === 0) {
return
}
@ -229,7 +232,7 @@ function postProcess(allFeatures: FeatureSource, theme: LayoutConfig, relationsT
// All the tiles are written at this point
// Only thing left to do is to create the index
const path = targetdir + "_" + layerId + "_overview.json"
const path = targetdir + "_" + layerId + "_" + targetZoomLevel + "_overview.json"
const perX = {}
createdTiles.map(i => Tiles.tile_from_index(i)).forEach(([z, x, y]) => {
const key = "" + x
@ -240,7 +243,18 @@ function postProcess(allFeatures: FeatureSource, theme: LayoutConfig, relationsT
})
writeFileSync(path, JSON.stringify(perX))
// And, if needed, to create a points-only layer
if(pointsOnlyLayers.indexOf(layer.id) >= 0){
const features = source.features.data.map(f => f.feature)
const points = features.map(feature => GeoOperations.centerpoint(feature))
console.log("Writing points overview for ", layerId)
const targetPath = targetdir+"_"+layerId+"_points.geojson"
// This is the geojson file containing all features for this tile
writeFileSync(targetPath, JSON.stringify({
type: "FeatureCollection",
features: points
}, null, " "))
}
}
new PerLayerFeatureSourceSplitter(
@ -255,10 +269,11 @@ function postProcess(allFeatures: FeatureSource, theme: LayoutConfig, relationsT
}
async function main(args: string[]) {
if (args.length == 0) {
console.error("Expected arguments are: theme zoomlevel targetdirectory lat0 lon0 lat1 lon1 [--generate-point-overview layer-name]")
console.error("Expected arguments are: theme zoomlevel targetdirectory lat0 lon0 lat1 lon1 [--generate-point-overview layer-name,layer-name,...]")
return;
}
const themeName = args[0]
@ -269,6 +284,12 @@ async function main(args: string[]) {
const lat1 = Number(args[5])
const lon1 = Number(args[6])
let generatePointLayersFor = []
if(args[7] == "--generate-point-overview"){
generatePointLayersFor = args[8].split(",")
}
const tileRange = Tiles.TileRangeBetween(zoomlevel, lat0, lon0, lat1, lon1)
const theme = AllKnownLayouts.allKnownLayouts.get(themeName)
@ -293,7 +314,7 @@ async function main(args: string[]) {
const extraFeatures = await downloadExtraData(theme);
const allFeaturesSource = loadAllTiles(targetdir, tileRange, theme, extraFeatures)
postProcess(allFeaturesSource, theme, relationTracker, targetdir)
sliceToTiles(allFeaturesSource, theme, relationTracker, targetdir, generatePointLayersFor)
}