2021-03-24 01:25:57 +01:00
import { GeoOperations } from "./GeoOperations" ;
import { Utils } from "../Utils" ;
import opening_hours from "opening_hours" ;
import Combine from "../UI/Base/Combine" ;
2021-06-15 00:28:59 +02:00
import BaseUIElement from "../UI/BaseUIElement" ;
import Title from "../UI/Base/Title" ;
import { FixedUiElement } from "../UI/Base/FixedUiElement" ;
2021-10-12 02:12:45 +02:00
import LayerConfig from "../Models/ThemeConfig/LayerConfig" ;
2021-12-07 02:22:56 +01:00
import { CountryCoder } from "latlon2country"
2021-12-13 20:51:44 +01:00
import ScriptUtils from "../scripts/ScriptUtils" ;
2021-03-24 01:25:57 +01:00
2021-04-22 13:30:00 +02:00
2021-12-07 02:22:56 +01:00
export class SimpleMetaTagger {
public readonly keys : string [ ] ;
public readonly doc : string ;
public readonly isLazy : boolean ;
public readonly includesDates : boolean
2021-12-12 17:35:08 +01:00
public readonly applyMetaTagsOnFeature : ( feature : any , freshness : Date , layer : LayerConfig , state ) = > boolean ;
2021-12-07 02:22:56 +01:00
/ * * *
* A function that adds some extra data to a feature
* @param docs : what does this extra data do ?
* @param f : apply the changes . Returns true if something changed
* /
constructor ( docs : { keys : string [ ] , doc : string , includesDates? : boolean , isLazy? : boolean , cleanupRetagger? : boolean } ,
2021-12-12 17:35:08 +01:00
f : ( ( feature : any , freshness : Date , layer : LayerConfig , state ) = > boolean ) ) {
2021-12-07 02:22:56 +01:00
this . keys = docs . keys ;
this . doc = docs . doc ;
this . isLazy = docs . isLazy
this . applyMetaTagsOnFeature = f ;
this . includesDates = docs . includesDates ? ? false ;
if ( ! docs . cleanupRetagger ) {
for ( const key of docs . keys ) {
if ( ! key . startsWith ( '_' ) && key . toLowerCase ( ) . indexOf ( "theme" ) < 0 ) {
throw ` Incorrect metakey ${ key } : it should start with underscore (_) `
}
}
}
}
}
export class CountryTagger extends SimpleMetaTagger {
2021-12-14 17:29:21 +01:00
private static readonly coder = new CountryCoder ( "https://raw.githubusercontent.com/pietervdvn/MapComplete-data/main/latlon2country" , Utils . downloadJson ) ;
2021-12-07 02:22:56 +01:00
public runningTasks : Set < any > ;
constructor ( ) {
const runningTasks = new Set < any > ( ) ;
super
(
{
keys : [ "_country" ] ,
doc : "The country code of the property (with latlon2country)" ,
includesDates : false
} ,
2021-12-12 17:35:08 +01:00
( ( feature , _ , __ , state ) = > {
2021-12-07 02:22:56 +01:00
let centerPoint : any = GeoOperations . centerpoint ( feature ) ;
const lat = centerPoint . geometry . coordinates [ 1 ] ;
const lon = centerPoint . geometry . coordinates [ 0 ] ;
runningTasks . add ( feature )
CountryTagger . coder . GetCountryCodeAsync ( lon , lat ) . then (
countries = > {
runningTasks . delete ( feature )
try {
const oldCountry = feature . properties [ "_country" ] ;
feature . properties [ "_country" ] = countries [ 0 ] . trim ( ) . toLowerCase ( ) ;
if ( oldCountry !== feature . properties [ "_country" ] ) {
2021-12-12 17:35:08 +01:00
const tagsSource = state ? . allElements ? . getEventSourceById ( feature . properties . id ) ;
2021-12-07 02:22:56 +01:00
tagsSource ? . ping ( ) ;
}
} catch ( e ) {
console . warn ( e )
}
}
) . catch ( _ = > {
runningTasks . delete ( feature )
} )
return false ;
} )
)
this . runningTasks = runningTasks ;
}
Fix issues with camera rotation
This commit fixes at least these issues that I was aware of:
* Cardinal directions (e.g. NE) were not recognized.
* The camera icon did not rotatie when direction=* was used instead of
camera:direction, but the blue direction visualizer did.
Pietervdvn said he would have liked to convert the code for direction
normalizing to calculatedTags in a JSON file (as documented in
Docs/CalculatedTags.md), but when he saw the oneliners I had to produce
in response, I was allowed to keep it in SimpleMetaTagger.ts for now.
For your amusement, the oneliners are included below.
"calculatedTags": [
"_direction:numerical=(dir => dir === undefined ? undefined : ({N: 0, NNE: 22.5, NE: 45, ENE: 67.5, E: 90, ESE: 112.5, SE: 135, SSE: 157.5, S: 180, SSW: 202.5, SW: 225, WSW: 247.5, W: 270, WNW: 292.5, NW: 315, NNW: 337.5}[dir] ?? (isNaN(parseFloat(dir)) ? undefined : ((parseFloat(dir) % 360 + 360) % 360)))))(feat.properties['camera:direction'] ?? feat.properties.direction)",
"_direction:leftright=feat.properties['_direction:numerical'] === undefined ? undefined : (feat.properties['_direction:numerical'] <= 180 ? 'right' : 'left')"
]
2021-04-28 16:45:48 +02:00
}
2021-12-07 02:22:56 +01:00
export default class SimpleMetaTaggers {
private static readonly cardinalDirections = {
N : 0 , NNE : 22.5 , NE : 45 , ENE : 67.5 ,
E : 90 , ESE : 112.5 , SE : 135 , SSE : 157.5 ,
S : 180 , SSW : 202.5 , SW : 225 , WSW : 247.5 ,
W : 270 , WNW : 292.5 , NW : 315 , NNW : 337.5
}
Fix issues with camera rotation
This commit fixes at least these issues that I was aware of:
* Cardinal directions (e.g. NE) were not recognized.
* The camera icon did not rotatie when direction=* was used instead of
camera:direction, but the blue direction visualizer did.
Pietervdvn said he would have liked to convert the code for direction
normalizing to calculatedTags in a JSON file (as documented in
Docs/CalculatedTags.md), but when he saw the oneliners I had to produce
in response, I was allowed to keep it in SimpleMetaTagger.ts for now.
For your amusement, the oneliners are included below.
"calculatedTags": [
"_direction:numerical=(dir => dir === undefined ? undefined : ({N: 0, NNE: 22.5, NE: 45, ENE: 67.5, E: 90, ESE: 112.5, SE: 135, SSE: 157.5, S: 180, SSW: 202.5, SW: 225, WSW: 247.5, W: 270, WNW: 292.5, NW: 315, NNW: 337.5}[dir] ?? (isNaN(parseFloat(dir)) ? undefined : ((parseFloat(dir) % 360 + 360) % 360)))))(feat.properties['camera:direction'] ?? feat.properties.direction)",
"_direction:leftright=feat.properties['_direction:numerical'] === undefined ? undefined : (feat.properties['_direction:numerical'] <= 180 ? 'right' : 'left')"
]
2021-04-28 16:45:48 +02:00
2021-04-25 13:25:03 +02:00
public static readonly objectMetaInfo = new SimpleMetaTagger (
{
keys : [ "_last_edit:contributor" ,
"_last_edit:contributor:uid" ,
"_last_edit:changeset" ,
"_last_edit:timestamp" ,
2021-10-16 02:54:22 +02:00
"_version_number" ,
2021-10-22 01:42:44 +02:00
"_backend" ] ,
2021-04-25 13:25:03 +02:00
doc : "Information about the last edit of this object."
} ,
2021-09-26 17:36:39 +02:00
( feature ) = > { /*Note: also called by 'UpdateTagsFromOsmAPI'*/
2021-04-25 13:25:03 +02:00
const tgs = feature . properties ;
2021-06-15 00:28:59 +02:00
function move ( src : string , target : string ) {
if ( tgs [ src ] === undefined ) {
2021-05-10 23:51:03 +02:00
return ;
}
tgs [ target ] = tgs [ src ]
delete tgs [ src ]
}
2021-06-15 00:28:59 +02:00
move ( "user" , "_last_edit:contributor" )
move ( "uid" , "_last_edit:contributor:uid" )
move ( "changeset" , "_last_edit:changeset" )
move ( "timestamp" , "_last_edit:timestamp" )
move ( "version" , "_version_number" )
2021-09-26 17:36:39 +02:00
return true ;
2021-04-25 13:25:03 +02:00
}
)
2021-11-07 16:43:29 +01:00
2021-04-25 13:25:03 +02:00
private static latlon = new SimpleMetaTagger ( {
keys : [ "_lat" , "_lon" ] ,
doc : "The latitude and longitude of the point (or centerpoint in the case of a way/area)"
} ,
2021-03-24 01:25:57 +01:00
( feature = > {
const centerPoint = GeoOperations . centerpoint ( feature ) ;
const lat = centerPoint . geometry . coordinates [ 1 ] ;
const lon = centerPoint . geometry . coordinates [ 0 ] ;
feature . properties [ "_lat" ] = "" + lat ;
feature . properties [ "_lon" ] = "" + lon ;
feature . _lon = lon ; // This is dirty, I know
feature . _lat = lat ;
2021-09-26 17:36:39 +02:00
return true ;
2021-03-24 01:25:57 +01:00
} )
) ;
2021-10-12 02:12:45 +02:00
private static layerInfo = new SimpleMetaTagger (
{
doc : "The layer-id to which this feature belongs. Note that this might be return any applicable if `passAllFeatures` is defined." ,
2021-10-22 01:42:44 +02:00
keys : [ "_layer" ] ,
2021-10-12 02:12:45 +02:00
includesDates : false ,
} ,
( feature , freshness , layer ) = > {
2021-10-22 01:42:44 +02:00
if ( feature . properties . _layer === layer . id ) {
2021-10-12 02:12:45 +02:00
return false ;
}
feature . properties . _layer = layer . id
return true ;
}
)
2021-10-22 01:42:44 +02:00
private static noBothButLeftRight = new SimpleMetaTagger (
{
keys : [ "sidewalk:left" , "sidewalk:right" , "generic_key:left:property" , "generic_key:right:property" ] ,
doc : "Rewrites tags from 'generic_key:both:property' as 'generic_key:left:property' and 'generic_key:right:property' (and similar for sidewalk tagging). Note that this rewritten tags _will be reuploaded on a change_. To prevent to much unrelated retagging, this is only enabled if the layer has at least some lineRenderings with offset defined" ,
includesDates : false ,
cleanupRetagger : true
} ,
( ( feature , state , layer ) = > {
2021-11-07 16:34:51 +01:00
if ( ! layer . lineRendering . some ( lr = > lr . leftRightSensitive ) ) {
2021-10-22 01:42:44 +02:00
return ;
}
2021-11-07 16:34:51 +01:00
2021-12-07 02:22:56 +01:00
return SimpleMetaTaggers . removeBothTagging ( feature . properties )
2021-10-22 01:42:44 +02:00
} )
)
2021-03-24 01:25:57 +01:00
private static surfaceArea = new SimpleMetaTagger (
2021-04-25 13:25:03 +02:00
{
keys : [ "_surface" , "_surface:ha" ] ,
2021-10-10 23:38:09 +02:00
doc : "The surface area of the feature, in square meters and in hectare. Not set on points and ways" ,
isLazy : true
2021-04-25 13:25:03 +02:00
} ,
2021-03-24 01:25:57 +01:00
( feature = > {
2021-10-22 01:42:44 +02:00
2021-10-10 23:38:09 +02:00
Object . defineProperty ( feature . properties , "_surface" , {
enumerable : false ,
configurable : true ,
get : ( ) = > {
2021-10-22 01:42:44 +02:00
const sqMeters = "" + GeoOperations . surfaceAreaInSqMeters ( feature ) ;
2021-10-10 23:38:09 +02:00
delete feature . properties [ "_surface" ]
feature . properties [ "_surface" ] = sqMeters ;
return sqMeters
}
} )
Object . defineProperty ( feature . properties , "_surface:ha" , {
enumerable : false ,
configurable : true ,
get : ( ) = > {
const sqMeters = GeoOperations . surfaceAreaInSqMeters ( feature ) ;
const sqMetersHa = "" + Math . floor ( sqMeters / 1000 ) / 10 ;
delete feature . properties [ "_surface:ha" ]
feature . properties [ "_surface:ha" ] = sqMetersHa ;
return sqMetersHa
}
} )
2021-10-22 01:42:44 +02:00
2021-09-26 17:36:39 +02:00
return true ;
2021-03-24 01:25:57 +01:00
} )
) ;
2021-06-22 00:29:07 +02:00
private static canonicalize = new SimpleMetaTagger (
{
doc : "If 'units' is defined in the layoutConfig, then this metatagger will rewrite the specified keys to have the canonical form (e.g. `1meter` will be rewritten to `1m`)" ,
2021-06-24 14:03:02 +02:00
keys : [ "Theme-defined keys" ] ,
2021-06-22 00:29:07 +02:00
} ,
2021-12-12 17:35:08 +01:00
( ( feature , _ , __ , state ) = > {
const units = Utils . NoNull ( [ ] . concat ( . . . state ? . layoutToUse ? . layers ? . map ( layer = > layer . units ? ? [ ] ) ) ) ;
2021-09-21 02:10:42 +02:00
if ( units . length == 0 ) {
2021-09-17 16:54:12 +02:00
return ;
}
2021-07-13 18:52:02 +02:00
let rewritten = false ;
2021-06-22 00:29:07 +02:00
for ( const key in feature . properties ) {
2021-06-22 03:16:45 +02:00
if ( ! feature . properties . hasOwnProperty ( key ) ) {
2021-06-22 00:29:07 +02:00
continue ;
}
for ( const unit of units ) {
2021-09-30 04:13:23 +02:00
if ( unit === undefined ) {
2021-09-27 15:38:12 +02:00
continue
}
2021-09-21 02:10:42 +02:00
if ( unit . appliesToKeys === undefined ) {
2021-09-17 16:54:12 +02:00
console . error ( "The unit " , unit , "has no appliesToKey defined" )
continue
}
2021-06-22 00:29:07 +02:00
if ( ! unit . appliesToKeys . has ( key ) ) {
continue ;
}
const value = feature . properties [ key ]
2021-09-30 04:13:23 +02:00
const denom = unit . findDenomination ( value )
if ( denom === undefined ) {
// no valid value found
break ;
}
const [ , denomination ] = denom ;
2021-07-04 20:36:19 +02:00
let canonical = denomination ? . canonicalValue ( value ) ? ? undefined ;
2021-09-09 00:05:51 +02:00
if ( canonical === value ) {
2021-07-10 21:03:17 +02:00
break ;
}
console . log ( "Rewritten " , key , ` from ' ${ value } ' into ' ${ canonical } ' ` )
2021-07-24 01:59:57 +02:00
if ( canonical === undefined && ! unit . eraseInvalid ) {
2021-06-22 12:13:44 +02:00
break ;
}
2021-07-24 01:59:57 +02:00
2021-06-22 03:16:45 +02:00
feature . properties [ key ] = canonical ;
2021-07-13 18:52:02 +02:00
rewritten = true ;
2021-06-22 12:13:44 +02:00
break ;
2021-06-22 00:29:07 +02:00
}
}
2021-09-26 17:36:39 +02:00
return rewritten
2021-06-22 00:29:07 +02:00
} )
)
2021-04-18 14:24:30 +02:00
private static lngth = new SimpleMetaTagger (
2021-04-25 13:25:03 +02:00
{
keys : [ "_length" , "_length:km" ] ,
doc : "The total length of a feature in meters (and in kilometers, rounded to one decimal for '_length:km'). For a surface, the length of the perimeter"
} ,
2021-04-18 14:24:30 +02:00
( feature = > {
const l = GeoOperations . lengthInMeters ( feature )
feature . properties [ "_length" ] = "" + l
const km = Math . floor ( l / 1000 )
const kmRest = Math . round ( ( l - km * 1000 ) / 100 )
2021-04-25 13:25:03 +02:00
feature . properties [ "_length:km" ] = "" + km + "." + kmRest
2021-09-26 17:36:39 +02:00
return true ;
2021-04-18 14:24:30 +02:00
} )
)
2021-12-07 02:22:56 +01:00
public static country = new CountryTagger ( )
2021-03-24 01:25:57 +01:00
private static isOpen = new SimpleMetaTagger (
2021-04-25 13:25:03 +02:00
{
keys : [ "_isOpen" , "_isOpen:description" ] ,
doc : "If 'opening_hours' is present, it will add the current state of the feature (being 'yes' or 'no')" ,
2021-10-10 23:38:09 +02:00
includesDates : true ,
isLazy : true
2021-04-25 13:25:03 +02:00
} ,
2021-12-12 17:35:08 +01:00
( ( feature , _ , __ , state ) = > {
2021-04-25 13:25:03 +02:00
if ( Utils . runningFromConsole ) {
2021-04-22 13:30:00 +02:00
// We are running from console, thus probably creating a cache
// isOpen is irrelevant
2021-09-26 17:36:39 +02:00
return false
2021-04-22 13:30:00 +02:00
}
2021-10-22 01:42:44 +02:00
Object . defineProperty ( feature . properties , "_isOpen" , {
2021-10-10 23:38:09 +02:00
enumerable : false ,
configurable : true ,
get : ( ) = > {
delete feature . properties . _isOpen
2021-10-11 23:28:51 +02:00
feature . properties . _isOpen = undefined
2021-12-12 17:35:08 +01:00
const tagsSource = state . allElements . getEventSourceById ( feature . properties . id ) ;
2021-10-10 23:38:09 +02:00
tagsSource . addCallbackAndRunD ( tags = > {
if ( tags . opening_hours === undefined || tags . _country === undefined ) {
return ;
2021-03-24 01:25:57 +01:00
}
2021-10-10 23:38:09 +02:00
try {
const [ lon , lat ] = GeoOperations . centerpointCoordinates ( feature )
const oh = new opening_hours ( tags [ "opening_hours" ] , {
lat : lat ,
lon : lon ,
address : {
country_code : tags._country.toLowerCase ( )
}
} , { tag_key : "opening_hours" } ) ;
// AUtomatically triggered on the next change
const updateTags = ( ) = > {
const oldValueIsOpen = tags [ "_isOpen" ] ;
const oldNextChange = tags [ "_isOpen:nextTrigger" ] ? ? 0 ;
if ( oldNextChange > ( new Date ( ) ) . getTime ( ) &&
2021-10-11 23:28:51 +02:00
tags [ "_isOpen:oldvalue" ] === tags [ "opening_hours" ]
2021-10-22 01:42:44 +02:00
&& tags [ "_isOpen" ] !== undefined ) {
2021-10-10 23:38:09 +02:00
// Already calculated and should not yet be triggered
return false ;
}
tags [ "_isOpen" ] = oh . getState ( ) ? "yes" : "no" ;
const comment = oh . getComment ( ) ;
if ( comment ) {
tags [ "_isOpen:description" ] = comment ;
}
if ( oldValueIsOpen !== tags . _isOpen ) {
tagsSource . ping ( ) ;
}
const nextChange = oh . getNextChange ( ) ;
if ( nextChange !== undefined ) {
const timeout = nextChange . getTime ( ) - ( new Date ( ) ) . getTime ( ) ;
tags [ "_isOpen:nextTrigger" ] = nextChange . getTime ( ) ;
tags [ "_isOpen:oldvalue" ] = tags . opening_hours
window . setTimeout (
( ) = > {
console . log ( "Updating the _isOpen tag for " , tags . id , ", it's timer expired after" , timeout ) ;
updateTags ( ) ;
} ,
timeout
)
}
}
updateTags ( ) ;
return true ; // Our job is done, lets unregister!
} catch ( e ) {
console . warn ( "Error while parsing opening hours of " , tags . id , e ) ;
2021-10-14 14:35:04 +02:00
delete tags . _isOpen
2021-10-10 23:38:09 +02:00
tags [ "_isOpen" ] = "parse_error" ;
2021-03-24 01:25:57 +01:00
}
2021-10-10 23:38:09 +02:00
} )
2021-10-11 23:28:51 +02:00
return undefined
2021-03-24 01:25:57 +01:00
}
} )
2021-10-10 23:38:09 +02:00
2021-03-24 01:25:57 +01:00
} )
)
private static directionSimplified = new SimpleMetaTagger (
2021-04-25 13:25:03 +02:00
{
Fix issues with camera rotation
This commit fixes at least these issues that I was aware of:
* Cardinal directions (e.g. NE) were not recognized.
* The camera icon did not rotatie when direction=* was used instead of
camera:direction, but the blue direction visualizer did.
Pietervdvn said he would have liked to convert the code for direction
normalizing to calculatedTags in a JSON file (as documented in
Docs/CalculatedTags.md), but when he saw the oneliners I had to produce
in response, I was allowed to keep it in SimpleMetaTagger.ts for now.
For your amusement, the oneliners are included below.
"calculatedTags": [
"_direction:numerical=(dir => dir === undefined ? undefined : ({N: 0, NNE: 22.5, NE: 45, ENE: 67.5, E: 90, ESE: 112.5, SE: 135, SSE: 157.5, S: 180, SSW: 202.5, SW: 225, WSW: 247.5, W: 270, WNW: 292.5, NW: 315, NNW: 337.5}[dir] ?? (isNaN(parseFloat(dir)) ? undefined : ((parseFloat(dir) % 360 + 360) % 360)))))(feat.properties['camera:direction'] ?? feat.properties.direction)",
"_direction:leftright=feat.properties['_direction:numerical'] === undefined ? undefined : (feat.properties['_direction:numerical'] <= 180 ? 'right' : 'left')"
]
2021-04-28 16:45:48 +02:00
keys : [ "_direction:numerical" , "_direction:leftright" ] ,
doc : "_direction:numerical is a normalized, numerical direction based on 'camera:direction' or on 'direction'; it is only present if a valid direction is found (e.g. 38.5 or NE). _direction:leftright is either 'left' or 'right', which is left-looking on the map or 'right-looking' on the map"
2021-04-25 13:25:03 +02:00
} ,
2021-03-24 01:25:57 +01:00
( feature = > {
const tags = feature . properties ;
const direction = tags [ "camera:direction" ] ? ? tags [ "direction" ] ;
if ( direction === undefined ) {
2021-09-26 17:36:39 +02:00
return false ;
2021-03-24 01:25:57 +01:00
}
2021-12-07 02:22:56 +01:00
const n = SimpleMetaTaggers . cardinalDirections [ direction ] ? ? Number ( direction ) ;
2021-03-24 01:25:57 +01:00
if ( isNaN ( n ) ) {
2021-09-26 17:36:39 +02:00
return false ;
2021-03-24 01:25:57 +01:00
}
Fix issues with camera rotation
This commit fixes at least these issues that I was aware of:
* Cardinal directions (e.g. NE) were not recognized.
* The camera icon did not rotatie when direction=* was used instead of
camera:direction, but the blue direction visualizer did.
Pietervdvn said he would have liked to convert the code for direction
normalizing to calculatedTags in a JSON file (as documented in
Docs/CalculatedTags.md), but when he saw the oneliners I had to produce
in response, I was allowed to keep it in SimpleMetaTagger.ts for now.
For your amusement, the oneliners are included below.
"calculatedTags": [
"_direction:numerical=(dir => dir === undefined ? undefined : ({N: 0, NNE: 22.5, NE: 45, ENE: 67.5, E: 90, ESE: 112.5, SE: 135, SSE: 157.5, S: 180, SSW: 202.5, SW: 225, WSW: 247.5, W: 270, WNW: 292.5, NW: 315, NNW: 337.5}[dir] ?? (isNaN(parseFloat(dir)) ? undefined : ((parseFloat(dir) % 360 + 360) % 360)))))(feat.properties['camera:direction'] ?? feat.properties.direction)",
"_direction:leftright=feat.properties['_direction:numerical'] === undefined ? undefined : (feat.properties['_direction:numerical'] <= 180 ? 'right' : 'left')"
]
2021-04-28 16:45:48 +02:00
// The % operator has range (-360, 360). We apply a trick to get [0, 360).
const normalized = ( ( n % 360 ) + 360 ) % 360 ;
2021-03-24 01:25:57 +01:00
Fix issues with camera rotation
This commit fixes at least these issues that I was aware of:
* Cardinal directions (e.g. NE) were not recognized.
* The camera icon did not rotatie when direction=* was used instead of
camera:direction, but the blue direction visualizer did.
Pietervdvn said he would have liked to convert the code for direction
normalizing to calculatedTags in a JSON file (as documented in
Docs/CalculatedTags.md), but when he saw the oneliners I had to produce
in response, I was allowed to keep it in SimpleMetaTagger.ts for now.
For your amusement, the oneliners are included below.
"calculatedTags": [
"_direction:numerical=(dir => dir === undefined ? undefined : ({N: 0, NNE: 22.5, NE: 45, ENE: 67.5, E: 90, ESE: 112.5, SE: 135, SSE: 157.5, S: 180, SSW: 202.5, SW: 225, WSW: 247.5, W: 270, WNW: 292.5, NW: 315, NNW: 337.5}[dir] ?? (isNaN(parseFloat(dir)) ? undefined : ((parseFloat(dir) % 360 + 360) % 360)))))(feat.properties['camera:direction'] ?? feat.properties.direction)",
"_direction:leftright=feat.properties['_direction:numerical'] === undefined ? undefined : (feat.properties['_direction:numerical'] <= 180 ? 'right' : 'left')"
]
2021-04-28 16:45:48 +02:00
tags [ "_direction:numerical" ] = normalized ;
tags [ "_direction:leftright" ] = normalized <= 180 ? "right" : "left" ;
2021-09-26 17:36:39 +02:00
return true ;
2021-03-24 01:25:57 +01:00
} )
)
2021-12-07 02:22:56 +01:00
2021-03-24 01:25:57 +01:00
private static currentTime = new SimpleMetaTagger (
2021-04-25 13:25:03 +02:00
{
keys : [ "_now:date" , "_now:datetime" , "_loaded:date" , "_loaded:_datetime" ] ,
doc : "Adds the time that the data got loaded - pretty much the time of downloading from overpass. The format is YYYY-MM-DD hh:mm, aka 'sortable' aka ISO-8601-but-not-entirely" ,
includesDates : true
} ,
2021-09-26 17:36:39 +02:00
( feature , freshness ) = > {
2021-03-24 01:25:57 +01:00
const now = new Date ( ) ;
if ( typeof freshness === "string" ) {
freshness = new Date ( freshness )
}
function date ( d : Date ) {
return d . toISOString ( ) . slice ( 0 , 10 ) ;
}
function datetime ( d : Date ) {
return d . toISOString ( ) . slice ( 0 , - 5 ) . replace ( "T" , " " ) ;
}
feature . properties [ "_now:date" ] = date ( now ) ;
feature . properties [ "_now:datetime" ] = datetime ( now ) ;
feature . properties [ "_loaded:date" ] = date ( freshness ) ;
feature . properties [ "_loaded:datetime" ] = datetime ( freshness ) ;
2021-09-26 17:36:39 +02:00
return true ;
2021-03-24 01:25:57 +01:00
}
)
2021-12-07 02:22:56 +01:00
public static metatags : SimpleMetaTagger [ ] = [
SimpleMetaTaggers . latlon ,
SimpleMetaTaggers . layerInfo ,
SimpleMetaTaggers . surfaceArea ,
SimpleMetaTaggers . lngth ,
SimpleMetaTaggers . canonicalize ,
SimpleMetaTaggers . country ,
SimpleMetaTaggers . isOpen ,
SimpleMetaTaggers . directionSimplified ,
SimpleMetaTaggers . currentTime ,
SimpleMetaTaggers . objectMetaInfo ,
SimpleMetaTaggers . noBothButLeftRight
2021-03-24 01:25:57 +01:00
] ;
2021-10-22 01:42:44 +02:00
2021-12-07 02:22:56 +01:00
public static readonly lazyTags : string [ ] = [ ] . concat ( . . . SimpleMetaTaggers . metatags . filter ( tagger = > tagger . isLazy )
2021-11-07 16:43:29 +01:00
. map ( tagger = > tagger . keys ) ) ;
2021-12-07 02:22:56 +01:00
2021-03-24 01:25:57 +01:00
2021-11-07 16:34:51 +01:00
/ * *
* Edits the given object to rewrite 'both' - tagging into a 'left-right' tagging scheme .
* These changes are performed in - place .
*
* Returns 'true' is at least one change has been made
* @param tags
* /
public static removeBothTagging ( tags : any ) : boolean {
let somethingChanged = false
/ * *
* Sets the key onto the properties ( but doesn ' t overwrite if already existing )
* /
function set ( k , value ) {
if ( tags [ k ] === undefined || tags [ k ] === "" ) {
tags [ k ] = value
somethingChanged = true
}
}
if ( tags [ "sidewalk" ] ) {
const v = tags [ "sidewalk" ]
switch ( v ) {
case "none" :
case "no" :
set ( "sidewalk:left" , "no" ) ;
set ( "sidewalk:right" , "no" ) ;
break
case "both" :
set ( "sidewalk:left" , "yes" ) ;
set ( "sidewalk:right" , "yes" ) ;
break ;
case "left" :
set ( "sidewalk:left" , "yes" ) ;
set ( "sidewalk:right" , "no" ) ;
break ;
case "right" :
set ( "sidewalk:left" , "no" ) ;
set ( "sidewalk:right" , "yes" ) ;
break ;
default :
set ( "sidewalk:left" , v ) ;
set ( "sidewalk:right" , v ) ;
break ;
}
delete tags [ "sidewalk" ]
somethingChanged = true
}
const regex = /\([^:]*\):both:\(.*\)/
for ( const key in tags ) {
const v = tags [ key ]
if ( key . endsWith ( ":both" ) ) {
const strippedKey = key . substring ( 0 , key . length - ":both" . length )
set ( strippedKey + ":left" , v )
set ( strippedKey + ":right" , v )
delete tags [ key ]
continue
}
const match = key . match ( regex )
if ( match !== null ) {
const strippedKey = match [ 1 ]
const property = match [ 1 ]
set ( strippedKey + ":left:" + property , v )
set ( strippedKey + ":right:" + property , v )
console . log ( "Left-right rewritten " + key )
delete tags [ key ]
}
}
return somethingChanged
}
2021-09-21 02:10:42 +02:00
public static HelpText ( ) : BaseUIElement {
2021-06-15 00:28:59 +02:00
const subElements : ( string | BaseUIElement ) [ ] = [
2021-03-24 01:25:57 +01:00
new Combine ( [
2021-06-15 00:28:59 +02:00
"Metatags are extra tags available, in order to display more data or to give better questions." ,
"The are calculated automatically on every feature when the data arrives in the webbrowser. This document gives an overview of the available metatags." ,
"**Hint:** when using metatags, add the [query parameter](URL_Parameters.md) `debug=true` to the URL. This will include a box in the popup for features which shows all the properties of the object"
] ) . SetClass ( "flex-col" )
2021-03-24 01:25:57 +01:00
] ;
2021-06-15 00:28:59 +02:00
subElements . push ( new Title ( "Metatags calculated by MapComplete" , 2 ) )
subElements . push ( new FixedUiElement ( "The following values are always calculated, by default, by MapComplete and are available automatically on all elements in every theme" ) )
2021-12-07 02:22:56 +01:00
for ( const metatag of SimpleMetaTaggers . metatags ) {
2021-03-24 01:25:57 +01:00
subElements . push (
2021-06-15 00:28:59 +02:00
new Title ( metatag . keys . join ( ", " ) , 3 ) ,
2021-10-10 23:38:09 +02:00
metatag . doc ,
metatag . isLazy ? "This is a lazy metatag and is only calculated when needed" : ""
2021-03-24 01:25:57 +01:00
)
}
2021-06-15 00:28:59 +02:00
return new Combine ( subElements ) . SetClass ( "flex-col" )
2021-03-24 01:25:57 +01:00
}
Fix issues with camera rotation
This commit fixes at least these issues that I was aware of:
* Cardinal directions (e.g. NE) were not recognized.
* The camera icon did not rotatie when direction=* was used instead of
camera:direction, but the blue direction visualizer did.
Pietervdvn said he would have liked to convert the code for direction
normalizing to calculatedTags in a JSON file (as documented in
Docs/CalculatedTags.md), but when he saw the oneliners I had to produce
in response, I was allowed to keep it in SimpleMetaTagger.ts for now.
For your amusement, the oneliners are included below.
"calculatedTags": [
"_direction:numerical=(dir => dir === undefined ? undefined : ({N: 0, NNE: 22.5, NE: 45, ENE: 67.5, E: 90, ESE: 112.5, SE: 135, SSE: 157.5, S: 180, SSW: 202.5, SW: 225, WSW: 247.5, W: 270, WNW: 292.5, NW: 315, NNW: 337.5}[dir] ?? (isNaN(parseFloat(dir)) ? undefined : ((parseFloat(dir) % 360 + 360) % 360)))))(feat.properties['camera:direction'] ?? feat.properties.direction)",
"_direction:leftright=feat.properties['_direction:numerical'] === undefined ? undefined : (feat.properties['_direction:numerical'] <= 180 ? 'right' : 'left')"
]
2021-04-28 16:45:48 +02:00
}