diff --git a/Customizations/JSON/LayerConfig.ts b/Customizations/JSON/LayerConfig.ts index 511744f8a0..075659160c 100644 --- a/Customizations/JSON/LayerConfig.ts +++ b/Customizations/JSON/LayerConfig.ts @@ -24,7 +24,7 @@ export default class LayerConfig { static WAYHANDLING_DEFAULT = 0; static WAYHANDLING_CENTER_ONLY = 1; static WAYHANDLING_CENTER_AND_WAY = 2; - + id: string; name: Translation description: Translation; @@ -45,7 +45,6 @@ export default class LayerConfig { width: TagRenderingConfig; dashArray: TagRenderingConfig; wayHandling: number; - hideUnderlayingFeaturesMinPercentage?: number; presets: { title: Translation, @@ -98,8 +97,13 @@ export default class LayerConfig { console.warn(`Unofficial theme ${this.id} with custom javascript! This is a security risk`) } this.calculatedTags = []; - for (const key in json.calculatedTags) { - this.calculatedTags.push([key, json.calculatedTags[key]]) + for (const kv of json.calculatedTags) { + + const index = kv.indexOf("=") + const key = kv.substring(0, index); + const code = kv.substring(index + 1); + + this.calculatedTags.push([key, code]) } } @@ -108,7 +112,6 @@ export default class LayerConfig { this.minzoom = json.minzoom ?? 0; this.maxzoom = json.maxzoom ?? 1000; this.wayHandling = json.wayHandling ?? 0; - this.hideUnderlayingFeaturesMinPercentage = json.hideUnderlayingFeaturesMinPercentage ?? 0; this.presets = (json.presets ?? []).map((pr, i) => ({ title: Translations.T(pr.title, `${context}.presets[${i}].title`), @@ -215,6 +218,9 @@ export default class LayerConfig { this.dashArray = tr("dashArray", ""); + if(json["showIf"] !== undefined){ + throw "Invalid key on layerconfig "+this.id+": showIf. Did you mean 'isShown' instead?"; + } } public CustomCodeSnippets(): string[] { diff --git a/Customizations/JSON/LayerConfigJson.ts b/Customizations/JSON/LayerConfigJson.ts index 7345d1b03d..335c265edc 100644 --- a/Customizations/JSON/LayerConfigJson.ts +++ b/Customizations/JSON/LayerConfigJson.ts @@ -43,9 +43,17 @@ export interface LayerConfigJson { source: {osmTags: AndOrTagConfigJson | string} | {geoJsonSource: string} | {overpassScript: string} /** - * A dictionary of 'key': 'js-expression'. These js-expressions will be calculated for every feature, giving extra tags to work with in the rest of the pipieline + * + * A list of extra tags to calculate, specified as "keyToAssignTo=javascript-expression". + * There are a few extra functions available. Refer to Docs/CalculatedTags.md for more information + * The functions will be run in order, e.g. + * [ + * "_max_overlap_m2=Math.max(...feat.overlapsWith("someOtherLayer").map(o => o.overlap)) + * "_max_overlap_ratio=Number(feat._max_overlap_m2)/feat.area + * ] + * */ - calculatedTags? : any; + calculatedTags? : string[]; /** * If set, this layer will not query overpass; but it'll still match the tags above which are by chance returned by other layers. @@ -145,14 +153,6 @@ export interface LayerConfigJson { */ wayHandling?: number; - /** - * Consider that we want to show 'Nature Reserves' and 'Forests'. Now, ofter, there are pieces of forest mapped _in_ the nature reserve. - * Now, showing those pieces of forest overlapping with the nature reserve truly clutters the map and is very user-unfriendly. - * - * The features are placed layer by layer. If a feature below a feature on this layer overlaps for more then 'x'-percent, the underlying feature is hidden. - */ - hideUnderlayingFeaturesMinPercentage?:number; - /** * If set, this layer will pass all the features it receives onto the next layer. * This is ideal for decoration, e.g. directionss on cameras diff --git a/Logic/Actors/ImageSearcher.ts b/Logic/Actors/ImageSearcher.ts index 09662878d9..bdf5247433 100644 --- a/Logic/Actors/ImageSearcher.ts +++ b/Logic/Actors/ImageSearcher.ts @@ -17,12 +17,12 @@ import {UIEventSource} from "../UIEventSource"; * Note that this list is embedded into an UIEVentSource, ready to put it into a carousel. * */ -export class ImageSearcher extends UIEventSource<{ key: string, url: string }[]>{ +export class ImageSearcher extends UIEventSource<{ key: string, url: string }[]> { private readonly _wdItem = new UIEventSource(""); private readonly _commons = new UIEventSource(""); - constructor(tags: UIEventSource, imagePrefix = "image", loadSpecial = true) { + private constructor(tags: UIEventSource, imagePrefix = "image", loadSpecial = true) { super([]) const self = this; @@ -31,7 +31,6 @@ export class ImageSearcher extends UIEventSource<{ key: string, url: string }[]> let somethingChanged = false; for (const image of images) { const url = image.url; - const key = image.key; if (url === undefined || url === null || url === "") { continue; @@ -93,7 +92,7 @@ export class ImageSearcher extends UIEventSource<{ key: string, url: string }[]> if (mapillary.indexOf(prefix) < 0) { mapillary = prefix + mapillary; } - + AddImages([{url: mapillary, key: undefined}]); } @@ -114,7 +113,6 @@ export class ImageSearcher extends UIEventSource<{ key: string, url: string }[]> imageURLS.push(wd.image); Wikimedia.GetCategoryFiles(wd.commonsWiki, (images: ImagesInCategory) => { for (const image of images.images) { - // @ts-ignore if (image.startsWith("File:")) { imageURLS.push(image); } @@ -129,17 +127,15 @@ export class ImageSearcher extends UIEventSource<{ key: string, url: string }[]> const imageUrls = []; const allCommons: string[] = commonsData.split(";"); for (const commons of allCommons) { - // @ts-ignore if (commons.startsWith("Category:")) { Wikimedia.GetCategoryFiles(commons, (images: ImagesInCategory) => { for (const image of images.images) { - // @ts-ignore if (image.startsWith("File:")) { imageUrls.push(image); } } }) - } else { // @ts-ignore + } else { if (commons.startsWith("File:")) { imageUrls.push(commons); } @@ -168,5 +164,18 @@ export class ImageSearcher extends UIEventSource<{ key: string, url: string }[]> return images; } + + private static _cache = new Map(); + + public static construct(tags: UIEventSource, imagePrefix = "image", loadSpecial = true): ImageSearcher { + const key = tags["id"] + " "+imagePrefix+loadSpecial; + if(ImageSearcher._cache.has(key)){ + return ImageSearcher._cache.get(key) + } + + const searcher = new ImageSearcher(tags, imagePrefix, loadSpecial); + ImageSearcher._cache.set(key, searcher) + return searcher; + } } \ No newline at end of file diff --git a/Logic/Actors/UpdateFromOverpass.ts b/Logic/Actors/UpdateFromOverpass.ts index c2ac6e85b4..d7b470a210 100644 --- a/Logic/Actors/UpdateFromOverpass.ts +++ b/Logic/Actors/UpdateFromOverpass.ts @@ -158,7 +158,7 @@ export default class UpdateFromOverpass implements FeatureSource { self.retries.data++; self.ForceRefresh(); self.timeout.setData(self.retries.data * 5); - console.log(`QUERY FAILED (retrying in ${5 * self.retries.data} sec)`, reason); + console.log(`QUERY FAILED (retrying in ${5 * self.retries.data} sec)`); self.retries.ping(); self.runningQuery.setData(false); diff --git a/Logic/ExtraFunction.ts b/Logic/ExtraFunction.ts index deb66bbfcd..3969b9434a 100644 --- a/Logic/ExtraFunction.ts +++ b/Logic/ExtraFunction.ts @@ -5,59 +5,9 @@ import Combine from "../UI/Base/Combine"; export class ExtraFunction { - private static DistanceToFunc = new ExtraFunction( - "distanceTo", - "Calculates the distance between the feature and a specified point", - ["longitude", "latitude"], - (feature) => { - return (lon, lat) => { - // Feature._lon and ._lat is conveniently place by one of the other metatags - return GeoOperations.distanceBetween([lon, lat], [feature._lon, feature._lat]); - } - } - ) - private static readonly allFuncs: ExtraFunction[] = [ExtraFunction.DistanceToFunc]; - private readonly _name: string; - private readonly _args: string[]; - private readonly _doc: string; - private readonly _f: (feat: any) => any; - - constructor(name: string, doc: string, args: string[], f: ((feat: any) => any)) { - this._name = name; - this._doc = doc; - this._args = args; - this._f = f; - - } - - public static FullPatchFeature(feature) { - for (const func of ExtraFunction.allFuncs) { - func.PatchFeature(feature); - } - } - - public static HelpText(): UIElement { - return new Combine([ - ExtraFunction.intro, - ...ExtraFunction.allFuncs.map(func => - new Combine([ - "

" + func._name + "

", - func._doc, - "
    ", - ...func._args.map(arg => "
  • " + arg + "
  • "), - "
" - ]) - ) - ]); - } - - public PatchFeature(feature: any) { - feature[this._name] = this._f(feature); - } - static readonly intro = `

Calculating tags with Javascript

-

In some cases, it is useful to have some tags calculated based on other properties. Some useful tags are available by default (e.g. _lat, lon, _country), as detailed above.

+

In some cases, it is useful to have some tags calculated based on other properties. Some useful tags are available by default (e.g. lat, lon, _country), as detailed above.

It is also possible to calculate your own tags - but this requires some javascript knowledge.

@@ -71,11 +21,97 @@ Before proceeding, some warnings: In the layer object, add a field calculatedTags, e.g.:
- "calculatedTags": { - "_someKey": "javascript-expression", - "name": "feat.properties.name ?? feat.properties.ref ?? feat.properties.operator", - "_distanceCloserThen3Km": "feat.distanceTo( some_lon, some_lat) < 3 ? 'yes' : 'no'" - } + "calculatedTags": [ + "_someKey=javascript-expression", + "name=feat.properties.name ?? feat.properties.ref ?? feat.properties.operator", + "_distanceCloserThen3Km=feat.distanceTo( some_lon, some_lat) < 3 ? 'yes' : 'no'" + ]
+ +The above code will be executed for every feature in the layer. The feature is accessible as feat and is an amended geojson object: +- area contains the surface area (in square meters) of the object +- lat and lon contain the latitude and longitude + +Some advanced functions are available on feat as well: + ` + private static OverlapFunc = new ExtraFunction( + "overlapWith", + "Gives a list of features from the specified layer which this feature overlaps with, the amount of overlap in m². The returned value is { feat: GeoJSONFeature, overlap: number}", + ["...layerIds - one or more layer ids of the layer from which every feature is checked for overlap)"], + (featuresPerLayer, feat) => { + return (...layerIds: string[]) => { + const result = [] + for (const layerId of layerIds) { + const otherLayer = featuresPerLayer.get(layerId); + if (otherLayer === undefined) { + console.error(`Trying to calculate 'overlapWith' with specified layer ${layerId}, but such layer is found`); + continue; + } + + if (otherLayer.length === 0) { + continue; + } + result.push(...GeoOperations.calculateOverlap(feat, otherLayer)); + } + return result; + } + } + ) + private static DistanceToFunc = new ExtraFunction( + "distanceTo", + "Calculates the distance between the feature and a specified point", + ["longitude", "latitude"], + (featuresPerLayer, feature) => { + return (lon, lat) => { + // Feature._lon and ._lat is conveniently place by one of the other metatags + return GeoOperations.distanceBetween([lon, lat], [feature._lon, feature._lat]); + } + } + ) + private static readonly allFuncs: ExtraFunction[] = [ExtraFunction.DistanceToFunc, ExtraFunction.OverlapFunc]; + private readonly _name: string; + private readonly _args: string[]; + private readonly _doc: string; + private readonly _f: (featuresPerLayer: Map, feat: any) => any; + + constructor(name: string, doc: string, args: string[], f: ((featuresPerLayer: Map, feat: any) => any)) { + this._name = name; + this._doc = doc; + this._args = args; + this._f = f; + + } + + public static FullPatchFeature(featuresPerLayer: Map, feature) { + for (const func of ExtraFunction.allFuncs) { + func.PatchFeature(featuresPerLayer, feature); + } + } + + public static HelpText(): UIElement { + return new Combine([ + ExtraFunction.intro, + "
    ", + ...ExtraFunction.allFuncs.map(func => + new Combine([ + "
  • ", func._name, "
  • " + ]) + ), + "
", + ...ExtraFunction.allFuncs.map(func => + new Combine([ + "

" + func._name + "

", + func._doc, + "
    ", + ...func._args.map(arg => "
  • " + arg + "
  • "), + "
" + ]) + ) + ]); + } + + public PatchFeature(featuresPerLayer: Map, feature: any) { + feature[this._name] = this._f(featuresPerLayer, feature); + } } \ No newline at end of file diff --git a/Logic/FeatureSource/FeaturePipeline.ts b/Logic/FeatureSource/FeaturePipeline.ts index fb8d21f7eb..f6cca66ff3 100644 --- a/Logic/FeatureSource/FeaturePipeline.ts +++ b/Logic/FeatureSource/FeaturePipeline.ts @@ -2,7 +2,6 @@ import FilteringFeatureSource from "../FeatureSource/FilteringFeatureSource"; import FeatureSourceMerger from "../FeatureSource/FeatureSourceMerger"; import RememberingSource from "../FeatureSource/RememberingSource"; import WayHandlingApplyingFeatureSource from "../FeatureSource/WayHandlingApplyingFeatureSource"; -import NoOverlapSource from "../FeatureSource/NoOverlapSource"; import FeatureDuplicatorPerLayer from "../FeatureSource/FeatureDuplicatorPerLayer"; import FeatureSource from "../FeatureSource/FeatureSource"; import {UIEventSource} from "../UIEventSource"; @@ -25,9 +24,8 @@ export default class FeaturePipeline implements FeatureSource { locationControl: UIEventSource) { const amendedOverpassSource = - new RememberingSource( - new NoOverlapSource(flayers, new FeatureDuplicatorPerLayer(flayers, - new LocalStorageSaver(updater, layout))) + new RememberingSource(new FeatureDuplicatorPerLayer(flayers, + new LocalStorageSaver(updater, layout)) ); const geojsonSources: GeoJsonSource [] = [] @@ -40,8 +38,7 @@ export default class FeaturePipeline implements FeatureSource { } const amendedLocalStorageSource = - new RememberingSource( - new NoOverlapSource(flayers, new FeatureDuplicatorPerLayer(flayers, new LocalStorageSource(layout))) + new RememberingSource(new FeatureDuplicatorPerLayer(flayers, new LocalStorageSource(layout)) ); newPoints = new FeatureDuplicatorPerLayer(flayers, newPoints); diff --git a/Logic/FeatureSource/NoOverlapSource.ts b/Logic/FeatureSource/NoOverlapSource.ts deleted file mode 100644 index c2af365328..0000000000 --- a/Logic/FeatureSource/NoOverlapSource.ts +++ /dev/null @@ -1,91 +0,0 @@ -import LayerConfig from "../../Customizations/JSON/LayerConfig"; -import FeatureSource from "./FeatureSource"; -import {UIEventSource} from "../UIEventSource"; -import {GeoOperations} from "../GeoOperations"; - -/** - * The no overlap source takes a featureSource and applies a filter on it. - * First, it'll figure out for each feature to which layer it belongs - * Then, it'll check any feature of any 'lower' layer - */ -export default class NoOverlapSource { - - features: UIEventSource<{ feature: any, freshness: Date }[]> = new UIEventSource<{ feature: any, freshness: Date }[]>([]); - - constructor(layers: UIEventSource<{ - layerDef: LayerConfig - }[]>, - upstream: FeatureSource) { - let noOverlapRemoval = true; - for (const layer of layers.data) { - if ((layer.layerDef.hideUnderlayingFeaturesMinPercentage ?? 0) !== 0) { - noOverlapRemoval = false; - } - } - if (noOverlapRemoval) { - this.features = upstream.features; - return; - } - - this.features = upstream.features.map( - features => { - if (features === undefined) { - return; - } - - const layerIds = [] - const layerDict = {}; - for (const layer of layers.data) { - layerDict[layer.layerDef.id] = layer; - layerIds.push(layer.layerDef.id); - if ((layer.layerDef.hideUnderlayingFeaturesMinPercentage ?? 0) !== 0) { - noOverlapRemoval = false; - } - } - - // There is overlap removal active - // We partition all the features with their respective layerIDs - const partitions = {}; - for (const layerId of layerIds) { - partitions[layerId] = [] - } - for (const feature of features) { - partitions[feature.feature._matching_layer_id].push(feature); - } - - // With this partitioning in hand, we run over every layer and remove every underlying feature if needed - for (let i = 0; i < layerIds.length; i++) { - let layerId = layerIds[i]; - const percentage = layerDict[layerId].layerDef.hideUnderlayingFeaturesMinPercentage ?? 0; - if (percentage === 0) { - // We don't have to remove underlying features! - continue; - } - const guardPartition = partitions[layerId]; - for (let j = i + 1; j < layerIds.length; j++) { - let layerJd = layerIds[j]; - let partitionToShrink: { feature: any, freshness: Date }[] = partitions[layerJd]; - let newPartition = []; - for (const mightBeDeleted of partitionToShrink) { - const doesOverlap = GeoOperations.featureIsContainedInAny( - mightBeDeleted.feature, - guardPartition.map(f => f.feature), - percentage - ); - if (!doesOverlap) { - newPartition.push(mightBeDeleted); - } - } - partitions[layerJd] = newPartition; - } - } - - // At last, we create the actual new features - let newFeatures: { feature: any, freshness: Date }[] = []; - for (const layerId of layerIds) { - newFeatures = newFeatures.concat(partitions[layerId]); - } - return newFeatures; - }); - } -} \ No newline at end of file diff --git a/Logic/GeoOperations.ts b/Logic/GeoOperations.ts index 4011b5c2ec..e239951cb1 100644 --- a/Logic/GeoOperations.ts +++ b/Logic/GeoOperations.ts @@ -30,67 +30,61 @@ export class GeoOperations { return turf.distance(lonlat0, lonlat1) } - static featureIsContainedInAny(feature: any, - shouldNotContain: any[], - maxOverlapPercentage: number): boolean { - // Returns 'false' if no problematic intersection is found + /** + * Calculates the overlap of 'feature' with every other specified feature. + * The features with which 'feature' overlaps, are returned together with their overlap area in m² + * + * If 'feature' is a point, it will return every feature the point is embedded in. Overlap will be undefined + */ + static calculateOverlap(feature: any, + otherFeatures: any[]): { feat: any, overlap: number }[] { + const featureBBox = BBox.get(feature); + const result : { feat: any, overlap: number }[] = []; if (feature.geometry.type === "Point") { const coor = feature.geometry.coordinates; - for (const shouldNotContainElement of shouldNotContain) { + for (const otherFeature of otherFeatures) { - let shouldNotContainBBox = BBox.get(shouldNotContainElement); - let featureBBox = BBox.get(feature); - if (!featureBBox.overlapsWith(shouldNotContainBBox)) { + let otherFeatureBBox = BBox.get(otherFeature); + if (!featureBBox.overlapsWith(otherFeatureBBox)) { continue; } - if (this.inside(coor, shouldNotContainElement)) { - return true + if (this.inside(coor, otherFeatures)) { + result.push({ feat: otherFeatures, overlap: undefined }) } } - return false; + return result; } if (feature.geometry.type === "Polygon" || feature.geometry.type === "MultiPolygon") { - const poly = feature; - let featureBBox = BBox.get(feature); - const featureSurface = GeoOperations.surfaceAreaInSqMeters(poly); - for (const shouldNotContainElement of shouldNotContain) { - - const shouldNotContainBBox = BBox.get(shouldNotContainElement); - const overlaps = featureBBox.overlapsWith(shouldNotContainBBox) + for (const otherFeature of otherFeatures) { + const otherFeatureBBox = BBox.get(otherFeature); + const overlaps = featureBBox.overlapsWith(otherFeatureBBox) if (!overlaps) { continue; } // Calculate the surface area of the intersection - // If it is too big, refuse try { - const intersection = turf.intersect(poly, shouldNotContainElement); + const intersection = turf.intersect(feature, otherFeature); if (intersection == null) { continue; } - const intersectionSize = turf.area(intersection); - const ratio = intersectionSize / featureSurface; - - if (ratio * 100 >= maxOverlapPercentage) { - console.log("Refused", poly.id, " due to ", shouldNotContainElement.id, "intersection ratio is ", ratio, "which is bigger then the target ratio of ", (maxOverlapPercentage / 100)) - return true; - } + const intersectionSize = turf.area(intersection); // in m² + result.push({feat: otherFeature, overlap: intersectionSize}) } catch (exception) { console.log("EXCEPTION CAUGHT WHILE INTERSECTING: ", exception); - // We assume that this failed due to an intersection - return true; } } - return false; // No problematic intersections found + return result; } - - return false; + console.error("Could not correctly calculate the overlap of ", feature, ": unsupported type") + return result; } + public static inside(pointCoordinate, feature): boolean { // ray-casting algorithm based on // http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html diff --git a/Logic/MetaTagging.ts b/Logic/MetaTagging.ts index 46cf5bbe4e..b405947f20 100644 --- a/Logic/MetaTagging.ts +++ b/Logic/MetaTagging.ts @@ -26,11 +26,21 @@ export default class MetaTagging { } // The functions - per layer - which add the new keys - const layerFuncs = new Map void)>(); + const layerFuncs = new Map, feature: any) => void)>(); for (const layer of layers) { layerFuncs.set(layer.id, this.createRetaggingFunc(layer)); } + const featuresPerLayer = new Map(); + for (const feature of features) { + + const key = feature.feature._matching_layer_id; + if (!featuresPerLayer.has(key)) { + featuresPerLayer.set(key, []) + } + featuresPerLayer.get(key).push(feature.feature) + } + for (const feature of features) { // @ts-ignore const key = feature.feature._matching_layer_id; @@ -39,19 +49,19 @@ export default class MetaTagging { continue; } - f(feature.feature) + f(featuresPerLayer, feature.feature) } } - private static createRetaggingFunc(layer: LayerConfig): ((feature: any) => void) { + private static createRetaggingFunc(layer: LayerConfig): ((featuresPerLayer: Map, feature: any) => void) { const calculatedTags: [string, string][] = layer.calculatedTags; if (calculatedTags === undefined) { return undefined; } - const functions: ((feature: any) => void)[] = []; + const functions: ((featuresPerLayer: Map, feature: any) => void)[] = []; for (const entry of calculatedTags) { const key = entry[0] const code = entry[1]; @@ -61,26 +71,24 @@ export default class MetaTagging { const func = new Function("feat", "return " + code + ";"); - const f = (feature: any) => { + const f = (featuresPerLayer, feature: any) => { feature.properties[key] = func(feature); } functions.push(f) } - return (feature) => { + return (featuresPerLayer: Map, feature) => { const tags = feature.properties if (tags === undefined) { return; } - ExtraFunction.FullPatchFeature(feature); - - for (const f of functions) { - try { - f(feature); - } catch (e) { - console.error("While calculating a tag value: ", e) + ExtraFunction.FullPatchFeature(featuresPerLayer, feature); + try { + for (const f of functions) { + f(featuresPerLayer, feature); } - + } catch (e) { + console.error("While calculating a tag value: ", e) } } } diff --git a/Logic/SimpleMetaTagger.ts b/Logic/SimpleMetaTagger.ts index b9f0b304f1..460993982b 100644 --- a/Logic/SimpleMetaTagger.ts +++ b/Logic/SimpleMetaTagger.ts @@ -49,7 +49,7 @@ export default class SimpleMetaTagger { const sqMeters = GeoOperations.surfaceAreaInSqMeters(feature); feature.properties["_surface"] = "" + sqMeters; feature.properties["_surface:ha"] = "" + Math.floor(sqMeters / 1000) / 10; - + feature.area = sqMeters; }) ); private static country = new SimpleMetaTagger( diff --git a/Logic/Web/Wikimedia.ts b/Logic/Web/Wikimedia.ts index 37be85dd95..bf68d22557 100644 --- a/Logic/Web/Wikimedia.ts +++ b/Logic/Web/Wikimedia.ts @@ -5,13 +5,13 @@ import * as $ from "jquery" */ export class Wikimedia { + private static knownLicenses = {}; + static ImageNameToUrl(filename: string, width: number = 500, height: number = 200): string { filename = encodeURIComponent(filename); return "https://commons.wikimedia.org/wiki/Special:FilePath/" + filename + "?width=" + width + "&height=" + height; } - private static knownLicenses = {}; - static LicenseData(filename: string, handle: ((LicenseInfo) => void)): void { if (filename in this.knownLicenses) { return this.knownLicenses[filename]; @@ -42,8 +42,9 @@ export class Wikimedia { } - static GetCategoryFiles(categoryName: string, handleCategory: ((ImagesInCategory) => void), - alreadyLoaded = 0, continueParameter: { k: string, param: string } = undefined) { + static GetCategoryFiles(categoryName: string, handleCategory: ((ImagesInCategory: ImagesInCategory) => void), + alreadyLoaded = 0, + continueParameter: { k: string, param: string } = undefined) { if (categoryName === undefined || categoryName === null || categoryName === "") { return; } @@ -58,7 +59,8 @@ export class Wikimedia { if (continueParameter !== undefined) { url = url + "&" + continueParameter.k + "=" + continueParameter.param; } - + const self = this; + console.log("Loading a wikimedia category: ", url) $.getJSON(url, (response) => { let imageOverview = new ImagesInCategory(); let members = response.query?.categorymembers; @@ -67,21 +69,27 @@ export class Wikimedia { } for (const member of members) { - imageOverview.images.push(member.title); } - if (response.continue === undefined || alreadyLoaded > 30) { + console.log("Got images! ", imageOverview) + if (response.continue === undefined) { handleCategory(imageOverview); - } else { - console.log("Recursive load for ", categoryName) - this.GetCategoryFiles(categoryName, (recursiveImages) => { - for (const image of imageOverview.images) { - recursiveImages.images.push(image); - } + return; + } + + if (alreadyLoaded > 10) { + console.log(`Recursive wikimedia category load stopped for ${categoryName} - got already enough images now (${alreadyLoaded})`) + handleCategory(imageOverview) + return; + } + + self.GetCategoryFiles(categoryName, + (recursiveImages) => { + recursiveImages.images.push(...imageOverview.images); handleCategory(recursiveImages); }, - alreadyLoaded + 10, {k: "cmcontinue", param: response.continue.cmcontinue}) - } + alreadyLoaded + 10, + {k: "cmcontinue", param: response.continue.cmcontinue}) }); } @@ -102,8 +110,7 @@ export class Wikimedia { handleWikidata(wd); }); } - - + } diff --git a/UI/CustomGenerator/LayerPanel.ts b/UI/CustomGenerator/LayerPanel.ts index 5375eb60b9..18eb8ec73f 100644 --- a/UI/CustomGenerator/LayerPanel.ts +++ b/UI/CustomGenerator/LayerPanel.ts @@ -98,10 +98,6 @@ export default class LayerPanel extends UIElement { {value: 2, shown: "Show both the ways/areas and the centerpoints"}, {value: 1, shown: "Show everything as centerpoint"}]), "wayHandling", "Way handling", "Describes how ways and areas are represented on the map: areas can be represented as the area itself, or it can be converted into the centerpoint"), - setting(ValidatedTextField.NumberInput("int", n => n <= 100), "hideUnderlayingFeaturesMinPercentage", "Max allowed overlap percentage", - "Consider that we want to show 'Nature Reserves' and 'Forests'. Now, ofter, there are pieces of forest mapped _in_ the nature reserve.
" + - "Now, showing those pieces of forest overlapping with the nature reserve truly clutters the map and is very user-unfriendly.
" + - "The features are placed layer by layer. If a feature below a feature on this layer overlaps for more then 'x'-percent, the underlying feature is hidden."), setting(new AndOrTagInput(), ["osmSource","overpassTags"], "Overpass query", "The tags of the objects to load from overpass"), diff --git a/UI/SpecialVisualizations.ts b/UI/SpecialVisualizations.ts index 72772e107b..0307fa7d14 100644 --- a/UI/SpecialVisualizations.ts +++ b/UI/SpecialVisualizations.ts @@ -59,7 +59,7 @@ export default class SpecialVisualizations { constr: (state: State, tags, args) => { const imagePrefix = args[0]; const loadSpecial = args[1].toLowerCase() === "true"; - const searcher: UIEventSource<{ key: string, url: string }[]> = new ImageSearcher(tags, imagePrefix, loadSpecial); + const searcher: UIEventSource<{ key: string, url: string }[]> = ImageSearcher.construct(tags, imagePrefix, loadSpecial); return new ImageCarousel(searcher, tags); } diff --git a/assets/themes/buurtnatuur/buurtnatuur.json b/assets/themes/buurtnatuur/buurtnatuur.json index 8e610ed2d6..a6e6cc9e4d 100644 --- a/assets/themes/buurtnatuur/buurtnatuur.json +++ b/assets/themes/buurtnatuur/buurtnatuur.json @@ -25,7 +25,7 @@ "startLat": 50.8435, "startLon": 4.3688, "startZoom": 16, - "widenFactor": 0.05, + "widenFactor": 0.01, "socialImage": "./assets/themes/buurtnatuur/social_image.jpg", "layers": [ { @@ -75,7 +75,6 @@ "tagRenderings": [ "images" ], - "hideUnderlayingFeaturesMinPercentage": 10, "icon": { "render": "circle:#ffffff;./assets/themes/buurtnatuur/nature_reserve.svg" }, @@ -141,6 +140,19 @@ ] } }, + "calculatedTags": [ + "_overlapWithUpperLayers=Math.max(...feat.overlapWith('nature_reserve').map(o => o.overlap))/feat.area", + "_tooMuchOverlap=Number(feat.properties._overlapWithUpperLayers) > 0.1 ? 'yes' :'no'" + ], + "isShown": { + "render": "yes", + "mappings": [ + { + "if": "_tooMuchOverlap=yes", + "then": "no" + } + ] + }, "title": { "render": { "nl": "Park" @@ -149,7 +161,7 @@ { "if": { "and": [ - "name:nl~" + "name:nl~*" ] }, "then": { @@ -174,7 +186,6 @@ "tagRenderings": [ "images" ], - "hideUnderlayingFeaturesMinPercentage": 10, "icon": { "render": "circle:#ffffff;./assets/themes/buurtnatuur/park.svg" }, @@ -228,6 +239,19 @@ ] } }, + "calculatedTags": [ + "_overlapWithUpperLayers=Math.max(...feat.overlapWith('parks','nature_reserve').map(o => o.overlap))/feat.area", + "_tooMuchOverlap=Number(feat.properties._overlapWithUpperLayers) > 0.1 ? 'yes' : 'no'" + ], + "isShown": { + "render": "yes", + "mappings": [ + { + "if": "_tooMuchOverlap=yes", + "then": "no" + } + ] + }, "title": { "render": { "nl": "Bos" @@ -236,7 +260,7 @@ { "if": { "and": [ - "name:nl~" + "name:nl~*" ] }, "then": { @@ -261,7 +285,6 @@ "tagRenderings": [ "images" ], - "hideUnderlayingFeaturesMinPercentage": 0, "icon": { "render": "circle:#ffffff;./assets/themes/buurtnatuur/forest.svg" },