2021-03-25 15:19:44 +01:00
import { GeoOperations } from "./GeoOperations" ;
import { UIElement } from "../UI/UIElement" ;
import Combine from "../UI/Base/Combine" ;
2021-04-18 14:24:30 +02:00
import State from "../State" ;
2021-04-22 13:30:00 +02:00
import { Relation } from "./Osm/ExtractRelations" ;
2021-03-25 15:19:44 +01:00
export class ExtraFunction {
2021-03-26 03:24:58 +01:00
static readonly intro = ` <h2>Calculating tags with Javascript</h2>
< p > In some cases , it is useful to have some tags calculated based on other properties . Some useful tags are available by default ( e . g . < b > lat < / b > , < b > lon < / b > , < b > _country < / b > ) , as detailed above . < / p >
< p > It is also possible to calculate your own tags - but this requires some javascript knowledge . < / p >
Before proceeding , some warnings :
< ul >
< li > DO NOT DO THIS AS BEGINNER < / li >
< li > < b > Only do this if all other techniques fail < / b > . This should < i > not < / i > be done to create a rendering effect , only to calculate a specific value < / li >
< li > < b > THIS MIGHT BE DISABLED WITHOUT ANY NOTICE ON UNOFFICIAL THEMES < / b > . 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 . < / li >
< / ul >
In the layer object , add a field < b > calculatedTags < / b > , e . g . :
< div class = "code" >
"calculatedTags" : [
"_someKey=javascript-expression" ,
"name=feat.properties.name ?? feat.properties.ref ?? feat.properties.operator" ,
"_distanceCloserThen3Km=feat.distanceTo( some_lon, some_lat) < 3 ? 'yes' : 'no'"
]
< / div >
The above code will be executed for every feature in the layer . The feature is accessible as < b > feat < / b > and is an amended geojson object :
- < b > area < / b > contains the surface area ( in square meters ) of the object
- < b > lat < / b > and < b > lon < / b > contain the latitude and longitude
Some advanced functions are available on < b > feat < / b > as well :
`
2021-04-18 14:24:30 +02:00
private static readonly OverlapFunc = new ExtraFunction (
2021-03-26 03:24:58 +01:00
"overlapWith" ,
"Gives a list of features from the specified layer which this feature overlaps with, the amount of overlap in m². The returned value is <b>{ feat: GeoJSONFeature, overlap: number}</b>" ,
[ "...layerIds - one or more layer ids of the layer from which every feature is checked for overlap)" ] ,
2021-04-22 13:30:00 +02:00
( params , feat ) = > {
2021-03-26 03:24:58 +01:00
return ( . . . layerIds : string [ ] ) = > {
const result = [ ]
for ( const layerId of layerIds ) {
2021-04-22 13:30:00 +02:00
const otherLayer = params . featuresPerLayer . get ( layerId ) ;
2021-03-26 03:24:58 +01:00
if ( otherLayer === undefined ) {
continue ;
}
if ( otherLayer . length === 0 ) {
continue ;
}
result . push ( . . . GeoOperations . calculateOverlap ( feat , otherLayer ) ) ;
}
return result ;
}
}
)
2021-04-18 14:24:30 +02:00
private static readonly DistanceToFunc = new ExtraFunction (
2021-03-25 15:19:44 +01:00
"distanceTo" ,
"Calculates the distance between the feature and a specified point" ,
[ "longitude" , "latitude" ] ,
2021-03-26 03:24:58 +01:00
( featuresPerLayer , feature ) = > {
2021-03-29 02:04:42 +02:00
return ( arg0 , lat ) = > {
2021-04-18 14:24:30 +02:00
if ( typeof arg0 === "number" ) {
2021-03-29 02:04:42 +02:00
const lon = arg0
// Feature._lon and ._lat is conveniently place by one of the other metatags
return GeoOperations . distanceBetween ( [ lon , lat ] , [ feature . _lon , feature . _lat ] ) ;
2021-04-18 14:24:30 +02:00
} else {
2021-03-29 02:04:42 +02:00
// arg0 is probably a feature
2021-04-18 14:24:30 +02:00
return GeoOperations . distanceBetween ( GeoOperations . centerpointCoordinates ( arg0 ) , [ feature . _lon , feature . _lat ] )
2021-03-29 02:04:42 +02:00
}
2021-04-18 14:24:30 +02:00
2021-03-25 15:19:44 +01:00
}
}
)
2021-03-29 02:04:42 +02:00
2021-04-18 14:24:30 +02:00
private static readonly ClosestObjectFunc = new ExtraFunction (
2021-03-29 02:04:42 +02:00
"closest" ,
"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." ,
[ "list of features" ] ,
2021-04-22 13:30:00 +02:00
( params , feature ) = > {
2021-03-29 02:04:42 +02:00
return ( features ) = > {
if ( typeof features === "string" ) {
2021-04-22 13:30:00 +02:00
features = params . featuresPerLayer . get ( features )
2021-03-29 02:04:42 +02:00
}
let closestFeature = undefined ;
let closestDistance = undefined ;
for ( const otherFeature of features ) {
2021-04-18 14:24:30 +02:00
if ( otherFeature == feature ) {
2021-03-29 02:04:42 +02:00
continue ; // We ignore self
}
let distance = undefined ;
if ( otherFeature . _lon !== undefined && otherFeature . _lat !== undefined ) {
distance = GeoOperations . distanceBetween ( [ otherFeature . _lon , otherFeature . _lat ] , [ feature . _lon , feature . _lat ] ) ;
} else {
distance = GeoOperations . distanceBetween (
GeoOperations . centerpointCoordinates ( otherFeature ) ,
[ feature . _lon , feature . _lat ]
)
}
2021-04-18 14:24:30 +02:00
if ( distance === undefined ) {
2021-03-29 02:04:42 +02:00
throw "Undefined distance!"
}
2021-04-18 14:24:30 +02:00
if ( closestFeature === undefined || distance < closestDistance ) {
2021-03-29 02:04:42 +02:00
closestFeature = otherFeature
closestDistance = distance ;
}
}
return closestFeature ;
}
}
)
2021-04-18 14:24:30 +02:00
private static readonly Memberships = new ExtraFunction (
"memberships" ,
2021-05-11 11:03:28 +02:00
"Gives a list of <code>{role: string, relation: Relation}</code>-objects, containing all the relations that this feature is part of. \n\nFor example: <code>_part_of_walking_routes=feat.memberships().map(r => r.relation.tags.name).join(';')</code>" ,
2021-04-18 14:24:30 +02:00
[ ] ,
2021-04-22 13:30:00 +02:00
( params , feature ) = > {
return ( ) = > params . relations ? ? [ ] ;
2021-04-18 14:24:30 +02:00
}
)
private static readonly allFuncs : ExtraFunction [ ] = [ ExtraFunction . DistanceToFunc , ExtraFunction . OverlapFunc , ExtraFunction . ClosestObjectFunc , ExtraFunction . Memberships ] ;
2021-03-25 15:19:44 +01:00
private readonly _name : string ;
private readonly _args : string [ ] ;
private readonly _doc : string ;
2021-04-22 13:30:00 +02:00
private readonly _f : ( params : { featuresPerLayer : Map < string , any [ ] > , relations : { role : string , relation : Relation } [ ] } , feat : any ) = > any ;
2021-03-25 15:19:44 +01:00
2021-04-22 13:30:00 +02:00
constructor ( name : string , doc : string , args : string [ ] , f : ( ( params : { featuresPerLayer : Map < string , any [ ] > , relations : { role : string , relation : Relation } [ ] } , feat : any ) = > any ) ) {
2021-03-25 15:19:44 +01:00
this . _name = name ;
this . _doc = doc ;
this . _args = args ;
this . _f = f ;
}
2021-04-22 13:30:00 +02:00
public static FullPatchFeature ( featuresPerLayer : Map < string , any [ ] > , relations : { role : string , relation : Relation } [ ] , feature ) {
2021-03-25 15:19:44 +01:00
for ( const func of ExtraFunction . allFuncs ) {
2021-04-22 13:30:00 +02:00
func . PatchFeature ( featuresPerLayer , relations , feature ) ;
2021-03-25 15:19:44 +01:00
}
}
public static HelpText ( ) : UIElement {
return new Combine ( [
ExtraFunction . intro ,
2021-03-26 03:24:58 +01:00
"<ul>" ,
2021-03-29 02:04:42 +02:00
. . . ExtraFunction . allFuncs . map ( func = >
new Combine ( [
"<li>" , func . _name , "</li>"
] )
2021-03-26 03:24:58 +01:00
) ,
"</ul>" ,
2021-03-25 15:19:44 +01:00
. . . ExtraFunction . allFuncs . map ( func = >
new Combine ( [
"<h3>" + func . _name + "</h3>" ,
func . _doc ,
"<ul>" ,
. . . func . _args . map ( arg = > "<li>" + arg + "</li>" ) ,
"</ul>"
] )
)
] ) ;
}
2021-04-22 13:30:00 +02:00
public PatchFeature ( featuresPerLayer : Map < string , any [ ] > , relations : { role : string , relation : Relation } [ ] , feature : any ) {
feature [ this . _name ] = this . _f ( { featuresPerLayer : featuresPerLayer , relations : relations } , feature ) ;
2021-03-25 15:19:44 +01:00
}
}