Reformat all files with prettier

This commit is contained in:
Pieter Vander Vennet 2022-09-08 21:40:48 +02:00
parent e22d189376
commit b541d3eab4
382 changed files with 50893 additions and 35566 deletions

View file

@ -1,11 +1,11 @@
import {GeoOperations} from "./GeoOperations";
import Combine from "../UI/Base/Combine";
import RelationsTracker from "./Osm/RelationsTracker";
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 "@turf/turf";
import { GeoOperations } from "./GeoOperations"
import Combine from "../UI/Base/Combine"
import RelationsTracker from "./Osm/RelationsTracker"
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 "@turf/turf"
export interface ExtraFuncParams {
/**
@ -13,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, { id: string }>[][],
getFeaturesWithin: (layerId: string, bbox: BBox) => Feature<Geometry, { id: string }>[][]
memberships: RelationsTracker
getFeatureById: (id: string) => Feature<Geometry, { id: string }>
}
@ -22,19 +22,23 @@ export interface ExtraFuncParams {
* Describes a function that is added to a geojson object in order to calculate calculated tags
*/
interface ExtraFunction {
readonly _name: string;
readonly _args: string[];
readonly _doc: string;
readonly _f: (params: ExtraFuncParams, feat: Feature<Geometry, any>) => any;
readonly _name: string
readonly _args: string[]
readonly _doc: string
readonly _f: (params: ExtraFuncParams, feat: Feature<Geometry, any>) => any
}
class EnclosingFunc implements ExtraFunction {
_name = "enclosingFeatures"
_doc = ["Gives a list of all features in the specified layers which fully contain this object. Returned features will always be (multi)polygons. (LineStrings and Points from the other layers are ignored)", "",
_doc = [
"Gives a list of all features in the specified layers which fully contain this object. Returned features will always be (multi)polygons. (LineStrings and Points from the other layers are ignored)",
"",
"The result is a list of features: `{feat: Polygon}[]`",
"This function will never return the feature itself."].join("\n")
_args = ["...layerIds - one or more layer ids of the layer from which every feature is checked for overlap)"]
"This function will never return the feature itself.",
].join("\n")
_args = [
"...layerIds - one or more layer ids of the layer from which every feature is checked for overlap)",
]
_f(params: ExtraFuncParams, feat: Feature<Geometry, any>) {
return (...layerIds: string[]) => {
@ -45,10 +49,10 @@ class EnclosingFunc implements ExtraFunction {
for (const layerId of layerIds) {
const otherFeaturess = params.getFeaturesWithin(layerId, bbox)
if (otherFeaturess === undefined) {
continue;
continue
}
if (otherFeaturess.length === 0) {
continue;
continue
}
for (const otherFeatures of otherFeaturess) {
for (const otherFeature of otherFeatures) {
@ -56,26 +60,33 @@ class EnclosingFunc implements ExtraFunction {
continue
}
seenIds.add(otherFeature.properties.id)
if (otherFeature.geometry.type !== "Polygon" && otherFeature.geometry.type !== "MultiPolygon") {
continue;
if (
otherFeature.geometry.type !== "Polygon" &&
otherFeature.geometry.type !== "MultiPolygon"
) {
continue
}
if (GeoOperations.completelyWithin(feat, <Feature<Polygon | MultiPolygon, any>>otherFeature)) {
result.push({feat: otherFeature})
if (
GeoOperations.completelyWithin(
feat,
<Feature<Polygon | MultiPolygon, any>>otherFeature
)
) {
result.push({ feat: otherFeature })
}
}
}
}
return result;
return result
}
}
}
class OverlapFunc implements ExtraFunction {
_name = "overlapWith";
_doc = ["Gives a list of features from the specified layer which this feature (partly) overlaps with. A point which is embedded in the feature is detected as well.",
_name = "overlapWith"
_doc = [
"Gives a list of features from the specified layer which this feature (partly) overlaps with. A point which is embedded in the feature is detected as well.",
"If the current feature is a point, all features that this point is embeded in are given.",
"",
"The returned value is `{ feat: GeoJSONFeature, overlap: number}[]` where `overlap` is the overlapping surface are (in m²) for areas, the overlapping length (in meter) if the current feature is a line or `undefined` if the current feature is a point.",
@ -83,27 +94,29 @@ class OverlapFunc implements ExtraFunction {
"",
"For example to get all objects which overlap or embed from a layer, use `_contained_climbing_routes_properties=feat.overlapWith('climbing_route')`",
"",
"Also see [enclosingFeatures](#enclosingFeatures) which can be used to get all objects which fully contain this feature"
"Also see [enclosingFeatures](#enclosingFeatures) which can be used to get all objects which fully contain this feature",
].join("\n")
_args = ["...layerIds - one or more layer ids of the layer from which every feature is checked for overlap)"]
_args = [
"...layerIds - one or more layer ids of the layer from which every feature is checked for overlap)",
]
_f(params, feat) {
return (...layerIds: string[]) => {
const result: { feat: any, overlap: number }[] = []
const result: { feat: any; overlap: number }[] = []
const seenIds = new Set<string>()
const bbox = BBox.get(feat)
for (const layerId of layerIds) {
const otherFeaturess = params.getFeaturesWithin(layerId, bbox)
if (otherFeaturess === undefined) {
continue;
continue
}
if (otherFeaturess.length === 0) {
continue;
continue
}
for (const otherFeatures of otherFeaturess) {
const overlap = GeoOperations.calculateOverlap(feat, otherFeatures)
for (const overlappingFeature of overlap) {
if(seenIds.has(overlappingFeature.feat.properties.id)){
if (seenIds.has(overlappingFeature.feat.properties.id)) {
continue
}
seenIds.add(overlappingFeature.feat.properties.id)
@ -113,105 +126,113 @@ class OverlapFunc implements ExtraFunction {
}
result.sort((a, b) => b.overlap - a.overlap)
return result;
return result
}
}
}
class IntersectionFunc implements ExtraFunction {
_name = "intersectionsWith";
_doc = "Gives the intersection points with selected features. Only works with (Multi)Polygons and LineStrings.\n\n" +
_name = "intersectionsWith"
_doc =
"Gives the intersection points with selected features. Only works with (Multi)Polygons and LineStrings.\n\n" +
"Returns a `{feat: GeoJson, intersections: [number,number][]}` where `feat` is the full, original feature. This list is in random order.\n\n" +
"If the current feature is a point, this function will return an empty list.\n" +
"Points from other layers are ignored - even if the points are parts of the current linestring."
_args = ["...layerIds - one or more layer ids of the layer from which every feature is checked for intersection)"]
_args = [
"...layerIds - one or more layer ids of the layer from which every feature is checked for intersection)",
]
_f(params: ExtraFuncParams, feat) {
return (...layerIds: string[]) => {
const result: { feat: any, intersections: [number, number][] }[] = []
const result: { feat: any; intersections: [number, number][] }[] = []
const bbox = BBox.get(feat)
for (const layerId of layerIds) {
const otherLayers = params.getFeaturesWithin(layerId, bbox)
if (otherLayers === undefined) {
continue;
continue
}
if (otherLayers.length === 0) {
continue;
continue
}
for (const tile of otherLayers) {
for (const otherFeature of tile) {
const intersections = GeoOperations.LineIntersections(feat, otherFeature)
if (intersections.length === 0) {
continue
}
result.push({feat: otherFeature, intersections})
result.push({ feat: otherFeature, intersections })
}
}
}
return result;
return result
}
}
}
class DistanceToFunc implements ExtraFunction {
_name = "distanceTo";
_doc = "Calculates the distance between the feature and a specified point in meter. The input should either be a pair of coordinates, a geojson feature or the ID of an object";
_name = "distanceTo"
_doc =
"Calculates the distance between the feature and a specified point in meter. The input should either be a pair of coordinates, a geojson feature or the ID of an object"
_args = ["feature OR featureID OR longitude", "undefined OR latitude"]
_f(featuresPerLayer, feature) {
return (arg0, lat) => {
if (arg0 === undefined) {
return undefined;
return undefined
}
if (typeof arg0 === "number") {
// Feature._lon and ._lat is conveniently place by one of the other metatags
return GeoOperations.distanceBetween([arg0, lat], GeoOperations.centerpointCoordinates(feature));
return GeoOperations.distanceBetween(
[arg0, lat],
GeoOperations.centerpointCoordinates(feature)
)
}
if (typeof arg0 === "string") {
// This is an identifier
const feature = featuresPerLayer.getFeatureById(arg0)
if (feature === undefined) {
return undefined;
return undefined
}
arg0 = feature;
arg0 = feature
}
// arg0 is probably a geojsonfeature
return GeoOperations.distanceBetween(GeoOperations.centerpointCoordinates(arg0), GeoOperations.centerpointCoordinates(feature))
return GeoOperations.distanceBetween(
GeoOperations.centerpointCoordinates(arg0),
GeoOperations.centerpointCoordinates(feature)
)
}
}
}
class ClosestObjectFunc implements ExtraFunction {
_name = "closest"
_doc = "Given either a list of geojson features or a single layer name, gives the single object which is nearest to the feature. In the case of ways/polygons, only the centerpoint is considered. Returns a single geojson feature or undefined if nothing is found (or not yet loaded)"
_doc =
"Given either a list of geojson features or a single layer name, gives the single object which is nearest to the feature. In the case of ways/polygons, only the centerpoint is considered. Returns a single geojson feature or undefined if nothing is found (or not yet loaded)"
_args = ["list of features or a layer name or '*' to get all features"]
_f(params, feature) {
return (features) => ClosestNObjectFunc.GetClosestNFeatures(params, feature, features)?.[0]?.feat
return (features) =>
ClosestNObjectFunc.GetClosestNFeatures(params, feature, features)?.[0]?.feat
}
}
class ClosestNObjectFunc implements ExtraFunction {
_name = "closestn"
_doc = "Given either a list of geojson features or a single layer name, gives the n closest objects which are nearest to the feature (excluding the feature itself). In the case of ways/polygons, only the centerpoint is considered. " +
_doc =
"Given either a list of geojson features or a single layer name, gives the n closest objects which are nearest to the feature (excluding the feature itself). In the case of ways/polygons, only the centerpoint is considered. " +
"Returns a list of `{feat: geojson, distance:number}` the empty list if nothing is found (or not yet loaded)\n\n" +
"If a 'unique tag key' is given, the tag with this key will only appear once (e.g. if 'name' is given, all features will have a different name)"
_args = ["list of features or layer name or '*' to get all features", "amount of features", "unique tag key (optional)", "maxDistanceInMeters (optional)"]
_args = [
"list of features or layer name or '*' to get all features",
"amount of features",
"unique tag key (optional)",
"maxDistanceInMeters (optional)",
]
/**
* Gets the closes N features, sorted by ascending distance.
@ -223,45 +244,61 @@ class ClosestNObjectFunc implements ExtraFunction {
* @constructor
* @private
*/
static GetClosestNFeatures(params: ExtraFuncParams,
feature: any,
features: string | any[],
options?: { maxFeatures?: number, uniqueTag?: string | undefined, maxDistance?: number }): { feat: any, distance: number }[] {
static GetClosestNFeatures(
params: ExtraFuncParams,
feature: any,
features: string | any[],
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
if (typeof features === "string") {
const name = features
const bbox = GeoOperations.bbox(GeoOperations.buffer(GeoOperations.bbox(feature), maxDistance))
const bbox = GeoOperations.bbox(
GeoOperations.buffer(GeoOperations.bbox(feature), maxDistance)
)
features = params.getFeaturesWithin(name, new BBox(bbox.geometry.coordinates))
} else {
features = [features]
}
if (features === undefined) {
return;
return
}
const selfCenter = GeoOperations.centerpointCoordinates(feature)
let closestFeatures: { feat: any, distance: number }[] = [];
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) {
if (otherFeature === feature || otherFeature.properties.id === feature.properties.id) {
continue; // We ignore self
if (
otherFeature === feature ||
otherFeature.properties.id === feature.properties.id
) {
continue // We ignore self
}
const distance = GeoOperations.distanceBetween(
GeoOperations.centerpointCoordinates(otherFeature),
selfCenter
)
if (distance === undefined || distance === null || isNaN(distance)) {
console.error("Could not calculate the distance between", feature, "and", otherFeature)
console.error(
"Could not calculate the distance between",
feature,
"and",
otherFeature
)
throw "Undefined distance!"
}
if (distance === 0) {
console.trace("Got a suspiciously zero distance between", otherFeature, "and self-feature", feature)
console.trace(
"Got a suspiciously zero distance between",
otherFeature,
"and self-feature",
feature
)
}
if (distance > maxDistance) {
@ -272,13 +309,15 @@ class ClosestNObjectFunc implements ExtraFunction {
// This is the first matching feature we find - always add it
closestFeatures.push({
feat: otherFeature,
distance: distance
distance: distance,
})
continue;
continue
}
if (closestFeatures.length >= maxFeatures && closestFeatures[maxFeatures - 1].distance < distance) {
if (
closestFeatures.length >= maxFeatures &&
closestFeatures[maxFeatures - 1].distance < distance
) {
// The last feature of the list (and thus the furthest away is still closer
// No use for checking, as we already have plenty of features!
continue
@ -286,11 +325,13 @@ class ClosestNObjectFunc implements ExtraFunction {
let targetIndex = closestFeatures.length
for (let i = 0; i < closestFeatures.length; i++) {
const closestFeature = closestFeatures[i];
const closestFeature = closestFeatures[i]
if (uniqueTag !== undefined) {
const uniqueTagsMatch = otherFeature.properties[uniqueTag] !== undefined &&
closestFeature.feat.properties[uniqueTag] === otherFeature.properties[uniqueTag]
const uniqueTagsMatch =
otherFeature.properties[uniqueTag] !== undefined &&
closestFeature.feat.properties[uniqueTag] ===
otherFeature.properties[uniqueTag]
if (uniqueTagsMatch) {
targetIndex = -1
if (closestFeature.distance > distance) {
@ -298,9 +339,9 @@ 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;
break
}
}
@ -316,19 +357,19 @@ class ClosestNObjectFunc implements ExtraFunction {
}
}
}
break;
break
}
}
if (targetIndex == -1) {
continue; // value is already swapped by the unique tag
continue // value is already swapped by the unique tag
}
if (targetIndex < maxFeatures) {
// insert and drop one
closestFeatures.splice(targetIndex, 0, {
feat: otherFeature,
distance: distance
distance: distance,
})
if (closestFeatures.length >= maxFeatures) {
closestFeatures.splice(maxFeatures, 1)
@ -337,19 +378,15 @@ class ClosestNObjectFunc implements ExtraFunction {
// Overwrite the last element
closestFeatures[targetIndex] = {
feat: otherFeature,
distance: distance
distance: distance,
}
}
}
}
return closestFeatures;
return closestFeatures
}
_f(params, feature) {
return (features, amount, uniqueTag, maxDistanceInMeters) => {
let distance: number = Number(maxDistanceInMeters)
if (isNaN(distance)) {
@ -358,60 +395,54 @@ class ClosestNObjectFunc implements ExtraFunction {
return ClosestNObjectFunc.GetClosestNFeatures(params, feature, features, {
maxFeatures: Number(amount),
uniqueTag: uniqueTag,
maxDistance: distance
});
maxDistance: distance,
})
}
}
}
class Memberships implements ExtraFunction {
_name = "memberships"
_doc = "Gives a list of `{role: string, relation: Relation}`-objects, containing all the relations that this feature is part of. " +
_doc =
"Gives a list of `{role: string, relation: Relation}`-objects, containing all the relations that this feature is part of. " +
"\n\n" +
"For example: `_part_of_walking_routes=feat.memberships().map(r => r.relation.tags.name).join(';')`"
_args = []
_f(params, feat) {
return () =>
params.memberships.knownRelations.data.get(feat.properties.id) ?? []
return () => params.memberships.knownRelations.data.get(feat.properties.id) ?? []
}
}
class GetParsed implements ExtraFunction {
_name = "get"
_doc = "Gets the property of the feature, parses it (as JSON) and returns it. Might return 'undefined' if not defined, null, ..."
_doc =
"Gets the property of the feature, parses it (as JSON) and returns it. Might return 'undefined' if not defined, null, ..."
_args = ["key"]
_f(params, feat) {
return key => {
return (key) => {
const value = feat.properties[key]
if (value === undefined) {
return undefined;
return undefined
}
try {
const parsed = JSON.parse(value)
if (parsed === null) {
return undefined;
return undefined
}
return parsed;
return parsed
} catch (e) {
console.warn("Could not parse property " + key + " due to: " + e + ", the value is " + value)
return undefined;
console.warn(
"Could not parse property " + key + " due to: " + e + ", the value is " + value
)
return undefined
}
}
}
}
export class ExtraFunctions {
static readonly intro = new Combine([
new Title("Calculating tags with Javascript", 2),
"In some cases, it is useful to have some tags calculated based on other properties. Some useful tags are available by default (e.g. `lat`, `lon`, `_country`), as detailed above.",
@ -421,13 +452,13 @@ export class ExtraFunctions {
new List([
"DO NOT DO THIS AS BEGINNER",
"**Only do this if all other techniques fail** This should _not_ be done to create a rendering effect, only to calculate a specific value",
"**THIS MIGHT BE DISABLED WITHOUT ANY NOTICE ON UNOFFICIAL THEMES** As unofficial themes might be loaded from the internet, this is the equivalent of injecting arbitrary code into the client. It'll be disabled if abuse occurs."
"**THIS MIGHT BE DISABLED WITHOUT ANY NOTICE ON UNOFFICIAL THEMES** As unofficial themes might be loaded from the internet, this is the equivalent of injecting arbitrary code into the client. It'll be disabled if abuse occurs.",
]),
"To enable this feature, add a field `calculatedTags` in the layer object, e.g.:",
"````",
"\"calculatedTags\": [",
" \"_someKey=javascript-expression\",",
" \"name=feat.properties.name ?? feat.properties.ref ?? feat.properties.operator\",",
'"calculatedTags": [',
' "_someKey=javascript-expression",',
' "name=feat.properties.name ?? feat.properties.ref ?? feat.properties.operator",',
" \"_distanceCloserThen3Km=feat.distanceTo( some_lon, some_lat) < 3 ? 'yes' : 'no'\" ",
" ]",
"````",
@ -436,11 +467,12 @@ export class ExtraFunctions {
new List([
"`area` contains the surface area (in square meters) of the object",
"`lat` and `lon` contain the latitude and longitude"
"`lat` and `lon` contain the latitude and longitude",
]),
"Some advanced functions are available on **feat** as well:"
]).SetClass("flex-col").AsMarkdown();
"Some advanced functions are available on **feat** as well:",
])
.SetClass("flex-col")
.AsMarkdown()
private static readonly allFuncs: ExtraFunction[] = [
new DistanceToFunc(),
@ -450,8 +482,8 @@ export class ExtraFunctions {
new ClosestObjectFunc(),
new ClosestNObjectFunc(),
new Memberships(),
new GetParsed()
];
new GetParsed(),
]
public static FullPatchFeature(params: ExtraFuncParams, feature) {
if (feature._is_patched) {
@ -464,20 +496,15 @@ export class ExtraFunctions {
}
public static HelpText(): BaseUIElement {
const elems = []
for (const func of ExtraFunctions.allFuncs) {
elems.push(new Title(func._name, 3),
func._doc,
new List(func._args ?? [], true))
elems.push(new Title(func._name, 3), func._doc, new List(func._args ?? [], true))
}
return new Combine([
ExtraFunctions.intro,
new List(ExtraFunctions.allFuncs.map(func => `[${func._name}](#${func._name})`)),
...elems
]);
new List(ExtraFunctions.allFuncs.map((func) => `[${func._name}](#${func._name})`)),
...elems,
])
}
}