Refactoring: fix metatagging

This commit is contained in:
Pieter Vander Vennet 2023-05-16 03:27:49 +02:00
parent 177697fe0a
commit 8fd3fbc0b7
34 changed files with 378 additions and 265 deletions

View file

@ -1,10 +1,11 @@
import { GeoOperations } from "./GeoOperations"
import {GeoOperations} from "./GeoOperations"
import Combine from "../UI/Base/Combine"
import BaseUIElement from "../UI/BaseUIElement"
import List from "../UI/Base/List"
import Title from "../UI/Base/Title"
import { BBox } from "./BBox"
import { Feature, Geometry, MultiPolygon, Polygon } from "geojson"
import {BBox} from "./BBox"
import {Feature, Geometry, MultiPolygon, Polygon} from "geojson"
import {GeoJSONFeature} from "maplibre-gl";
export interface ExtraFuncParams {
/**
@ -12,7 +13,7 @@ export interface ExtraFuncParams {
* Note that more features then requested can be given back.
* Format: [ [ geojson, geojson, geojson, ... ], [geojson, ...], ...]
*/
getFeaturesWithin: (layerId: string, bbox: BBox) => Feature<Geometry, Record<string, string>>[]
getFeaturesWithin: (layerId: string, bbox: BBox) => Feature<Geometry, Record<string, string>>[][]
getFeatureById: (id: string) => Feature<Geometry, Record<string, string>>
}
@ -52,24 +53,27 @@ class EnclosingFunc implements ExtraFunction {
if (otherFeaturess.length === 0) {
continue
}
for (const otherFeature of otherFeaturess) {
if (seenIds.has(otherFeature.properties.id)) {
continue
}
seenIds.add(otherFeature.properties.id)
if (
otherFeature.geometry.type !== "Polygon" &&
otherFeature.geometry.type !== "MultiPolygon"
) {
continue
}
if (
GeoOperations.completelyWithin(
<Feature>feat,
<Feature<Polygon | MultiPolygon, any>>otherFeature
)
) {
result.push({ feat: otherFeature })
for (const otherFeatures of otherFeaturess) {
for (const otherFeature of otherFeatures) {
if (seenIds.has(otherFeature.properties.id)) {
continue
}
seenIds.add(otherFeature.properties.id)
if (
otherFeature.geometry.type !== "Polygon" &&
otherFeature.geometry.type !== "MultiPolygon"
) {
continue
}
if (
GeoOperations.completelyWithin(
<Feature>feat,
<Feature<Polygon | MultiPolygon, any>>otherFeature
)
) {
result.push({feat: otherFeature})
}
}
}
}
@ -157,7 +161,7 @@ class IntersectionFunc implements ExtraFunction {
if (intersections.length === 0) {
continue
}
result.push({ feat: otherFeature, intersections })
result.push({feat: otherFeature, intersections})
}
}
@ -241,20 +245,22 @@ class ClosestNObjectFunc implements ExtraFunction {
static GetClosestNFeatures(
params: ExtraFuncParams,
feature: any,
features: string | any[],
features: string | Feature[],
options?: { maxFeatures?: number; uniqueTag?: string | undefined; maxDistance?: number }
): { feat: any; distance: number }[] {
const maxFeatures = options?.maxFeatures ?? 1
const maxDistance = options?.maxDistance ?? 500
const uniqueTag: string | undefined = options?.uniqueTag
console.log("Calculating 'closestn' features")
let allFeatures: Feature[][]
if (typeof features === "string") {
const name = features
const bbox = GeoOperations.bbox(
GeoOperations.buffer(GeoOperations.bbox(feature), maxDistance)
)
features = params.getFeaturesWithin(name, new BBox(bbox.geometry.coordinates))
allFeatures = params.getFeaturesWithin(name, new BBox(bbox.geometry.coordinates))
} else {
features = [features]
allFeatures = [features]
}
if (features === undefined) {
return
@ -263,9 +269,9 @@ class ClosestNObjectFunc implements ExtraFunction {
const selfCenter = GeoOperations.centerpointCoordinates(feature)
let closestFeatures: { feat: any; distance: number }[] = []
for (const featureList of features) {
// Features is provided by 'getFeaturesWithin' which returns a list of lists of features, hence the double loop here
for (const otherFeature of featureList) {
for (const feats of allFeatures) {
for (const otherFeature of feats) {
if (
otherFeature === feature ||
otherFeature.properties.id === feature.properties.id
@ -333,7 +339,7 @@ class ClosestNObjectFunc implements ExtraFunction {
// We want to see the tag `uniquetag=some_value` only once in the entire list (e.g. to prevent road segements of identical names to fill up the list of 'names of nearby roads')
// AT this point, we have found a closer segment with the same, identical tag
// so we replace directly
closestFeatures[i] = { feat: otherFeature, distance: distance }
closestFeatures[i] = {feat: otherFeature, distance: distance}
}
break
}
@ -423,6 +429,8 @@ class GetParsed implements ExtraFunction {
}
}
export type ExtraFuncType = typeof ExtraFunctions.types[number]
export class ExtraFunctions {
static readonly intro = new Combine([
new Title("Calculating tags with Javascript", 2),
@ -440,7 +448,7 @@ export class ExtraFunctions {
'"calculatedTags": [',
' "_someKey=javascript-expression",',
' "name=feat.properties.name ?? feat.properties.ref ?? feat.properties.operator",',
" \"_distanceCloserThen3Km=feat.distanceTo( some_lon, some_lat) < 3 ? 'yes' : 'no'\" ",
" \"_distanceCloserThen3Km=distanceTo(feat)( some_lon, some_lat) < 3 ? 'yes' : 'no'\" ",
" ]",
"````",
"",
@ -455,7 +463,8 @@ export class ExtraFunctions {
.SetClass("flex-col")
.AsMarkdown()
private static readonly allFuncs: ExtraFunction[] = [
static readonly types = ["distanceTo", "overlapWith", "enclosingFeatures", "intersectionsWith", "closest", "closestn", "get"] as const
private static readonly allFuncs = [
new DistanceToFunc(),
new OverlapFunc(),
new EnclosingFunc(),
@ -465,14 +474,16 @@ export class ExtraFunctions {
new GetParsed(),
]
public static FullPatchFeature(params: ExtraFuncParams, feature) {
if (feature._is_patched) {
return
}
feature._is_patched = true
for (const func of ExtraFunctions.allFuncs) {
feature[func._name] = func._f(params, feature)
public static constructHelpers(params: ExtraFuncParams): Record<ExtraFuncType, (feature: Feature) => Function> {
const record: Record<string, (feature: GeoJSONFeature) => Function> = {}
for (const f of ExtraFunctions.allFuncs) {
if (this.types.indexOf(<any>f._name) < 0) {
throw "Invalid extraFunc-type: " + f._name
}
record[f._name] = (feat) => f._f(params, feat)
}
return record
}
public static HelpText(): BaseUIElement {

View file

@ -23,8 +23,7 @@ export default class GeoIndexedStore implements FeatureSource {
* @param bbox
* @constructor
*/
public GetFeaturesWithin(bbox: BBox, strict: boolean = false): Feature[] {
// TODO optimize
public GetFeaturesWithin(bbox: BBox): Feature[] {
const bboxFeature = bbox.asGeojsonCached()
return this.features.data.filter((f) => {
if (f.geometry.type === "Point") {
@ -40,7 +39,6 @@ export default class GeoIndexedStore implements FeatureSource {
if (f.geometry.type === "Polygon" || f.geometry.type === "MultiPolygon") {
return GeoOperations.intersect(f, bboxFeature) !== undefined
}
console.log("Calculating intersection between", bboxFeature, "and", f)
return GeoOperations.intersect(f, bboxFeature) !== undefined
})
}

View file

@ -1,12 +1,14 @@
import SimpleMetaTaggers, { MetataggingState, SimpleMetaTagger } from "./SimpleMetaTagger"
import { ExtraFuncParams, ExtraFunctions } from "./ExtraFunctions"
import SimpleMetaTaggers, {MetataggingState, SimpleMetaTagger} from "./SimpleMetaTagger"
import {ExtraFuncParams, ExtraFunctions, ExtraFuncType} from "./ExtraFunctions"
import LayerConfig from "../Models/ThemeConfig/LayerConfig"
import { Feature } from "geojson"
import {Feature} from "geojson"
import FeaturePropertiesStore from "./FeatureSource/Actors/FeaturePropertiesStore"
import LayoutConfig from "../Models/ThemeConfig/LayoutConfig"
import { GeoIndexedStoreForLayer } from "./FeatureSource/Actors/GeoIndexedStore"
import { IndexedFeatureSource } from "./FeatureSource/FeatureSource"
import {GeoIndexedStoreForLayer} from "./FeatureSource/Actors/GeoIndexedStore"
import {IndexedFeatureSource} from "./FeatureSource/FeatureSource"
import OsmObjectDownloader from "./Osm/OsmObjectDownloader"
import {Utils} from "../Utils";
import {GeoJSONFeature} from "maplibre-gl";
/**
* Metatagging adds various tags to the elements, e.g. lat, lon, surface area, ...
@ -27,8 +29,16 @@ export default class MetaTagging {
}) {
const params: ExtraFuncParams = {
getFeatureById: (id) => state.indexedFeatures.featuresById.data.get(id),
getFeaturesWithin: (layerId, bbox) =>
state.perLayer.get(layerId).GetFeaturesWithin(bbox),
getFeaturesWithin: (layerId, bbox) => {
if(layerId === '*' || layerId === null || layerId === undefined){
const feats: Feature[][] = []
state.perLayer.forEach((layer) => {
feats.push(layer.GetFeaturesWithin(bbox))
})
return feats
}
return [state.perLayer.get(layerId).GetFeaturesWithin(bbox)];
},
}
for (const layer of state.layout.layers) {
if (layer.source === null) {
@ -60,7 +70,7 @@ export default class MetaTagging {
}
/**
* This method (re)calculates all metatags and calculated tags on every given object.
* This method (re)calculates all metatags and calculated tags on every given feature.
* The given features should be part of the given layer
*
* Returns true if at least one feature has changed properties
@ -96,16 +106,26 @@ export default class MetaTagging {
}
// The calculated functions - per layer - which add the new keys
const layerFuncs = this.createRetaggingFunc(layer)
// Calculated functions are defined by the layer
const layerFuncs = this.createRetaggingFunc(layer, ExtraFunctions.constructHelpers(params))
const state: MetataggingState = { layout, osmObjectDownloader }
let atLeastOneFeatureChanged = false
let strictlyEvaluated = 0
for (let i = 0; i < features.length; i++) {
const feature = features[i]
const tags = featurePropertiesStores?.getStore(feature.properties.id)
let somethingChanged = false
let definedTags = new Set(Object.getOwnPropertyNames(feature.properties))
if (layerFuncs !== undefined) {
let retaggingChanged = false
try {
retaggingChanged = layerFuncs(feature)
} catch (e) {
console.error(e)
}
somethingChanged = somethingChanged || retaggingChanged
}
for (const metatag of metatagsToApply) {
try {
if (!metatag.keys.some((key) => !(key in feature.properties))) {
@ -123,7 +143,10 @@ export default class MetaTagging {
metatag.applyMetaTagsOnFeature(feature, layer, tags, state)
if (options?.evaluateStrict) {
for (const key of metatag.keys) {
feature.properties[key]
const evaluated = feature.properties[key]
if(evaluated !== undefined){
strictlyEvaluated++
}
}
}
} else {
@ -153,15 +176,7 @@ export default class MetaTagging {
}
}
if (layerFuncs !== undefined) {
let retaggingChanged = false
try {
retaggingChanged = layerFuncs(params, feature)
} catch (e) {
console.error(e)
}
somethingChanged = somethingChanged || retaggingChanged
}
if (somethingChanged) {
try {
@ -175,91 +190,83 @@ export default class MetaTagging {
return atLeastOneFeatureChanged
}
private static createFunctionsForFeature(
layerId: string,
calculatedTags: [string, string, boolean][]
): ((feature: any) => void)[] {
const functions: ((feature: any) => any)[] = []
for (const entry of calculatedTags) {
const key = entry[0]
const code = entry[1]
const isStrict = entry[2]
if (code === undefined) {
continue
}
/**
* Creates a function that implements that calculates a property and adds this property onto the feature properties
* @param specification
* @param helperFunctions
* @param layerId
* @private
*/
private static createFunctionForFeature( [key, code, isStrict]: [string, string, boolean],
helperFunctions: Record<ExtraFuncType, (feature: Feature) => Function>,
layerId: string = "unkown layer"
): ((feature: GeoJSONFeature) => void) | undefined {
if (code === undefined) {
return undefined
}
const calculateAndAssign: (feat: any) => any = (feat) => {
try {
let result = new Function("feat", "return " + code + ";")(feat)
if (result === "") {
result === undefined
}
if (result !== undefined && typeof result !== "string") {
// Make sure it is a string!
result = JSON.stringify(result)
}
delete feat.properties[key]
feat.properties[key] = result
return result
} catch (e) {
if (MetaTagging.errorPrintCount < MetaTagging.stopErrorOutputAt) {
console.warn(
"Could not calculate a " +
(isStrict ? "strict " : "") +
" calculated tag for key " +
key +
" defined by " +
code +
" (in layer" +
layerId +
") due to \n" +
e +
"\n. Are you the theme creator? Doublecheck your code. Note that the metatags might not be stable on new features",
e,
e.stack
)
MetaTagging.errorPrintCount++
if (MetaTagging.errorPrintCount == MetaTagging.stopErrorOutputAt) {
console.error(
"Got ",
MetaTagging.stopErrorOutputAt,
" errors calculating this metatagging - stopping output now"
)
}
}
return undefined
const calculateAndAssign: ((feat: GeoJSONFeature) => (string | undefined)) = (feat) => {
try {
let result = new Function("feat", "{"+ExtraFunctions.types.join(", ")+"}", "return " + code + ";")(feat, helperFunctions)
if (result === "") {
result = undefined
}
if (result !== undefined && typeof result !== "string") {
// Make sure it is a string!
result = JSON.stringify(result)
}
delete feat.properties[key]
feat.properties[key] = result
return result
} catch (e) {
if (MetaTagging.errorPrintCount < MetaTagging.stopErrorOutputAt) {
console.warn(
"Could not calculate a " +
(isStrict ? "strict " : "") +
" calculated tag for key " +
key +
" defined by " +
code +
" (in layer" +
layerId +
") due to \n" +
e +
"\n. Are you the theme creator? Doublecheck your code. Note that the metatags might not be stable on new features",
e,
e.stack
)
MetaTagging.errorPrintCount++
if (MetaTagging.errorPrintCount == MetaTagging.stopErrorOutputAt) {
console.error(
"Got ",
MetaTagging.stopErrorOutputAt,
" errors calculating this metatagging - stopping output now"
)
}
}
}
if (isStrict) {
functions.push(calculateAndAssign)
continue
}
// Lazy function
const f = (feature: any) => {
delete feature.properties[key]
Object.defineProperty(feature.properties, key, {
configurable: true,
enumerable: false, // By setting this as not enumerable, the localTileSaver will _not_ calculate this
get: function () {
return calculateAndAssign(feature)
},
})
return undefined
}
functions.push(f)
}
return functions
if(isStrict){
return calculateAndAssign
}
return (feature: any) => {
delete feature.properties[key]
Utils.AddLazyProperty(feature.properties, key, () => calculateAndAssign(feature))
return undefined
}
}
/**
* Creates the function which adds all the calculated tags to a feature. Called once per layer
*/
private static createRetaggingFunc(
layer: LayerConfig
): (params: ExtraFuncParams, feature: any) => boolean {
layer: LayerConfig,
helpers: Record<ExtraFuncType, (feature: Feature) => Function>
): (feature: any) => boolean {
const calculatedTags: [string, string, boolean][] = layer.calculatedTags
if (calculatedTags === undefined || calculatedTags.length === 0) {
return undefined
@ -267,18 +274,17 @@ export default class MetaTagging {
let functions: ((feature: Feature) => void)[] = MetaTagging.retaggingFuncCache.get(layer.id)
if (functions === undefined) {
functions = MetaTagging.createFunctionsForFeature(layer.id, calculatedTags)
functions = calculatedTags.map(spec => this.createFunctionForFeature(spec, helpers, layer.id))
MetaTagging.retaggingFuncCache.set(layer.id, functions)
}
return (params: ExtraFuncParams, feature) => {
return (feature: Feature) => {
const tags = feature.properties
if (tags === undefined) {
return
}
try {
ExtraFunctions.FullPatchFeature(params, feature)
for (const f of functions) {
f(feature)
}

View file

@ -96,16 +96,15 @@ export default class DependencyCalculator {
return []
},
}
// Init the extra patched functions...
ExtraFunctions.FullPatchFeature(params, obj)
const helpers = ExtraFunctions.constructHelpers(params)
// ... Run the calculated tag code, which will trigger the getFeaturesWithin above...
for (let i = 0; i < layer.calculatedTags.length; i++) {
const [key, code] = layer.calculatedTags[i]
currentLine = i // Leak the state...
currentKey = key
try {
const func = new Function("feat", "return " + code + ";")
const result = func(obj)
const func = new Function("feat", "{"+ExtraFunctions.types.join(",")+"}", "return " + code + ";")
const result = func(obj, helpers)
obj.properties[key] = JSON.stringify(result)
} catch (e) {}
}

View file

@ -0,0 +1,90 @@
<script lang="ts">
import {Store, UIEventSource} from "../../Logic/UIEventSource";
import type {RasterLayerPolygon} from "../../Models/RasterLayers";
import {AvailableRasterLayers} from "../../Models/RasterLayers";
import Tr from "../Base/Tr.svelte";
import {onDestroy} from "svelte";
import Translations from "../i18n/Translations";
import Svg from "../../Svg";
import {Map as MlMap} from "maplibre-gl"
import MaplibreMap from "../Map/MaplibreMap.svelte";
import {MapLibreAdaptor} from "../Map/MapLibreAdaptor";
import type {MapProperties} from "../../Models/MapProperties";
export let mapproperties: MapProperties
export let normalMap: UIEventSource<MlMap>
/**
* The current background (raster) layer of the polygon.
* This is undefined if a vector layer is used
*/
let rasterLayer: UIEventSource<RasterLayerPolygon | undefined> = mapproperties.rasterLayer
let name = rasterLayer.data?.properties?.name
let icon = Svg.satellite_svg()
onDestroy(rasterLayer.addCallback(polygon => {
name = polygon.properties?.name
}))
/**
* The layers that this component can offer as a choice.
*/
export let availableRasterLayers: Store<RasterLayerPolygon[]>
let altmap: UIEventSource<MlMap> = new UIEventSource(undefined)
let altproperties = new MapLibreAdaptor(altmap, {zoom: UIEventSource.feedFrom(mapproperties.zoom)})
altproperties.allowMoving.setData(false)
altproperties.allowZooming.setData(false)
let altmap0: UIEventSource<MlMap> = new UIEventSource(undefined)
let altproperties0 = new MapLibreAdaptor(altmap0, {zoom: altproperties.zoom})
// altproperties0.allowMoving.setData(false)
// altproperties0.allowZooming.setData(false)
function updatedAltLayer() {
const available = availableRasterLayers.data
const current = rasterLayer.data
const defaultLayer = AvailableRasterLayers.maplibre
const firstOther = available.find(l => l !== current && l !== defaultLayer)
altproperties.rasterLayer.setData(firstOther)
const secondOther = available.find(l => l !== current && l !== firstOther && l !== defaultLayer)
altproperties0.rasterLayer.setData(secondOther)
}
onDestroy(availableRasterLayers.addCallbackAndRunD(updatedAltLayer))
onDestroy(rasterLayer.addCallbackAndRunD(updatedAltLayer))
function pixelCenterOf(map: UIEventSource<MlMap>): [number, number] {
const rect = map?.data?.getCanvas()?.getBoundingClientRect()
if (!rect) {
return undefined
}
const x = (rect.left + rect.right) / 2
const y = (rect.top + rect.bottom) / 2
return [x, y]
}
mapproperties.location.addCallbackAndRunD(({lon, lat}) => {
if (!normalMap.data || !altmap.data) {
return
}
const altMapCenter = pixelCenterOf(altmap)
const c = normalMap.data.unproject(altMapCenter)
altproperties.location.setData({lon: c.lng, lat: c.lat})
const altMapCenter0 = pixelCenterOf(altmap0)
const c0 = normalMap.data.unproject(altMapCenter0)
altproperties0.location.setData({lon: c0.lng, lat: c0.lat})
})
</script>
<div class="flex">
<div class="w-32 h-32 overflow-hidden border-interactive">
<MaplibreMap map={altmap}/>
</div>
<div class="w-32 h-32 overflow-hidden border-interactive">
<MaplibreMap map={altmap0}/>
</div>
<div class="low-interaction flex flex-col">
<b>Current background:</b>
<Tr t={Translations.T(name)}/>
</div>
</div>

View file

@ -56,7 +56,7 @@
<textarea class="w-full" bind:value={$_value} inputmode={validator.inputmode ?? "text"} placeholder={_placeholder}></textarea>
{:else }
<span class="inline-flex">
<input bind:this={htmlElem} bind:value={$_value} inputmode={validator.inputmode ?? "text"} placeholder={_placeholder}>
<input bind:this={htmlElem} bind:value={$_value} class="w-full" inputmode={validator.inputmode ?? "text"} placeholder={_placeholder}>
{#if !$isValid}
<ExclamationIcon class="h-6 w-6 -ml-6"></ExclamationIcon>
{/if}

View file

@ -16,7 +16,7 @@
*/
export let map: Writable<MaplibreMap>
export let attribution = true
export let attribution = false
let center = {};
onMount(() => {

View file

@ -31,7 +31,7 @@
</script>
{#if config !== undefined && (config?.condition === undefined || config.condition.matchesProperties(_tags))}
<div class={"flex flex-col w-full "+classes+" "+extraClasses}>
<div class={"link-underline flex flex-col w-full "+classes+" "+extraClasses}>
{#if trs.length === 1}
<TagRenderingMapping mapping={trs[0]} {tags} {state} {selectedElement} {layer}></TagRenderingMapping>
{/if}

View file

@ -75,7 +75,7 @@
<button slot="cancel" class="secondary" on:click={() => {editMode = false}}>
<Tr t={Translations.t.general.cancel}/>
</button>
<XCircleIcon slot="upper-right" class="w-8 h-8" on:click={() => {editMode = false}}/>
<XCircleIcon slot="upper-right" class="w-8 h-8 cursor-pointer" on:click={() => {editMode = false}}/>
</TagRenderingQuestion>
{:else}
<div class="flex justify-between low-interaction items-center rounded px-2">

View file

@ -73,7 +73,9 @@
}>();
function onSave() {
if(selectedTags === undefined){
return
}
if (layer.source === null) {
/**
* This is a special, priviliged layer.
@ -191,16 +193,14 @@
<img slot="image" src="./assets/svg/login.svg" class="w-8 h-8"/>
<Tr t={Translations.t.general.loginToStart} slot="message"></Tr>
</SubtleButton>
<div class="flex justify-end">
{#if $feedback !== undefined}
<div class="alert">
<Tr t={$feedback}/>
</div>
{/if}
<div class="flex justify-end flex-wrap-reverse sm:flex-nowrap items-stretch">
<!-- TagRenderingQuestion-buttons -->
{#if $feedback !== undefined}
<div class="alert">
<Tr t={$feedback}/>
</div>
{/if}
<slot name="cancel"></slot>
<button on:click={onSave} class={(selectedTags === undefined ? "disabled" : "button-shadow")+" primary"}>
<Tr t={Translations.t.general.save}></Tr>
</button>

View file

@ -41,6 +41,7 @@
import {ShareScreen} from "./BigComponents/ShareScreen";
import NextButton from "./Base/NextButton.svelte";
import IfNot from "./Base/IfNot.svelte";
import BackgroundSwitcher from "./BigComponents/BackgroundSwitcher.svelte";
export let state: ThemeViewState;
let layout = state.layout;
@ -115,7 +116,7 @@
{selectedLayer}/>
</div>
</If>
<div class="float-left m-1 sm:mt-2">
<div class="float-left m-1 sm:mt-2 flex flex-col">
<MapControlButton on:click={() => state.guistate.themeIsOpened.setData(true)}>
<div class="flex m-0.5 mx-1 sm:mx-1 md:mx-2 items-center cursor-pointer max-[480px]:w-full">
<img class="w-6 h-6 md:w-8 md:h-8 block mr-0.5 sm:mr-1 md:mr-2" src={layout.icon}>
@ -136,16 +137,16 @@
{/if}
<ToSvelte construct={() => new ExtraLinkButton(state, layout.extraLink)}></ToSvelte>
<If condition={state.featureSwitchIsTesting}>
<span class="alert">
<div class="alert w-fit">
Testmode
</span>
</div>
</If>
</div>
</div>
<div class="absolute bottom-0 left-0 mb-4 ml-4">
<BackgroundSwitcher availableRasterLayers={state.availableLayers} mapproperties={state.mapProperties} normalMap={state.map}/>
</div>
<div class="absolute bottom-0 right-0 mb-4 mr-4 flex flex-col items-end">

View file

@ -1169,7 +1169,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
if (typeof window === "undefined") {
return "https://mapcomplete.osm.be"
}
const path = window.location.href.split("/")
const path = (window.location.protocol+ window.location.host + window.location.pathname) .split("/")
path.pop()
path.push("index.html")
return path.join("/")

View file

@ -31,7 +31,7 @@
}
},
"calculatedTags": [
"_closest_3_street_names=feat.closestn('named_streets',3, 'name').map(f => f.feat.properties.name)",
"_closest_3_street_names=closestn(feat)('named_streets',3, 'name').map(f => f.feat.properties.name)",
"_closest_street:0:name=JSON.parse(feat.properties._closest_3_street_names)[0]",
"_closest_street:1:name=JSON.parse(feat.properties._closest_3_street_names)[1]",
"_closest_street:2:name=JSON.parse(feat.properties._closest_3_street_names)[2]"

View file

@ -96,19 +96,19 @@
]
},
"calculatedTags": [
"_contained_climbing_routes_properties=feat.overlapWith('climbing_route').map(f => f.feat.properties).map(p => {return {id: p.id, name: p.name, 'climbing:grade:french': p['climbing:grade:french'], 'climbing:length': p['climbing:length']} })",
"_contained_climbing_routes=feat.get('_contained_climbing_routes_properties')?.map(p => `<li><a href='#${p.id}'>${p.name ?? 'climbing route'}</a> (<b class='climbing-${p['__difficulty:char']} rounded-full p-l-1 p-r-1'>${p['climbing:grade:french'] ?? 'unknown difficulty'}</b>, ${p['climbing:length'] ?? 'unkown length'} meter)</li>`).join('')",
"_contained_climbing_route_ids=feat.get('_contained_climbing_routes_properties')?.map(p => p.id)",
"_difficulty_hist=feat.get('_contained_climbing_routes_properties')?.map(p => p['climbing:grade:french'])?.filter(p => (p ?? null) !== null)?.sort()",
"_difficulty_max=feat.get('_difficulty_hist')?.at(-1)",
"_difficulty_min=feat.get('_difficulty_hist')?.at(0)",
"_length_hist=feat.get('_contained_climbing_routes_properties')?.map(p => p['climbing:length'])?.filter(p => (p ?? null) !== null)?.sort()",
"_length_max=feat.get('_length_hist')?.at(-1)",
"_length_min=feat.get('_length_hist')?.at(0)",
"_bolts_hist=feat.get('_contained_climbing_routes_properties')?.map(p => p['climbing:bolts'])?.filter(p => (p ?? null) !== null)?.sort()",
"_bolts_max=feat.get('_bolts_hist')?.at(-1)",
"_bolts_min=feat.get('_bolts_hist')?.at(0)",
"_contained_climbing_routes_count=feat.get('_contained_climbing_routes_properties')?.length"
"_contained_climbing_routes_properties=overlapWith(feat)('climbing_route').map(f => f.feat.properties).map(p => {return {id: p.id, name: p.name, 'climbing:grade:french': p['climbing:grade:french'], 'climbing:length': p['climbing:length']} })",
"_contained_climbing_routes=get(feat)('_contained_climbing_routes_properties')?.map(p => `<li><a href='#${p.id}'>${p.name ?? 'climbing route'}</a> (<b class='climbing-${p['__difficulty:char']} rounded-full p-l-1 p-r-1'>${p['climbing:grade:french'] ?? 'unknown difficulty'}</b>, ${p['climbing:length'] ?? 'unkown length'} meter)</li>`).join('')",
"_contained_climbing_route_ids=get(feat)('_contained_climbing_routes_properties')?.map(p => p.id)",
"_difficulty_hist=get(feat)('_contained_climbing_routes_properties')?.map(p => p['climbing:grade:french'])?.filter(p => (p ?? null) !== null)?.sort()",
"_difficulty_max=get(feat)('_difficulty_hist')?.at(-1)",
"_difficulty_min=get(feat)('_difficulty_hist')?.at(0)",
"_length_hist=get(feat)('_contained_climbing_routes_properties')?.map(p => p['climbing:length'])?.filter(p => (p ?? null) !== null)?.sort()",
"_length_max=get(feat)('_length_hist')?.at(-1)",
"_length_min=get(feat)('_length_hist')?.at(0)",
"_bolts_hist=get(feat)('_contained_climbing_routes_properties')?.map(p => p['climbing:bolts'])?.filter(p => (p ?? null) !== null)?.sort()",
"_bolts_max=get(feat)('_bolts_hist')?.at(-1)",
"_bolts_min=get(feat)('_bolts_hist')?.at(0)",
"_contained_climbing_routes_count=get(feat)('_contained_climbing_routes_properties')?.length"
],
"tagRenderings": [
"images",

View file

@ -44,9 +44,9 @@
}
},
"calculatedTags": [
"_closest_other_drinking_water=feat.closestn('drinking_water', 1, undefined, 5000).map(f => ({id: f.feat.id, distance: ''+f.distance}))[0]",
"_closest_other_drinking_water_id=JSON.parse(feat.properties._closest_other_drinking_water)?.id",
"_closest_other_drinking_water_distance=Math.floor(Number(JSON.parse(feat.properties._closest_other_drinking_water)?.distance))"
"_closest_other_drinking_water=closestn(feat)('drinking_water', 1, undefined, 5000).map(f => ({id: f.feat.id, distance: ''+f.distance}))[0]",
"_closest_other_drinking_water_id=get(feat)('_closest_other_drinking_water')?.id",
"_closest_other_drinking_water_distance=Math.floor(Number(get(feat)('_closest_other_drinking_water')?.distance))"
],
"minzoom": 13,
"presets": [

View file

@ -33,7 +33,7 @@
"fr": "Tous les objets dont létymologie est connue"
},
"calculatedTags": [
"_same_name_ids=feat.closestn('*', 250, undefined, 2500)?.filter(f => f.feat.properties.name === feat.properties.name)?.map(f => f.feat.properties.id)??[]"
"_same_name_ids=closestn(feat)('*', 250, undefined, 2500)?.filter(f => f.feat.properties.name === feat.properties.name)?.map(f => f.feat.properties.id)??[]"
],
"tagRenderings": [
{

View file

@ -37,12 +37,12 @@
]
},
"calculatedTags": [
"_total_comments:=feat.get('comments').length",
"_first_comment:=feat.get('comments')[0].text",
"_opened_by_anonymous_user:=feat.get('comments')[0].user === undefined",
"_first_user:=feat.get('comments')[0].user",
"_last_user:=(() => {const comms = feat.get('comments'); return comms[comms.length - 1].user})()",
"_first_user_id:=feat.get('comments')[0].uid",
"_total_comments:=get(feat)('comments').length",
"_first_comment:=get(feat)('comments')[0].text",
"_opened_by_anonymous_user:=get(feat)('comments')[0].user === undefined",
"_first_user:=get(feat)('comments')[0].user",
"_last_user:=(() => {const comms = get(feat)('comments'); return comms[comms.length - 1].user})()",
"_first_user_id:=get(feat)('comments')[0].uid",
"_is_import_note:=(() => {const lines = feat.properties['_first_comment'].split('\\n'); const matchesMapCompleteURL = lines.map(l => l.match(\".*https://mapcomplete.osm.be/\\([a-zA-Z_-]+\\)\\(.html\\).*#import\")); const matchedIndexes = matchesMapCompleteURL.map((doesMatch, i) => [doesMatch !== null, i]).filter(v => v[0]).map(v => v[1]); return matchedIndexes[0] })()"
],
"titleIcons": [

View file

@ -52,13 +52,13 @@
}
],
"calculatedTags": [
"_entrance_properties=feat.overlapWith('entrance')?.map(e => e.feat.properties)?.filter(p => p !== undefined && p.indoor !== 'door')",
"_entrance_properties_with_width=feat.get('_entrance_properties')?.filter(p => p['width'] !== undefined)",
"_entrances_count=feat.get('_entrance_properties').length",
"_entrances_count_without_width_count= feat.get('_entrances_count') - feat.get('_entrance_properties_with_width').length",
"_biggest_width= Math.max( feat.get('_entrance_properties').map(p => p.width))",
"_biggest_width_properties= /* Can be a list! */ feat.get('_entrance_properties').filter(p => p.width === feat.get('_biggest_width'))",
"_biggest_width_id=feat.get('_biggest_width_properties').id"
"_entrance_properties=overlapWith(feat)('entrance')?.map(e => e.feat.properties)?.filter(p => p !== undefined && p.indoor !== 'door')",
"_entrance_properties_with_width=get(feat)('_entrance_properties')?.filter(p => p['width'] !== undefined)",
"_entrances_count=get(feat)('_entrance_properties').length",
"_entrances_count_without_width_count= get(feat)('_entrances_count') - get(feat)('_entrance_properties_with_width').length",
"_biggest_width= Math.max( get(feat)('_entrance_properties').map(p => p.width))",
"_biggest_width_properties= /* Can be a list! */ get(feat)('_entrance_properties').filter(p => p.width === get(feat)('_biggest_width'))",
"_biggest_width_id=get(feat)('_biggest_width_properties').id"
],
"units": [
{

View file

@ -192,7 +192,7 @@
},
"minzoom": 19,
"calculatedTags": [
"_overlaps_with_buildings=feat.overlapWith('osm:buildings').filter(f => f.feat.properties.id.indexOf('-') < 0)",
"_overlaps_with_buildings=overlapWith(feat)('osm:buildings').filter(f => f.feat.properties.id.indexOf('-') < 0)",
"_overlaps_with=feat.get('_overlaps_with_buildings').find(f => f.overlap > 1 /* square meter */ )",
"_overlaps_with_properties=feat.get('_overlaps_with')?.feat?.properties",
"_overlap_percentage=Math.round(100 * (feat.get('_overlaps_with')?.overlap / feat.get('_overlaps_with_properties')['_surface:strict']))",
@ -365,7 +365,7 @@
},
"minzoom": 19,
"calculatedTags": [
"_closed_osm_addr:=feat.closest('osm:adresses').properties",
"_closed_osm_addr:=closest(feat)('osm:adresses').properties",
"_bag_obj:addr:housenumber=`${feat.properties.huisnummer}${feat.properties.huisletter}${(feat.properties.toevoeging != '') ? '-' : ''}${feat.properties.toevoeging}`",
"_bag_obj:ref:bag=Number(feat.properties.identificatie)",
"_bag_obj:source:date=new Date().toISOString().split('T')[0]",

View file

@ -150,7 +150,7 @@
}
},
"calculatedTags": [
"_overlapWithUpperLayers=Math.max(...feat.overlapWith('nature_reserve_buurtnatuur').map(o => o.overlap))/feat.area",
"_overlapWithUpperLayers=Math.max(...overlapWith(feat)('nature_reserve_buurtnatuur').map(o => o.overlap))/feat.area",
"_tooMuchOverlap=Number(feat.properties._overlapWithUpperLayers) > 0.1 ? 'yes' :'no'"
],
"isShown": "_tooMuchOverlap!=yes",
@ -250,7 +250,7 @@
}
},
"calculatedTags": [
"_overlapWithUpperLayers=Math.max(...feat.overlapWith('parks','nature_reserve_buurtnatuur').map(o => o.overlap))/feat.area",
"_overlapWithUpperLayers=Math.max(...overlapWith(feat)('parks','nature_reserve_buurtnatuur').map(o => o.overlap))/feat.area",
"_tooMuchOverlap=Number(feat.properties._overlapWithUpperLayers) > 0.1 ? 'yes' : 'no'"
],
"isShown": "_tooMuchOverlap!=yes",

View file

@ -99,7 +99,7 @@
}
],
"+calculatedTags": [
"_embedding_feature_properties=feat.overlapWith('climbing_area').map(f => f.feat.properties).filter(p => p !== undefined).map(p => {return{access: p.access, id: p.id, name: p.name, climbing: p.climbing, 'access:description': p['access:description']}})",
"_embedding_feature_properties=overlapWith(feat)('climbing_area').map(f => f.feat.properties).filter(p => p !== undefined).map(p => {return{access: p.access, id: p.id, name: p.name, climbing: p.climbing, 'access:description': p['access:description']}})",
"_embedding_features_with_access=feat.get('_embedding_feature_properties')?.filter(p => p.access !== undefined)?.at(0)",
"_embedding_feature_with_rock=feat.get('_embedding_feature_properties')?.filter(p => p.rock !== undefined)?.at(0)",
"_embedding_features_with_rock:rock=feat.get('_embedding_feature_with_rock')?.rock",

View file

@ -328,13 +328,13 @@
"builtin": "crab_address",
"override": {
"calculatedTags+": [
"_embedded_in=feat.overlapWith('osm-buildings').filter(b => /* Do not match newly created objects */ b.feat.properties.id.indexOf('-') < 0)[0]?.feat?.properties ?? {}",
"_embedded_in=overlapWith(feat)('osm-buildings').filter(b => /* Do not match newly created objects */ b.feat.properties.id.indexOf('-') < 0)[0]?.feat?.properties ?? {}",
"_embedding_nr=feat.get('_embedded_in')['addr:housenumber']+(feat.get('_embedded_in')['addr:unit'] ?? '')",
"_embedding_street=feat.get('_embedded_in')['addr:street']",
"_embedding_id=feat.get('_embedded_in').id",
"_closeby_addresses=feat.closestn('address',10,undefined,50).map(f => f.feat).filter(addr => addr.properties['addr:street'] == feat.properties['STRAATNM'] && feat.properties['HNRLABEL'] == addr.properties['addr:housenumber'] + (addr.properties['addr:unit']??'') ).length",
"_closeby_addresses=closestn(feat)('address',10,undefined,50).map(f => f.feat).filter(addr => addr.properties['addr:street'] == feat.properties['STRAATNM'] && feat.properties['HNRLABEL'] == addr.properties['addr:housenumber'] + (addr.properties['addr:unit']??'') ).length",
"_has_identical_closeby_address=feat.get('_closeby_addresses') >= 1 ? 'yes' : 'no'",
"_embedded_in_grb=feat.overlapWith('grb')[0]?.feat?.properties ?? {}",
"_embedded_in_grb=overlapWith(feat)('grb')[0]?.feat?.properties ?? {}",
"_embedding_nr_grb=feat.get('_embedded_in_grb')['addr:housenumber']",
"_embedding_street_grb=feat.get('_embedded_in_grb')['addr:street']"
],
@ -465,7 +465,7 @@
"name": "GRB geometries",
"title": "GRB outline",
"calculatedTags": [
"_overlaps_with_buildings=feat.overlapWith('osm-buildings').filter(f => f.feat.properties.id.indexOf('-') < 0)",
"_overlaps_with_buildings=overlapWith(feat)('osm-buildings').filter(f => f.feat.properties.id.indexOf('-') < 0)",
"_overlaps_with=feat.get('_overlaps_with_buildings').find(f => f.overlap > 1 /* square meter */ )",
"_osm_obj:source:ref=feat.get('_overlaps_with')?.feat?.properties['source:geometry:ref']",
"_osm_obj:id=feat.get('_overlaps_with')?.feat?.properties?.id",
@ -483,7 +483,7 @@
"_imported_osm_still_fresh= feat.properties['_osm_obj:source:date'] == feat.properties._grb_date",
"_target_building_type=feat.properties['_osm_obj:building'] === 'yes' ? feat.properties.building : (feat.properties['_osm_obj:building'] ?? feat.properties.building)",
"_building:min_level= feat.properties['fixme']?.startsWith('verdieping, correct the building tag, add building:level and building:min_level before upload in JOSM!') ? '1' : ''",
"_intersects_with_other_features=feat.intersectionsWith('generic_osm_object').map(f => \"<a href='https://osm.org/\"+f.feat.properties.id+\"' target='_blank'>\" + f.feat.properties.id + \"</a>\").join(', ')"
"_intersects_with_other_features=intersectionsWith(feat)('generic_osm_object').map(f => \"<a href='https://osm.org/\"+f.feat.properties.id+\"' target='_blank'>\" + f.feat.properties.id + \"</a>\").join(', ')"
],
"tagRenderings": [
{
@ -699,7 +699,7 @@
"builtin": "current_view",
"override": {
"calculatedTags": [
"_overlapping=Number(feat.properties.zoom) >= 16 ? feat.overlapWith('grb').map(ff => ff.feat.properties) : undefined",
"_overlapping=Number(feat.properties.zoom) >= 16 ? overlapWith(feat)('grb').map(ff => ff.feat.properties) : undefined",
"_applicable=feat.get('_overlapping')?.filter(p => (p._imported_osm_object_found === 'true' || p._intersects_with_other_features === ''))?.map(p => p.id)",
"_applicable_count=feat.get('_applicable')?.length"
],

View file

@ -568,7 +568,7 @@
}
],
"calculatedTags": [
"_embedded_cs=feat.overlapWith('mapcomplete-changes').map(f => f.feat.properties)",
"_embedded_cs=overlapWith(feat)('mapcomplete-changes').map(f => f.feat.properties)",
"_embedded_cs:themes=feat.get('_embedded_cs').map(cs => cs.theme)",
"_embedded_cs:users=feat.get('_embedded_cs').map(cs => cs['_last_edit:contributor'])"
],

View file

@ -266,7 +266,7 @@
}
],
"calculatedTags": [
"_embedded_cs=feat.overlapWith('mapcomplete-changes').map(f => f.feat.properties)",
"_embedded_cs=overlapWith(feat)('mapcomplete-changes').map(f => f.feat.properties)",
"_embedded_cs:themes=feat.get('_embedded_cs').map(cs => cs.theme)",
"_embedded_cs:users=feat.get('_embedded_cs').map(cs => cs['_last_edit:contributor'])"
],

View file

@ -420,8 +420,8 @@
"geoJson": "https://maproulette.org/api/v2/challenge/view/28012"
},
"calculatedTags": [
"_closest_osm_hotel=feat.closest('hotel')?.properties?.id",
"_closest_osm_hotel_distance=feat.distanceTo(feat.properties._closest_osm_hotel)",
"_closest_osm_hotel=closest(feat)('hotel')?.properties?.id",
"_closest_osm_hotel_distance=distanceTo(feat)(feat.properties._closest_osm_hotel)",
"_has_closeby_feature=Number(feat.properties._closest_osm_hotel_distance) < 50 ? 'yes' : 'no'"
],
"+tagRenderings": [
@ -480,7 +480,7 @@
],
"overrideAll": {
"+calculatedTags": [
"_enclosing_building=feat.enclosingFeatures('walls_and_buildings')?.map(f => f.feat.properties.id)?.at(0)"
"_enclosing_building=enclosingFeatures(feat)('walls_and_buildings')?.map(f => f.feat.properties.id)?.at(0)"
],
"tagRenderings+": [
{

View file

@ -186,9 +186,9 @@
}
},
"calculatedTags": [
"_postal_code_properties=(() => { const f = feat.overlapWith('postal_code_boundary'); if(f.length===0){return {};}; const p = f[0]?.feat?.properties; return {id:p.id, postal_code: p.postal_code, _closest_town_hall: p._closest_town_hall}; })()",
"_postal_code_properties=(() => { const f = overlapWith(feat)('postal_code_boundary'); if(f.length===0){return {};}; const p = f[0]?.feat?.properties; return {id:p.id, postal_code: p.postal_code, _closest_town_hall: p._closest_town_hall}; })()",
"_postal_code=feat.get('_postal_code_properties')?.postal_code",
"_postal_code_center_distance=feat.distanceTo(feat.get('_postal_code_properties').id)"
"_postal_code_center_distance=distanceTo(feat)(feat.get('_postal_code_properties').id)"
],
"description": {},
"tagRenderings": [],

View file

@ -56,7 +56,7 @@
},
"minzoom": 12,
"calculatedTags": [
"_is_shadowed=feat.overlapWith('shadow').length > 0 ? 'yes': ''",
"_is_shadowed=overlapWith(feat)('shadow').length > 0 ? 'yes': ''",
"_video:id= feat.properties.video === undefined ? undefined : new URL(feat.properties.video).searchParams.get('v')"
]
}
@ -72,7 +72,7 @@
"isOsmCache": true
},
"calculatedTags": [
"_is_shadowed=feat.overlapWith('shadow').length > 0 ? 'yes': ''",
"_is_shadowed=overlapWith(feat)('shadow').length > 0 ? 'yes': ''",
"_video:id=feat.properties.video === undefined ? undefined : new URL(feat.properties.video).searchParams.get('v')"
]
}
@ -88,7 +88,7 @@
"isOsmCache": true
},
"calculatedTags": [
"_is_shadowed=feat.overlapWith('shadow').length > 0 ? 'yes': ''",
"_is_shadowed=overlapWith(feat)('shadow').length > 0 ? 'yes': ''",
"_video:id=feat.properties.video === undefined ? undefined : new URL(feat.properties.video).searchParams.get('v')"
]
}
@ -104,7 +104,7 @@
"isOsmCache": true
},
"calculatedTags": [
"_is_shadowed=feat.overlapWith('shadow').length > 0 ? 'yes': ''",
"_is_shadowed=overlapWith(feat)('shadow').length > 0 ? 'yes': ''",
"_video:id=feat.properties.video === undefined ? undefined : new URL(feat.properties.video).searchParams.get('v')"
]
}
@ -113,7 +113,7 @@
"builtin": "slow_roads",
"override": {
"calculatedTags": [
"_is_shadowed=feat.overlapWith('shadow').length > 0 ? 'yes': ''"
"_is_shadowed=overlapWith(feat)('shadow').length > 0 ? 'yes': ''"
],
"source": {
"geoJsonLocal": "http://127.0.0.1:8080/speelplekken_{layer}_{z}_{x}_{y}.geojson",

View file

@ -23,8 +23,8 @@
"isOsmCache": false
},
"calculatedTags": [
"_closest_osm_street_lamp=feat.closest('street_lamps')?.properties?.id",
"_closest_osm_street_lamp_distance=feat.distanceTo(feat.properties._closest_osm_street_lamp)",
"_closest_osm_street_lamp=closest(feat)('street_lamps')?.properties?.id",
"_closest_osm_street_lamp_distance=distanceTo(feat)(feat.properties._closest_osm_street_lamp)",
"_has_closeby_feature=Number(feat.properties._closest_osm_street_lamp_distance) < 5 ? 'yes' : 'no'"
],
"title": "Straatlantaarn in dataset",
@ -54,8 +54,8 @@
"builtin": "maproulette_challenge",
"override": {
"calculatedTags": [
"_closest_osm_street_lamp=feat.closest('street_lamps')?.properties?.id",
"_closest_osm_street_lamp_distance=feat.distanceTo(feat.properties._closest_osm_street_lamp)",
"_closest_osm_street_lamp=closest(feat)('street_lamps')?.properties?.id",
"_closest_osm_street_lamp_distance=distanceTo(feat)(feat.properties._closest_osm_street_lamp)",
"_has_closeby_feature=Number(feat.properties._closest_osm_street_lamp_distance) < 5 ? 'yes' : 'no'"
],
"tagRenderings+": [

View file

@ -53,7 +53,7 @@
},
"minzoom": 18,
"calculatedTags": [
"_has_address=feat.overlapWith('address').length > 0"
"_has_address=overlapWith(feat)('address').length > 0"
],
"mapRendering": [
{
@ -135,10 +135,10 @@
}
],
"calculatedTags": [
"_embedding_object=feat.overlapWith('address')[0]?.feat?.properties ?? null",
"_embedding_object=overlapWith(feat)('address')[0]?.feat?.properties ?? null",
"_embedding_object:addr:housenumber=JSON.parse(feat.properties._embedding_object)?.['addr:housenumber']",
"_embedding_object:addr:street=JSON.parse(feat.properties._embedding_object)?.['addr:street']",
"_embedding_inspire_polygon_has_address=feat.overlapWith('raw_inspire_polygons')[0]?.feat?.properties?._has_address",
"_embedding_inspire_polygon_has_address=overlapWith(feat)('raw_inspire_polygons')[0]?.feat?.properties?._has_address",
"_embedding_object:id=feat.get('_embedding_object')?.id ?? feat.properties._embedding_inspire_polygon_has_address"
],
"filter": [
@ -233,7 +233,7 @@
"hu": "Címek"
},
"calculatedTags": [
"_closest_3_street_names=feat.closestn('named_streets',3, 'name').map(f => f.feat.properties.name)",
"_closest_3_street_names=closestn(feat)('named_streets',3, 'name').map(f => f.feat.properties.name)",
"_closest_street:0:name=JSON.parse(feat.properties._closest_3_street_names)[0]",
"_closest_street:1:name=JSON.parse(feat.properties._closest_3_street_names)[1]",
"_closest_street:2:name=JSON.parse(feat.properties._closest_3_street_names)[2]"

View file

@ -27,8 +27,8 @@
"isOsmCache": false
},
"calculatedTags": [
"_closest_osm_waste_basket=feat.closest('waste_basket')?.properties?.id",
"_closest_osm_waste_basket_distance=feat.distanceTo(feat.properties._closest_osm_waste_basket)",
"_closest_osm_waste_basket=closest(feat)('waste_basket')?.properties?.id",
"_closest_osm_waste_basket_distance=distanceTo(feat)(feat.properties._closest_osm_waste_basket)",
"_has_closeby_feature=Number(feat.properties._closest_osm_waste_basket_distance) < 10 ? 'yes' : 'no'"
],
"title": "Afvalbak in dataset",
@ -65,10 +65,10 @@
"isOsmCache": false
},
"calculatedTags": [
"_closest_osm_recycling=feat.closest('recycling')?.properties?.id",
"_closest_osm_waste_disposal=feat.closest('waste_disposal')?.properties?.id",
"_closest_osm_recycling_distance=feat.distanceTo(feat.properties._closest_osm_recycling)",
"_closest_osm_waste_disposal_distance=feat.distanceTo(feat.properties._closest_osm_waste_disposal)",
"_closest_osm_recycling=closest(feat)('recycling')?.properties?.id",
"_closest_osm_waste_disposal=closest(feat)('waste_disposal')?.properties?.id",
"_closest_osm_recycling_distance=distanceTo(feat)(feat.properties._closest_osm_recycling)",
"_closest_osm_waste_disposal_distance=distanceTo(feat)(feat.properties._closest_osm_waste_disposal)",
"_has_closeby_recycling=Number(feat.properties._closest_osm_recycling_distance) < 10 ? 'yes' : 'no'",
"_has_closeby_waste_disposal=Number(feat.properties._closest_osm_waste_disposal_distance) < 10 ? 'yes' : 'no'"
],

View file

@ -117,6 +117,10 @@ input {
color: var(--foreground-color);
}
input[type=text]{
width: 100%;
}
/************************* BIG CATEGORIES ********************************/
/**

View file

@ -1091,6 +1091,12 @@ video {
width: 100vw;
}
.w-fit {
width: -webkit-fit-content;
width: -moz-fit-content;
width: fit-content;
}
.w-4 {
width: 1rem;
}
@ -1103,12 +1109,6 @@ video {
width: 0.75rem;
}
.w-fit {
width: -webkit-fit-content;
width: -moz-fit-content;
width: fit-content;
}
.w-7 {
width: 1.75rem;
}
@ -1117,6 +1117,10 @@ video {
width: 2.75rem;
}
.w-24 {
width: 6rem;
}
.w-1\/2 {
width: 50%;
}
@ -1130,10 +1134,6 @@ video {
width: 24rem;
}
.w-24 {
width: 6rem;
}
.w-10 {
width: 2.5rem;
}
@ -1956,6 +1956,10 @@ input {
color: var(--foreground-color);
}
input[type=text]{
width: 100%;
}
/************************* BIG CATEGORIES ********************************/
/**
@ -2451,6 +2455,10 @@ a.link-underline {
max-width: 36rem;
}
.sm\:flex-nowrap {
flex-wrap: nowrap;
}
.sm\:items-stretch {
align-items: stretch;
}

View file

@ -59,7 +59,7 @@ async function testPdf() {
await pdf.ConvertSvg("nl")
}
testPdf().then((_) => console.log("All done"))
// testPdf().then((_) => console.log("All done"))
//testinput()
/*/
testspecial()

View file

@ -1,6 +1,8 @@
import { ExtraFuncParams, ExtraFunctions } from "../../Logic/ExtraFunctions"
import { OsmFeature } from "../../Models/OsmFeature"
import { describe, expect, it } from "vitest"
import {GeoJSONFeature} from "maplibre-gl";
import {Feature} from "geojson";
describe("OverlapFunc", () => {
it("should give doors on the edge", () => {
@ -21,7 +23,7 @@ describe("OverlapFunc", () => {
},
}
const hermanTeirlinck = {
const hermanTeirlinck: Feature = {
type: "Feature",
id: "way/444059131",
properties: {
@ -104,21 +106,15 @@ describe("OverlapFunc", () => {
],
],
},
bbox: {
maxLat: 50.8668593,
maxLon: 4.3509055,
minLat: 50.8655878,
minLon: 4.3493369,
},
}
const params: ExtraFuncParams = {
getFeatureById: () => undefined,
getFeaturesWithin: () => [[door]],
getFeaturesWithin: () => [door],
}
const helpers = ExtraFunctions.constructHelpers(params)
ExtraFunctions.FullPatchFeature(params, hermanTeirlinck)
const overlap = (<any>hermanTeirlinck).overlapWith("*")
const overlap = helpers.overlapWith(hermanTeirlinck)("*")
console.log(JSON.stringify(overlap))
expect(overlap[0].feat == door).toBe(true)
})