Update of latlon2country and use its async interface; small refactoring of simplemetagging, improvements to cacheBuilder which respects isShown and calculated tags now

This commit is contained in:
pietervdvn 2021-12-07 02:22:56 +01:00
parent e053e9f279
commit 9cfb7fbe68
14 changed files with 417 additions and 4320 deletions

View file

@ -1,3 +0,0 @@
{
"files.eol": "\n"
}

View file

@ -68,7 +68,7 @@ export default class FilteringFeatureSource implements FeatureSourceForLayer, Ti
const features: { feature: any; freshness: Date }[] = this.upstream.features.data; const features: { feature: any; freshness: Date }[] = this.upstream.features.data;
const newFeatures = features.filter((f) => { const newFeatures = features.filter((f) => {
self.registerCallback(f.feature, layer.layerDef) self.registerCallback(f.feature)
if ( if (
this.state.selectedElement.data?.id === f.feature.id || this.state.selectedElement.data?.id === f.feature.id ||
@ -105,15 +105,18 @@ export default class FilteringFeatureSource implements FeatureSourceForLayer, Ti
this._is_dirty.setData(false) this._is_dirty.setData(false)
} }
private registerCallback(feature: any, layer: LayerConfig) { private registerCallback(feature: any) {
const src = this.state.allElements.addOrGetElement(feature) const src = this.state?.allElements?.addOrGetElement(feature)
if(src == undefined){
return
}
if (this._alreadyRegistered.has(src)) { if (this._alreadyRegistered.has(src)) {
return return
} }
this._alreadyRegistered.add(src) this._alreadyRegistered.add(src)
const self = this; const self = this;
src.addCallbackAndRunD(isShown => { src.addCallbackAndRunD(_ => {
self._is_dirty.setData(true) self._is_dirty.setData(true)
}) })
} }

View file

@ -86,6 +86,10 @@ export default class TiledFeatureSource implements Tiled, IndexedFeatureSource,
} }
public static createHierarchy(features: FeatureSource, options?: TiledFeatureSourceOptions): TiledFeatureSource { public static createHierarchy(features: FeatureSource, options?: TiledFeatureSourceOptions): TiledFeatureSource {
options = {
...options,
layer: features["layer"] ?? options.layer
}
const root = new TiledFeatureSource(0, 0, 0, null, options) const root = new TiledFeatureSource(0, 0, 0, null, options)
features.features?.addCallbackAndRunD(feats => root.addFeatures(feats)) features.features?.addCallbackAndRunD(feats => root.addFeatures(feats))
return root; return root;
@ -200,6 +204,6 @@ export interface TiledFeatureSourceOptions {
* Setting 'dontEnforceMinZoomLevel' will still allow bigger zoom levels for those features * Setting 'dontEnforceMinZoomLevel' will still allow bigger zoom levels for those features
*/ */
readonly dontEnforceMinZoom?: boolean, readonly dontEnforceMinZoom?: boolean,
readonly registerTile?: (tile: TiledFeatureSource & Tiled) => void, readonly registerTile?: (tile: TiledFeatureSource & FeatureSourceForLayer & Tiled) => void,
readonly layer?: FilteredLayer readonly layer?: FilteredLayer
} }

View file

@ -25,8 +25,7 @@ export class GeoOperations {
} }
static centerpointCoordinates(feature: any): [number, number] { static centerpointCoordinates(feature: any): [number, number] {
// @ts-ignore return <[number, number]> turf.center(feature).geometry.coordinates;
return turf.center(feature).geometry.coordinates;
} }
/** /**
@ -35,7 +34,7 @@ export class GeoOperations {
* @param lonlat1 * @param lonlat1
*/ */
static distanceBetween(lonlat0: [number, number], lonlat1: [number, number]) { static distanceBetween(lonlat0: [number, number], lonlat1: [number, number]) {
return turf.distance(lonlat0, lonlat1) * 1000 return turf.distance(lonlat0, lonlat1, {units: "meters"})
} }
/** /**
@ -196,8 +195,8 @@ export class GeoOperations {
static buffer(feature: any, bufferSizeInMeter: number) { static buffer(feature: any, bufferSizeInMeter: number) {
return turf.buffer(feature, bufferSizeInMeter / 1000, { return turf.buffer(feature, bufferSizeInMeter / 1000, {
units: 'kilometers' units:'kilometers'
}) } )
} }
static bbox(feature: any) { static bbox(feature: any) {

View file

@ -1,4 +1,4 @@
import SimpleMetaTagger from "./SimpleMetaTagger"; import SimpleMetaTaggers from "./SimpleMetaTagger";
import {ExtraFuncParams, ExtraFunctions} from "./ExtraFunctions"; import {ExtraFuncParams, ExtraFunctions} from "./ExtraFunctions";
import LayerConfig from "../Models/ThemeConfig/LayerConfig"; import LayerConfig from "../Models/ThemeConfig/LayerConfig";
import State from "../State"; import State from "../State";
@ -33,9 +33,8 @@ export default class MetaTagging {
return; return;
} }
const metatagsToApply: SimpleMetaTaggers[] = []
const metatagsToApply: SimpleMetaTagger [] = [] for (const metatag of SimpleMetaTaggers.metatags) {
for (const metatag of SimpleMetaTagger.metatags) {
if (metatag.includesDates) { if (metatag.includesDates) {
if (options.includeDates ?? true) { if (options.includeDates ?? true) {
metatagsToApply.push(metatag) metatagsToApply.push(metatag)
@ -59,19 +58,23 @@ export default class MetaTagging {
let somethingChanged = false let somethingChanged = false
for (const metatag of metatagsToApply) { for (const metatag of metatagsToApply) {
try { try {
// @ts-ignore
if (!metatag.keys.some(key => feature.properties[key] === undefined)) { if (!metatag.keys.some(key => feature.properties[key] === undefined)) {
// All keys are already defined, we probably already ran this one // All keys are already defined, we probably already ran this one
continue continue
} }
// @ts-ignore
if (metatag.isLazy) { if (metatag.isLazy) {
somethingChanged = true; somethingChanged = true;
// @ts-ignore
metatag.applyMetaTagsOnFeature(feature, freshness, layer) metatag.applyMetaTagsOnFeature(feature, freshness, layer)
} else { } else {
// @ts-ignore
const newValueAdded = metatag.applyMetaTagsOnFeature(feature, freshness, layer) const newValueAdded = metatag.applyMetaTagsOnFeature(feature, freshness, layer)
/* Note that the expression: /* Note that the expression:
* `somethingChanged = newValueAdded || metatag.applyMetaTagsOnFeature(feature, freshness)` * `somethingChanged = newValueAdded || metatag.applyMetaTagsOnFeature(feature, freshness)`
@ -83,6 +86,7 @@ export default class MetaTagging {
somethingChanged = newValueAdded || somethingChanged somethingChanged = newValueAdded || somethingChanged
} }
} catch (e) { } catch (e) {
// @ts-ignore
console.error("Could not calculate metatag for ", metatag.keys.join(","), ":", e, e.stack) console.error("Could not calculate metatag for ", metatag.keys.join(","), ":", e, e.stack)
} }
} }
@ -117,8 +121,9 @@ export default class MetaTagging {
const func = new Function("feat", "return " + code + ";"); const func = new Function("feat", "return " + code + ";");
const f = (feature: any) => { const f = (feature: any) => {
delete feature.properties[key] delete feature.properties[key]
Object.defineProperty(feature.properties, key, { Object.defineProperty(feature.properties, key, {
configurable: true, configurable: true,
enumerable: false, // By setting this as not enumerable, the localTileSaver will _not_ calculate this enumerable: false, // By setting this as not enumerable, the localTileSaver will _not_ calculate this
@ -149,7 +154,6 @@ export default class MetaTagging {
} }
}) })
} }

View file

@ -7,18 +7,92 @@ import BaseUIElement from "../UI/BaseUIElement";
import Title from "../UI/Base/Title"; import Title from "../UI/Base/Title";
import {FixedUiElement} from "../UI/Base/FixedUiElement"; import {FixedUiElement} from "../UI/Base/FixedUiElement";
import LayerConfig from "../Models/ThemeConfig/LayerConfig"; import LayerConfig from "../Models/ThemeConfig/LayerConfig";
import {CountryCoder} from "latlon2country"
const cardinalDirections = { export class SimpleMetaTagger {
N: 0, NNE: 22.5, NE: 45, ENE: 67.5, public readonly keys: string[];
E: 90, ESE: 112.5, SE: 135, SSE: 157.5, public readonly doc: string;
S: 180, SSW: 202.5, SW: 225, WSW: 247.5, public readonly isLazy: boolean;
W: 270, WNW: 292.5, NW: 315, NNW: 337.5 public readonly includesDates: boolean
public readonly applyMetaTagsOnFeature: (feature: any, freshness: Date, layer: LayerConfig) => boolean;
/***
* A function that adds some extra data to a feature
* @param docs: what does this extra data do?
* @param f: apply the changes. Returns true if something changed
*/
constructor(docs: { keys: string[], doc: string, includesDates?: boolean, isLazy?: boolean, cleanupRetagger?: boolean },
f: ((feature: any, freshness: Date, layer: LayerConfig) => boolean)) {
this.keys = docs.keys;
this.doc = docs.doc;
this.isLazy = docs.isLazy
this.applyMetaTagsOnFeature = f;
this.includesDates = docs.includesDates ?? false;
if (!docs.cleanupRetagger) {
for (const key of docs.keys) {
if (!key.startsWith('_') && key.toLowerCase().indexOf("theme") < 0) {
throw `Incorrect metakey ${key}: it should start with underscore (_)`
}
}
}
}
} }
export class CountryTagger extends SimpleMetaTagger {
private static readonly coder = new CountryCoder("https://pietervdvn.github.io/latlon2country/");
public runningTasks: Set<any>;
constructor() {
const runningTasks= new Set<any>();
super
(
{
keys: ["_country"],
doc: "The country code of the property (with latlon2country)",
includesDates: false
},
((feature, _) => {
let centerPoint: any = GeoOperations.centerpoint(feature);
const lat = centerPoint.geometry.coordinates[1];
const lon = centerPoint.geometry.coordinates[0];
runningTasks.add(feature)
CountryTagger.coder.GetCountryCodeAsync(lon, lat).then(
countries => {
runningTasks.delete(feature)
try {
const oldCountry = feature.properties["_country"];
feature.properties["_country"] = countries[0].trim().toLowerCase();
if (oldCountry !== feature.properties["_country"]) {
const tagsSource = State.state?.allElements?.getEventSourceById(feature.properties.id);
tagsSource?.ping();
}
} catch (e) {
console.warn(e)
}
}
).catch(_ => {
runningTasks.delete(feature)
})
return false;
})
)
this.runningTasks = runningTasks;
}
}
export default class SimpleMetaTaggers {
private static readonly cardinalDirections = {
N: 0, NNE: 22.5, NE: 45, ENE: 67.5,
E: 90, ESE: 112.5, SE: 135, SSE: 157.5,
S: 180, SSW: 202.5, SW: 225, WSW: 247.5,
W: 270, WNW: 292.5, NW: 315, NNW: 337.5
}
export default class SimpleMetaTagger {
public static coder: any;
public static readonly objectMetaInfo = new SimpleMetaTagger( public static readonly objectMetaInfo = new SimpleMetaTagger(
{ {
keys: ["_last_edit:contributor", keys: ["_last_edit:contributor",
@ -92,7 +166,7 @@ export default class SimpleMetaTagger {
return; return;
} }
return SimpleMetaTagger.removeBothTagging(feature.properties) return SimpleMetaTaggers.removeBothTagging(feature.properties)
}) })
) )
private static surfaceArea = new SimpleMetaTagger( private static surfaceArea = new SimpleMetaTagger(
@ -197,32 +271,7 @@ export default class SimpleMetaTagger {
return true; return true;
}) })
) )
private static country = new SimpleMetaTagger( public static country = new CountryTagger()
{
keys: ["_country"],
doc: "The country code of the property (with latlon2country)",
includesDates: false
},
((feature, _) => {
let centerPoint: any = GeoOperations.centerpoint(feature);
const lat = centerPoint.geometry.coordinates[1];
const lon = centerPoint.geometry.coordinates[0];
SimpleMetaTagger.coder?.GetCountryCodeFor(lon, lat, (countries: string[]) => {
try {
const oldCountry = feature.properties["_country"];
feature.properties["_country"] = countries[0].trim().toLowerCase();
if (oldCountry !== feature.properties["_country"]) {
const tagsSource = State.state.allElements.getEventSourceById(feature.properties.id);
tagsSource.ping();
}
} catch (e) {
console.warn(e)
}
})
return false;
})
)
private static isOpen = new SimpleMetaTagger( private static isOpen = new SimpleMetaTagger(
{ {
keys: ["_isOpen", "_isOpen:description"], keys: ["_isOpen", "_isOpen:description"],
@ -319,7 +368,7 @@ export default class SimpleMetaTagger {
if (direction === undefined) { if (direction === undefined) {
return false; return false;
} }
const n = cardinalDirections[direction] ?? Number(direction); const n = SimpleMetaTaggers.cardinalDirections[direction] ?? Number(direction);
if (isNaN(n)) { if (isNaN(n)) {
return false; return false;
} }
@ -333,6 +382,7 @@ export default class SimpleMetaTagger {
}) })
) )
private static currentTime = new SimpleMetaTagger( private static currentTime = new SimpleMetaTagger(
{ {
keys: ["_now:date", "_now:datetime", "_loaded:date", "_loaded:_datetime"], keys: ["_now:date", "_now:datetime", "_loaded:date", "_loaded:_datetime"],
@ -361,49 +411,24 @@ export default class SimpleMetaTagger {
return true; return true;
} }
) )
public static metatags = [ public static metatags: SimpleMetaTagger[] = [
SimpleMetaTagger.latlon, SimpleMetaTaggers.latlon,
SimpleMetaTagger.layerInfo, SimpleMetaTaggers.layerInfo,
SimpleMetaTagger.surfaceArea, SimpleMetaTaggers.surfaceArea,
SimpleMetaTagger.lngth, SimpleMetaTaggers.lngth,
SimpleMetaTagger.canonicalize, SimpleMetaTaggers.canonicalize,
SimpleMetaTagger.country, SimpleMetaTaggers.country,
SimpleMetaTagger.isOpen, SimpleMetaTaggers.isOpen,
SimpleMetaTagger.directionSimplified, SimpleMetaTaggers.directionSimplified,
SimpleMetaTagger.currentTime, SimpleMetaTaggers.currentTime,
SimpleMetaTagger.objectMetaInfo, SimpleMetaTaggers.objectMetaInfo,
SimpleMetaTagger.noBothButLeftRight SimpleMetaTaggers.noBothButLeftRight
]; ];
public readonly keys: string[];
public readonly doc: string;
public readonly isLazy: boolean;
public readonly includesDates: boolean
public readonly applyMetaTagsOnFeature: (feature: any, freshness: Date, layer: LayerConfig) => boolean;
public static readonly lazyTags: string[] = [].concat(...SimpleMetaTagger.metatags.filter(tagger => tagger.isLazy) public static readonly lazyTags: string[] = [].concat(...SimpleMetaTaggers.metatags.filter(tagger => tagger.isLazy)
.map(tagger => tagger.keys)); .map(tagger => tagger.keys));
/***
* A function that adds some extra data to a feature
* @param docs: what does this extra data do?
* @param f: apply the changes. Returns true if something changed
*/
constructor(docs: { keys: string[], doc: string, includesDates?: boolean, isLazy?: boolean, cleanupRetagger?: boolean },
f: ((feature: any, freshness: Date, layer: LayerConfig) => boolean)) {
this.keys = docs.keys;
this.doc = docs.doc;
this.isLazy = docs.isLazy
this.applyMetaTagsOnFeature = f;
this.includesDates = docs.includesDates ?? false;
if (!docs.cleanupRetagger) {
for (const key of docs.keys) {
if (!key.startsWith('_') && key.toLowerCase().indexOf("theme") < 0) {
throw `Incorrect metakey ${key}: it should start with underscore (_)`
}
}
}
}
/** /**
* Edits the given object to rewrite 'both'-tagging into a 'left-right' tagging scheme. * Edits the given object to rewrite 'both'-tagging into a 'left-right' tagging scheme.
@ -494,7 +519,7 @@ export default class SimpleMetaTagger {
subElements.push(new Title("Metatags calculated by MapComplete", 2)) subElements.push(new Title("Metatags calculated by MapComplete", 2))
subElements.push(new FixedUiElement("The following values are always calculated, by default, by MapComplete and are available automatically on all elements in every theme")) subElements.push(new FixedUiElement("The following values are always calculated, by default, by MapComplete and are available automatically on all elements in every theme"))
for (const metatag of SimpleMetaTagger.metatags) { for (const metatag of SimpleMetaTaggers.metatags) {
subElements.push( subElements.push(
new Title(metatag.keys.join(", "), 3), new Title(metatag.keys.join(", "), 3),
metatag.doc, metatag.doc,

View file

@ -29,7 +29,7 @@
{ {
"id": "render_crab", "id": "render_crab",
"render": { "render": {
"nl": "Volgens het CRAB ligt hier <b>{STRAATNM}</b> {HUISNR} (label: {_HNRLABEL})" "nl": "Volgens het CRAB ligt hier <b>{STRAATNM}</b> {HUISNR} (label: {HNRLABEL})"
} }
} }
] ]

View file

@ -370,8 +370,8 @@
"_embedding_street!:={STRAATNM}" "_embedding_street!:={STRAATNM}"
] ]
}, },
{ "#": "Matches the embedding GRB object", {
"#": "Matches the embedding GRB object",
"or": [ "or": [
"_embedding_nr_grb!:={HUISNR}", "_embedding_nr_grb!:={HUISNR}",
"_embedding_street_grb!:={STRAATNM}" "_embedding_street_grb!:={STRAATNM}"
@ -453,15 +453,14 @@
"render": "{import_button(OSM-buildings,building=$building;man_made=$man_made; source:geometry:date=$_grb_date; source:geometry:ref=$_grb_ref; addr:street=$addr:street; addr:housenumber=$addr:housenumber; building:min_level=$_building:min_level, Upload this building to OpenStreetMap)}", "render": "{import_button(OSM-buildings,building=$building;man_made=$man_made; source:geometry:date=$_grb_date; source:geometry:ref=$_grb_ref; addr:street=$addr:street; addr:housenumber=$addr:housenumber; building:min_level=$_building:min_level, Upload this building to OpenStreetMap)}",
"mappings": [ "mappings": [
{ {
"if": {"and": "if": {
[ "and": [
"_overlaps_with!=", "_overlaps_with!=",
"_osm_obj:addr:street=", "_osm_obj:addr:street=",
"_osm_obj:addr:housenumber=", "_osm_obj:addr:housenumber=",
"addr:street~*", "addr:street~*",
"addr:housenumber~*" "addr:housenumber~*"
]
]
}, },
"then": "{import_button(OSM-buildings,building=$_target_building_type; source:geometry:date=$_grb_date; source:geometry:ref=$_grb_ref; addr:street=$addr:street; addr:housenumber=$addr:housenumber, Replace the geometry in OpenStreetMap and add the address,,,_osm_obj:id)}" "then": "{import_button(OSM-buildings,building=$_target_building_type; source:geometry:date=$_grb_date; source:geometry:ref=$_grb_ref; addr:street=$addr:street; addr:housenumber=$addr:housenumber, Replace the geometry in OpenStreetMap and add the address,,,_osm_obj:id)}"
}, },
@ -578,21 +577,22 @@
"centroid" "centroid"
] ]
}, },
{ "width": { {
"render": 5, "width": {
"mappings": [ "render": 5,
{ "mappings": [
"if": "_imported=yes", {
"then": "1" "if": "_imported=yes",
} "then": "1"
] }
}, ]
},
"color": { "color": {
"render": "#00a", "render": "#00a",
"mappings": [ "mappings": [
{ {
"if": "_imported=yes", "if": "_imported=yes",
"then":"#00ff00" "then": "#00ff00"
}, },
{ {
"if": { "if": {

View file

@ -0,0 +1,146 @@
{
"id": "postal_codes",
"title": {
"en": "Postal codes"
},
"shortDescription": {
"en": "Postal codes"
},
"description": {
"en": "Postal codes"
},
"language": [
"en"
],
"maintainer": "",
"icon": "./assets/svg/bug.svg",
"version": "0",
"startLat": 0,
"startLon": 0,
"startZoom": 1,
"widenFactor": 0.05,
"socialImage": "",
"layers": [
{
"id": "postal_codes",
"name": {
"en": "postal codes"
},
"minzoom": 12,
"title": {
"render": {
"en": "Postal code {postal_code}"
}
},
"description": {},
"tagRenderings": [
{
"id": "postal_code",
"render": {
"en": "The postal code is {postal_code}"
}
}
],
"presets": [],
"source": {
"isOsmCache": true,
"geoJson": "http://127.0.0.1:8080/postal_codes_postal_codes_{z}_{x}_{y}.geojson",
"geoJsonZoomLevel": 1,
"osmTags": {
"or": [
"boundary=postal_code",
{
"and": [
"bounary=administrative",
"postal_code~*"
]
}
]
}
},
"mapRendering": [
{
"icon": {
"render": "./assets/svg/bug.svg"
},
"iconSize": {
"render": "40,40,center"
},
"location": [
"point",
"centroid"
]
},
{
"color": {
"render": "#00f"
},
"width": {
"render": "8"
}
}
],
"isShown": {
"render": "yes",
"mappings": [{
"if" :"_country!=be",
"then": "no"
}]
}
},
{
"id": "town_halls",
"name": {
"en": "town halls"
},
"minzoom": 12,
"title": {
"render": {
"en": "Town halls"
}
},
"calculatedTags": [
"_postal_code=feat.overlapWith('postal_codes')[0]?.feat?.properties?.postal_code"
],
"description": {},
"tagRenderings": [
],
"presets": [],
"source": {
"isOsmCache": true,
"geoJson": "http://127.0.0.1:8080/postal_codes_town_hall_{z}_{x}_{y}.geojson",
"geoJsonZoomLevel": 1,
"osmTags": "amenity=townhall"
},
"mapRendering": [
{
"icon": {
"render": "./assets/svg/bug.svg"
},
"iconSize": {
"render": "40,40,center"
},
"location": [
"point",
"centroid"
]
},
{
"color": {
"render": "#00f"
},
"width": {
"render": "8"
}
}
],
"isShown": {
"render": "yes",
"mappings": [{
"if" :"_country!=be",
"then": "no"
}]
}
}
]
}

View file

@ -3,8 +3,6 @@ import {QueryParameters} from "./Logic/Web/QueryParameters";
import Combine from "./UI/Base/Combine"; import Combine from "./UI/Base/Combine";
import AvailableBaseLayers from "./Logic/Actors/AvailableBaseLayers"; import AvailableBaseLayers from "./Logic/Actors/AvailableBaseLayers";
import MinimapImplementation from "./UI/Base/MinimapImplementation"; import MinimapImplementation from "./UI/Base/MinimapImplementation";
import CountryCoder from "latlon2country/index";
import SimpleMetaTagger from "./Logic/SimpleMetaTagger";
import {Utils} from "./Utils"; import {Utils} from "./Utils";
import AllThemesGui from "./UI/AllThemesGui"; import AllThemesGui from "./UI/AllThemesGui";
import DetermineLayout from "./Logic/DetermineLayout"; import DetermineLayout from "./Logic/DetermineLayout";
@ -14,12 +12,10 @@ import State from "./State";
import AvailableBaseLayersImplementation from "./Logic/Actors/AvailableBaseLayersImplementation"; import AvailableBaseLayersImplementation from "./Logic/Actors/AvailableBaseLayersImplementation";
import ShowOverlayLayerImplementation from "./UI/ShowDataLayer/ShowOverlayLayerImplementation"; import ShowOverlayLayerImplementation from "./UI/ShowDataLayer/ShowOverlayLayerImplementation";
import {DefaultGuiState} from "./UI/DefaultGuiState"; import {DefaultGuiState} from "./UI/DefaultGuiState";
import {Browser} from "leaflet";
// Workaround for a stupid crash: inject some functions which would give stupid circular dependencies or crash the other nodejs scripts running from console // Workaround for a stupid crash: inject some functions which would give stupid circular dependencies or crash the other nodejs scripts running from console
MinimapImplementation.initialize() MinimapImplementation.initialize()
AvailableBaseLayers.implement(new AvailableBaseLayersImplementation()) AvailableBaseLayers.implement(new AvailableBaseLayersImplementation())
SimpleMetaTagger.coder = new CountryCoder("https://pietervdvn.github.io/latlon2country/");
ShowOverlayLayerImplementation.Implement(); ShowOverlayLayerImplementation.Implement();
// Miscelleanous // Miscelleanous

View file

@ -1032,6 +1032,24 @@
"shortDescription": "A map with playgrounds", "shortDescription": "A map with playgrounds",
"title": "Playgrounds" "title": "Playgrounds"
}, },
"postal_codes": {
"description": "Postal codes",
"layers": {
"0": {
"name": "postal codes",
"tagRenderings": {
"postal_code": {
"render": "The postal code is {postal_code}"
}
},
"title": {
"render": "Postal code {postal_code}"
}
}
},
"shortDescription": "Postal codes",
"title": "Postal codes"
},
"postboxes": { "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. ", "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": { "layers": {

4215
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -77,7 +77,7 @@
"idb-keyval": "^6.0.3", "idb-keyval": "^6.0.3",
"jquery": "^3.6.0", "jquery": "^3.6.0",
"jspdf": "^2.3.1", "jspdf": "^2.3.1",
"latlon2country": "^1.1.3", "latlon2country": "^1.2.3",
"leaflet": "^1.7.1", "leaflet": "^1.7.1",
"leaflet-polylineoffset": "^1.1.1", "leaflet-polylineoffset": "^1.1.1",
"leaflet-providers": "^1.13.0", "leaflet-providers": "^1.13.0",

View file

@ -21,7 +21,9 @@ import StaticFeatureSource from "../Logic/FeatureSource/Sources/StaticFeatureSou
import TiledFeatureSource from "../Logic/FeatureSource/TiledFeatureSource/TiledFeatureSource"; import TiledFeatureSource from "../Logic/FeatureSource/TiledFeatureSource/TiledFeatureSource";
import Constants from "../Models/Constants"; import Constants from "../Models/Constants";
import {GeoOperations} from "../Logic/GeoOperations"; import {GeoOperations} from "../Logic/GeoOperations";
import SimpleMetaTaggers from "../Logic/SimpleMetaTagger";
import FilteringFeatureSource from "../Logic/FeatureSource/Sources/FilteringFeatureSource";
import Loc from "../Models/Loc";
ScriptUtils.fixUtils() ScriptUtils.fixUtils()
@ -177,12 +179,15 @@ function loadAllTiles(targetdir: string, r: TileRange, theme: LayoutConfig, extr
* Load all the tiles into memory from disk * Load all the tiles into memory from disk
*/ */
function sliceToTiles(allFeatures: FeatureSource, theme: LayoutConfig, relationsTracker: RelationsTracker, targetdir: string, pointsOnlyLayers: string[]) { function sliceToTiles(allFeatures: FeatureSource, theme: LayoutConfig, relationsTracker: RelationsTracker, targetdir: string, pointsOnlyLayers: string[]) {
function handleLayer(source: FeatureSourceForLayer) { const skippedLayers = new Set<string>()
async function handleLayer(source: FeatureSourceForLayer) {
const layer = source.layer.layerDef; const layer = source.layer.layerDef;
const targetZoomLevel = layer.source.geojsonZoomLevel ?? 0 const targetZoomLevel = layer.source.geojsonZoomLevel ?? 0
const layerId = layer.id const layerId = layer.id
if (layer.source.isOsmCacheLayer !== true) { if (layer.source.isOsmCacheLayer !== true) {
skippedLayers.add(layer.id)
return; return;
} }
console.log("Handling layer ", layerId, "which has", source.features.data.length, "features") console.log("Handling layer ", layerId, "which has", source.features.data.length, "features")
@ -201,6 +206,13 @@ function sliceToTiles(allFeatures: FeatureSource, theme: LayoutConfig, relations
includeDates: false, includeDates: false,
includeNonDates: true includeNonDates: true
}); });
while (SimpleMetaTaggers.country.runningTasks.size > 0) {
console.log("Still waiting for ", SimpleMetaTaggers.country.runningTasks.size," features which don't have a country yet")
await ScriptUtils.sleep(1)
}
const createdTiles = [] const createdTiles = []
// At this point, we have all the features of the entire area. // At this point, we have all the features of the entire area.
@ -210,23 +222,57 @@ function sliceToTiles(allFeatures: FeatureSource, theme: LayoutConfig, relations
maxZoomLevel: targetZoomLevel, maxZoomLevel: targetZoomLevel,
maxFeatureCount: undefined, maxFeatureCount: undefined,
registerTile: tile => { registerTile: tile => {
const tileIndex = tile.tileIndex;
if (tile.features.data.length === 0) { if (tile.features.data.length === 0) {
return return
} }
for (const feature of tile.features.data) {
const filteredTile = new FilteringFeatureSource({
locationControl: new UIEventSource<Loc>(undefined),
allElements: undefined,
selectedElement: new UIEventSource<any>(undefined)
},
tileIndex,
tile,
new UIEventSource<any>(undefined)
)
if (filteredTile.features.data.length === 0) {
return
}
let strictlyCalculated = 0
let featureCount = 0
for (const feature of filteredTile.features.data) {
// Some cleanup // Some cleanup
delete feature.feature["bbox"] delete feature.feature["bbox"]
if(tile.layer.layerDef.calculatedTags !== undefined){
// Evaluate all the calculated tags strictly
const calculatedTagKeys = tile.layer.layerDef.calculatedTags.map(ct => ct[0])
featureCount++
for (const calculatedTagKey of calculatedTagKeys) {
const strict = feature.feature.properties[calculatedTagKey]
feature.feature.properties[calculatedTagKey] =strict
strictlyCalculated ++;
if(strictlyCalculated % 100 === 0){
console.log("Strictly calculated ", strictlyCalculated, "values for tile",tileIndex,": now at ", featureCount,"/",filteredTile.features.data.length, "examle value: ", strict)
}
}
}
} }
// Lets save this tile! // Lets save this tile!
const [z, x, y] = Tiles.tile_from_index(tile.tileIndex) const [z, x, y] = Tiles.tile_from_index(tileIndex)
// console.log("Writing tile ", z, x, y, layerId) // console.log("Writing tile ", z, x, y, layerId)
const targetPath = geoJsonName(targetdir + "_" + layerId, x, y, z) const targetPath = geoJsonName(targetdir + "_" + layerId, x, y, z)
createdTiles.push(tile.tileIndex) createdTiles.push(tileIndex)
// This is the geojson file containing all features for this tile // This is the geojson file containing all features for this tile
writeFileSync(targetPath, JSON.stringify({ writeFileSync(targetPath, JSON.stringify({
type: "FeatureCollection", type: "FeatureCollection",
features: tile.features.data.map(f => f.feature) features: filteredTile.features.data.map(f => f.feature)
}, null, " ")) }, null, " "))
console.log("Written tile", targetPath,"with", filteredTile.features.data.length)
} }
}) })
@ -267,18 +313,31 @@ function sliceToTiles(allFeatures: FeatureSource, theme: LayoutConfig, relations
handleLayer, handleLayer,
allFeatures allFeatures
) )
const skipped = Array.from(skippedLayers)
if (skipped.length > 0) {
console.warn("Did not save any cache files for layers " + skipped.join(", ") + " as these didn't set the flag `isOsmCache` to true")
}
} }
async function main(args: string[]) { async function main(args: string[]) {
if (args.length == 0) { console.log("Cache builder started with args ", args.join(", "))
console.error("Expected arguments are: theme zoomlevel targetdirectory lat0 lon0 lat1 lon1 [--generate-point-overview layer-name,layer-name,...]") if (args.length < 6) {
console.error("Expected arguments are: theme zoomlevel targetdirectory lat0 lon0 lat1 lon1 [--generate-point-overview layer-name,layer-name,...]\n" +
"Note: a new directory named <theme> will be created in targetdirectory")
return; return;
} }
const themeName = args[0] const themeName = args[0]
const zoomlevel = Number(args[1]) const zoomlevel = Number(args[1])
const targetdir = args[2] + "/" + themeName const targetdir = args[2] + "/" + themeName
if (!existsSync(args[2])) {
console.log("Directory not found")
throw "The directory " + args[2] + "does not exist"
}
const lat0 = Number(args[3]) const lat0 = Number(args[3])
const lon0 = Number(args[4]) const lon0 = Number(args[4])
const lat1 = Number(args[5]) const lat1 = Number(args[5])
@ -292,6 +351,11 @@ async function main(args: string[]) {
const tileRange = Tiles.TileRangeBetween(zoomlevel, lat0, lon0, lat1, lon1) const tileRange = Tiles.TileRangeBetween(zoomlevel, lat0, lon0, lat1, lon1)
if (tileRange.total === 0) {
console.log("Tilerange has zero tiles - this is probably an error")
return
}
const theme = AllKnownLayouts.allKnownLayouts.get(themeName) const theme = AllKnownLayouts.allKnownLayouts.get(themeName)
if (theme === undefined) { if (theme === undefined) {
const keys = [] const keys = []
@ -321,5 +385,9 @@ async function main(args: string[]) {
let args = [...process.argv] let args = [...process.argv]
args.splice(0, 2) args.splice(0, 2)
main(args); try {
main(args).catch(e => console.error("Error building cache:", e));
} catch (e) {
console.error("Error building cache:", e)
}
console.log("All done!") console.log("All done!")