forked from MapComplete/MapComplete
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:
parent
e053e9f279
commit
9cfb7fbe68
14 changed files with 417 additions and 4320 deletions
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
|
@ -1,3 +0,0 @@
|
||||||
{
|
|
||||||
"files.eol": "\n"
|
|
||||||
}
|
|
|
@ -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)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
|
@ -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) {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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})"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -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": {
|
||||||
|
|
146
assets/themes/postal_codes.json
Normal file
146
assets/themes/postal_codes.json
Normal 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"
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
4
index.ts
4
index.ts
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
4215
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -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",
|
||||||
|
|
|
@ -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!")
|
Loading…
Reference in a new issue