forked from MapComplete/MapComplete
Refactoring: fix metatagging
This commit is contained in:
parent
177697fe0a
commit
8fd3fbc0b7
34 changed files with 378 additions and 265 deletions
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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) {}
|
||||
}
|
||||
|
|
90
UI/BigComponents/BackgroundSwitcher.svelte
Normal file
90
UI/BigComponents/BackgroundSwitcher.svelte
Normal 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>
|
|
@ -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}
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
*/
|
||||
export let map: Writable<MaplibreMap>
|
||||
|
||||
export let attribution = true
|
||||
export let attribution = false
|
||||
let center = {};
|
||||
|
||||
onMount(() => {
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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">
|
||||
|
|
2
Utils.ts
2
Utils.ts
|
@ -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("/")
|
||||
|
|
|
@ -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]"
|
||||
|
|
|
@ -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",
|
||||
|
@ -317,4 +317,4 @@
|
|||
"width": "8"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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": [
|
||||
|
@ -274,4 +274,4 @@
|
|||
"fr": "Une couche montrant les fontaines d'eau potable",
|
||||
"ca": "Una capa que mostra fonts d'aigua potable"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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": [
|
||||
{
|
||||
|
@ -306,4 +306,4 @@
|
|||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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": [
|
||||
|
@ -341,4 +341,4 @@
|
|||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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": [
|
||||
{
|
||||
|
@ -153,4 +153,4 @@
|
|||
"condition": "_biggest_width_id~*"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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]",
|
||||
|
@ -411,4 +411,4 @@
|
|||
}
|
||||
],
|
||||
"hideFromOverview": true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
|
@ -613,4 +613,4 @@
|
|||
]
|
||||
},
|
||||
"credits": "Pieter Vander Vennet"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
|
@ -338,4 +338,4 @@
|
|||
}
|
||||
],
|
||||
"credits": "Christian Neumann <christian@utopicode.de>"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
],
|
||||
|
@ -752,4 +752,4 @@
|
|||
"overpassMaxZoom": 17,
|
||||
"osmApiTileSize": 17,
|
||||
"credits": "Pieter Vander Vennet"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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'])"
|
||||
],
|
||||
|
|
|
@ -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'])"
|
||||
],
|
||||
|
|
|
@ -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+": [
|
||||
{
|
||||
|
@ -504,4 +504,4 @@
|
|||
]
|
||||
},
|
||||
"enableDownload": true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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": [],
|
||||
|
@ -216,4 +216,4 @@
|
|||
"isShown": "_country=be"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
|
@ -267,4 +267,4 @@
|
|||
],
|
||||
"isShown": "_is_shadowed!=yes"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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+": [
|
||||
|
@ -69,4 +69,4 @@
|
|||
],
|
||||
"hideFromOverview": true,
|
||||
"credits": "Robin van der Linde"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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]"
|
||||
|
@ -695,4 +695,4 @@
|
|||
"enableShareScreen": false,
|
||||
"enableMoreQuests": false,
|
||||
"credits": "Pieter Vander Vennet, Rob Nickerson, Russ Garrett"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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'"
|
||||
],
|
||||
|
@ -103,4 +103,4 @@
|
|||
],
|
||||
"hideFromOverview": true,
|
||||
"credits": "Robin van der Linde"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -117,6 +117,10 @@ input {
|
|||
color: var(--foreground-color);
|
||||
}
|
||||
|
||||
input[type=text]{
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/************************* BIG CATEGORIES ********************************/
|
||||
|
||||
/**
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
2
test.ts
2
test.ts
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
|
|
Loading…
Reference in a new issue