Full code cleanup
This commit is contained in:
parent
8e6ee8c87f
commit
bd21212eba
246 changed files with 19418 additions and 11729 deletions
|
@ -17,7 +17,6 @@ export default class Constants {
|
|||
// Doesn't support nwr: "https://overpass.openstreetmap.fr/api/interpreter"
|
||||
]
|
||||
|
||||
|
||||
|
||||
// The user journey states thresholds when a new feature gets unlocked
|
||||
public static userJourney = {
|
||||
|
|
|
@ -44,13 +44,13 @@ export class Denomination {
|
|||
get human(): Translation {
|
||||
return this._human.Clone()
|
||||
}
|
||||
|
||||
|
||||
get humanSingular(): Translation {
|
||||
return (this._humanSingular ?? this._human).Clone()
|
||||
}
|
||||
|
||||
getToggledHuman(isSingular: UIEventSource<boolean>): BaseUIElement{
|
||||
if(this._humanSingular === undefined){
|
||||
|
||||
getToggledHuman(isSingular: UIEventSource<boolean>): BaseUIElement {
|
||||
if (this._humanSingular === undefined) {
|
||||
return this.human
|
||||
}
|
||||
return new Toggle(
|
||||
|
@ -59,7 +59,7 @@ export class Denomination {
|
|||
isSingular
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
public canonicalValue(value: string, actAsDefault?: boolean) {
|
||||
if (value === undefined) {
|
||||
return undefined;
|
||||
|
@ -68,12 +68,12 @@ export class Denomination {
|
|||
if (stripped === null) {
|
||||
return null;
|
||||
}
|
||||
if(stripped === "1" && this._canonicalSingular !== undefined){
|
||||
return "1 "+this._canonicalSingular
|
||||
if (stripped === "1" && this._canonicalSingular !== undefined) {
|
||||
return "1 " + this._canonicalSingular
|
||||
}
|
||||
return stripped + " " + this.canonical;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the core value (without unit) if:
|
||||
* - the value ends with the canonical or an alternative value (or begins with if prefix is set)
|
||||
|
@ -89,30 +89,31 @@ export class Denomination {
|
|||
|
||||
value = value.toLowerCase()
|
||||
const self = this;
|
||||
function startsWith(key){
|
||||
if(self.prefix){
|
||||
|
||||
function startsWith(key) {
|
||||
if (self.prefix) {
|
||||
return value.startsWith(key)
|
||||
}else{
|
||||
} else {
|
||||
return value.endsWith(key)
|
||||
}
|
||||
}
|
||||
|
||||
function substr(key){
|
||||
if(self.prefix){
|
||||
|
||||
function substr(key) {
|
||||
if (self.prefix) {
|
||||
return value.substr(key.length).trim()
|
||||
}else{
|
||||
} else {
|
||||
return value.substring(0, value.length - key.length).trim()
|
||||
}
|
||||
}
|
||||
|
||||
if(this.canonical !== "" && startsWith(this.canonical.toLowerCase())){
|
||||
|
||||
if (this.canonical !== "" && startsWith(this.canonical.toLowerCase())) {
|
||||
return substr(this.canonical)
|
||||
}
|
||||
|
||||
if(this._canonicalSingular !== undefined && this._canonicalSingular !== "" && startsWith(this._canonicalSingular)){
|
||||
}
|
||||
|
||||
if (this._canonicalSingular !== undefined && this._canonicalSingular !== "" && startsWith(this._canonicalSingular)) {
|
||||
return substr(this._canonicalSingular)
|
||||
}
|
||||
|
||||
|
||||
for (const alternativeValue of this.alternativeDenominations) {
|
||||
if (startsWith(alternativeValue)) {
|
||||
return substr(alternativeValue);
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
import {UIEventSource} from "../Logic/UIEventSource";
|
||||
import LayerConfig from "./ThemeConfig/LayerConfig";
|
||||
import {And} from "../Logic/Tags/And";
|
||||
import FilterConfig from "./ThemeConfig/FilterConfig";
|
||||
|
||||
export default interface FilteredLayer {
|
||||
readonly isDisplayed: UIEventSource<boolean>;
|
||||
readonly appliedFilters: UIEventSource<{filter: FilterConfig, selected: number}[]>;
|
||||
readonly appliedFilters: UIEventSource<{ filter: FilterConfig, selected: number }[]>;
|
||||
readonly layerDef: LayerConfig;
|
||||
}
|
|
@ -18,7 +18,7 @@ export default class FilterConfig {
|
|||
if (json.id === undefined) {
|
||||
throw `A filter without id was found at ${context}`
|
||||
}
|
||||
if(json.id.match(/^[a-zA-Z0-9_-]*$/) === null){
|
||||
if (json.id.match(/^[a-zA-Z0-9_-]*$/) === null) {
|
||||
throw `A filter with invalid id was found at ${context}. Ids should only contain letters, numbers or - _`
|
||||
|
||||
}
|
||||
|
@ -42,9 +42,9 @@ export default class FilterConfig {
|
|||
|
||||
return {question: question, osmTags: osmTags};
|
||||
});
|
||||
|
||||
if(this.options.length > 1 && this.options[0].osmTags["and"]?.length !== 0){
|
||||
throw "Error in "+context+"."+this.id+": the first option of a multi-filter should always be the 'reset' option and not have any filters"
|
||||
|
||||
if (this.options.length > 1 && this.options[0].osmTags["and"]?.length !== 0) {
|
||||
throw "Error in " + context + "." + this.id + ": the first option of a multi-filter should always be the 'reset' option and not have any filters"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -64,14 +64,14 @@ export interface LayerConfigJson {
|
|||
* NOTE: the previous format was 'overpassTags: AndOrTagConfigJson | string', which is interpreted as a shorthand for source: {osmTags: "key=value"}
|
||||
* While still supported, this is considered deprecated
|
||||
*/
|
||||
source: ({ osmTags: AndOrTagConfigJson | string, overpassScript?: string } |
|
||||
source: ({ osmTags: AndOrTagConfigJson | string, overpassScript?: string } |
|
||||
{ osmTags: AndOrTagConfigJson | string, geoJson: string, geoJsonZoomLevel?: number, isOsmCache?: boolean, mercatorCrs?: boolean }) & ({
|
||||
/**
|
||||
* The maximum amount of seconds that a tile is allowed to linger in the cache
|
||||
*/
|
||||
maxCacheAge?: number
|
||||
})
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* A list of extra tags to calculate, specified as "keyToAssignTo=javascript-expression".
|
||||
|
@ -93,8 +93,8 @@ export interface LayerConfigJson {
|
|||
|
||||
/**
|
||||
* This tag rendering should either be 'yes' or 'no'. If 'no' is returned, then the feature will be hidden from view.
|
||||
* This is useful to hide certain features from view.
|
||||
*
|
||||
* This is useful to hide certain features from view.
|
||||
*
|
||||
* Important: hiding features does not work dynamically, but is only calculated when the data is first renders.
|
||||
* This implies that it is not possible to hide a feature after a tagging change
|
||||
*
|
||||
|
@ -207,15 +207,13 @@ export interface LayerConfigJson {
|
|||
* This is mainly create questions for a 'left' and a 'right' side of the road.
|
||||
* These will be grouped and questions will be asked together
|
||||
*/
|
||||
tagRenderings?: (string | {builtin: string, override: any} | TagRenderingConfigJson | {
|
||||
tagRenderings?: (string | { builtin: string, override: any } | TagRenderingConfigJson | {
|
||||
rewrite: {
|
||||
sourceString: string,
|
||||
into: string[]
|
||||
}[],
|
||||
renderings: (string | {builtin: string, override: any} | TagRenderingConfigJson)[]
|
||||
renderings: (string | { builtin: string, override: any } | TagRenderingConfigJson)[]
|
||||
}) [],
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
|
@ -273,15 +271,15 @@ export interface LayerConfigJson {
|
|||
|
||||
/**
|
||||
* Indicates if a point can be moved and configures the modalities.
|
||||
*
|
||||
*
|
||||
* A feature can be moved by MapComplete if:
|
||||
*
|
||||
*
|
||||
* - It is a point
|
||||
* - The point is _not_ part of a way or a a relation.
|
||||
*
|
||||
*
|
||||
* Off by default. Can be enabled by setting this flag or by configuring.
|
||||
*/
|
||||
allowMove?: boolean | MoveConfigJson
|
||||
allowMove?: boolean | MoveConfigJson
|
||||
|
||||
/**
|
||||
* IF set, a 'split this road' button is shown
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import {TagRenderingConfigJson} from "./TagRenderingConfigJson";
|
||||
import {LayerConfigJson} from "./LayerConfigJson";
|
||||
import TilesourceConfigJson from "./TilesourceConfigJson";
|
||||
|
||||
|
@ -15,7 +14,7 @@ import TilesourceConfigJson from "./TilesourceConfigJson";
|
|||
* General remark: a type (string | any) indicates either a fixed or a translatable string.
|
||||
*/
|
||||
export interface LayoutConfigJson {
|
||||
|
||||
|
||||
/**
|
||||
* The id of this layout.
|
||||
*
|
||||
|
@ -216,7 +215,7 @@ export interface LayoutConfigJson {
|
|||
*/
|
||||
maxZoom?: number,
|
||||
/**
|
||||
* The number of elements per tile needed to start clustering
|
||||
* The number of elements per tile needed to start clustering
|
||||
* If clustering is defined, defaults to 25
|
||||
*/
|
||||
minNeededElements?: number
|
||||
|
|
|
@ -2,9 +2,9 @@ import {TagRenderingConfigJson} from "./TagRenderingConfigJson";
|
|||
|
||||
/**
|
||||
* The LineRenderingConfig gives all details onto how to render a single line of a feature.
|
||||
*
|
||||
*
|
||||
* This can be used if:
|
||||
*
|
||||
*
|
||||
* - The feature is a line
|
||||
* - The feature is an area
|
||||
*/
|
||||
|
@ -28,9 +28,9 @@ export default interface LineRenderingConfigJson {
|
|||
dashArray?: string | TagRenderingConfigJson
|
||||
|
||||
/**
|
||||
* The number of pixels this line should be moved.
|
||||
* The number of pixels this line should be moved.
|
||||
* Use a positive numbe to move to the right, a negative to move to the left (left/right as defined by the drawing direction of the line).
|
||||
*
|
||||
*
|
||||
* IMPORTANT: MapComplete will already normalize 'key:both:property' and 'key:both' into the corresponding 'key:left' and 'key:right' tagging (same for 'sidewalk=left/right/both' which is rewritten to 'sidewalk:left' and 'sidewalk:right')
|
||||
* This simplifies programming. Refer to the CalculatedTags.md-documentation for more details
|
||||
*/
|
||||
|
|
|
@ -3,9 +3,9 @@ import {AndOrTagConfigJson} from "./TagConfigJson";
|
|||
|
||||
/**
|
||||
* The PointRenderingConfig gives all details onto how to render a single point of a feature.
|
||||
*
|
||||
*
|
||||
* This can be used if:
|
||||
*
|
||||
*
|
||||
* - The feature is a point
|
||||
* - To render something at the centroid of an area, or at the start, end or projected centroid of a way
|
||||
*/
|
||||
|
@ -16,7 +16,7 @@ export default interface PointRenderingConfigJson {
|
|||
* Using `location: ["point", "centroid"] will always render centerpoint
|
||||
*/
|
||||
location: ("point" | "centroid" | "start" | "end")[]
|
||||
|
||||
|
||||
/**
|
||||
* The icon for an element.
|
||||
* Note that this also doubles as the icon for this layer (rendered with the overpass-tags) ánd the icon in the presets.
|
||||
|
|
|
@ -17,7 +17,7 @@ export interface TagRenderingConfigJson {
|
|||
* The first tagRendering of a group will always be a sticky element.
|
||||
*/
|
||||
group?: string
|
||||
|
||||
|
||||
/**
|
||||
* Renders this value. Note that "{key}"-parts are substituted by the corresponding values of the element.
|
||||
* If neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.
|
||||
|
@ -89,13 +89,13 @@ export interface TagRenderingConfigJson {
|
|||
* Allows fixed-tag inputs, shown either as radiobuttons or as checkboxes
|
||||
*/
|
||||
mappings?: {
|
||||
|
||||
|
||||
/**
|
||||
* If this condition is met, then the text under `then` will be shown.
|
||||
* If no value matches, and the user selects this mapping as an option, then these tags will be uploaded to OSM.
|
||||
*
|
||||
*
|
||||
* For example: {'if': 'diet:vegetarion=yes', 'then':'A vegetarian option is offered here'}
|
||||
*
|
||||
*
|
||||
* This can be an substituting-tag as well, e.g. {'if': 'addr:street:={_calculated_nearby_streetname}', 'then': '{_calculated_nearby_streetname}'}
|
||||
*/
|
||||
if: AndOrTagConfigJson | string,
|
||||
|
|
|
@ -12,12 +12,11 @@ export default interface UnitConfigJson {
|
|||
/**
|
||||
* The possible denominations
|
||||
*/
|
||||
applicableUnits:ApplicableUnitJson[]
|
||||
applicableUnits: ApplicableUnitJson[]
|
||||
|
||||
}
|
||||
|
||||
export interface ApplicableUnitJson
|
||||
{
|
||||
export interface ApplicableUnitJson {
|
||||
/**
|
||||
* The canonical value which will be added to the text.
|
||||
* e.g. "m" for meters
|
||||
|
@ -28,8 +27,8 @@ export interface ApplicableUnitJson
|
|||
* The canonical denomination in the case that the unit is precisely '1'
|
||||
*/
|
||||
canonicalDenominationSingular?: string,
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A list of alternative values which can occur in the OSM database - used for parsing.
|
||||
*/
|
||||
|
|
|
@ -264,7 +264,7 @@ export default class LayerConfig extends WithContextLoader {
|
|||
}
|
||||
}
|
||||
|
||||
public defaultIcon() : BaseUIElement | undefined{
|
||||
public defaultIcon(): BaseUIElement | undefined {
|
||||
const mapRendering = this.mapRendering.filter(r => r.location.has("point"))[0]
|
||||
if (mapRendering === undefined) {
|
||||
return undefined
|
||||
|
|
|
@ -52,8 +52,8 @@ export default class LayoutConfig {
|
|||
public readonly overpassMaxZoom: number
|
||||
public readonly osmApiTileSize: number
|
||||
public readonly official: boolean;
|
||||
public readonly trackAllNodes : boolean;
|
||||
|
||||
public readonly trackAllNodes: boolean;
|
||||
|
||||
constructor(json: LayoutConfigJson, official = true, context?: string) {
|
||||
this.official = official;
|
||||
this.id = json.id;
|
||||
|
@ -63,7 +63,7 @@ export default class LayoutConfig {
|
|||
this.version = json.version;
|
||||
this.language = [];
|
||||
this.trackAllNodes = false
|
||||
|
||||
|
||||
if (typeof json.language === "string") {
|
||||
this.language = [json.language];
|
||||
} else {
|
||||
|
@ -87,32 +87,32 @@ export default class LayoutConfig {
|
|||
this.startZoom = json.startZoom;
|
||||
this.startLat = json.startLat;
|
||||
this.startLon = json.startLon;
|
||||
if(json.widenFactor <= 0){
|
||||
throw "Widenfactor too small, shoud be > 0"
|
||||
if (json.widenFactor <= 0) {
|
||||
throw "Widenfactor too small, shoud be > 0"
|
||||
}
|
||||
if(json.widenFactor > 20){
|
||||
throw "Widenfactor is very big, use a value between 1 and 5 (current value is "+json.widenFactor+") at "+context
|
||||
if (json.widenFactor > 20) {
|
||||
throw "Widenfactor is very big, use a value between 1 and 5 (current value is " + json.widenFactor + ") at " + context
|
||||
}
|
||||
|
||||
|
||||
this.widenFactor = json.widenFactor ?? 1.5;
|
||||
|
||||
|
||||
this.defaultBackgroundId = json.defaultBackgroundId;
|
||||
this.tileLayerSources = (json.tileLayerSources??[]).map((config, i) => new TilesourceConfig(config, `${this.id}.tileLayerSources[${i}]`))
|
||||
const layerInfo = LayoutConfig.ExtractLayers(json, official, context);
|
||||
this.tileLayerSources = (json.tileLayerSources ?? []).map((config, i) => new TilesourceConfig(config, `${this.id}.tileLayerSources[${i}]`))
|
||||
const layerInfo = LayoutConfig.ExtractLayers(json, official, context);
|
||||
this.layers = layerInfo.layers
|
||||
this.trackAllNodes = layerInfo.extractAllNodes
|
||||
|
||||
|
||||
|
||||
|
||||
this.clustering = {
|
||||
maxZoom: 16,
|
||||
minNeededElements: 25,
|
||||
};
|
||||
if(json.clustering === false){
|
||||
if (json.clustering === false) {
|
||||
this.clustering = {
|
||||
maxZoom: 0,
|
||||
minNeededElements: 100000,
|
||||
};
|
||||
}else if (json.clustering) {
|
||||
} else if (json.clustering) {
|
||||
this.clustering = {
|
||||
maxZoom: json.clustering.maxZoom ?? 18,
|
||||
minNeededElements: json.clustering.minNeededElements ?? 25,
|
||||
|
@ -124,7 +124,7 @@ export default class LayoutConfig {
|
|||
if (json.hideInOverview) {
|
||||
throw "The json for " + this.id + " contains a 'hideInOverview'. Did you mean hideFromOverview instead?"
|
||||
}
|
||||
this.lockLocation = <[[number, number], [number, number]]> json.lockLocation ?? undefined;
|
||||
this.lockLocation = <[[number, number], [number, number]]>json.lockLocation ?? undefined;
|
||||
this.enableUserBadge = json.enableUserBadge ?? true;
|
||||
this.enableShareScreen = json.enableShareScreen ?? true;
|
||||
this.enableMoreQuests = json.enableMoreQuests ?? true;
|
||||
|
@ -139,10 +139,10 @@ export default class LayoutConfig {
|
|||
this.enableIframePopout = json.enableIframePopout ?? true
|
||||
this.customCss = json.customCss;
|
||||
this.overpassUrl = Constants.defaultOverpassUrls
|
||||
if(json.overpassUrl !== undefined){
|
||||
if(typeof json.overpassUrl === "string"){
|
||||
if (json.overpassUrl !== undefined) {
|
||||
if (typeof json.overpassUrl === "string") {
|
||||
this.overpassUrl = [json.overpassUrl]
|
||||
}else{
|
||||
} else {
|
||||
this.overpassUrl = json.overpassUrl
|
||||
}
|
||||
}
|
||||
|
@ -152,11 +152,11 @@ export default class LayoutConfig {
|
|||
|
||||
}
|
||||
|
||||
private static ExtractLayers(json: LayoutConfigJson, official: boolean, context: string): {layers: LayerConfig[], extractAllNodes: boolean} {
|
||||
private static ExtractLayers(json: LayoutConfigJson, official: boolean, context: string): { layers: LayerConfig[], extractAllNodes: boolean } {
|
||||
const result: LayerConfig[] = []
|
||||
let exportAllNodes = false
|
||||
json.layers.forEach((layer, i) => {
|
||||
|
||||
|
||||
if (typeof layer === "string") {
|
||||
if (AllKnownLayers.sharedLayersJson.get(layer) !== undefined) {
|
||||
if (json.overrideAll !== undefined) {
|
||||
|
@ -183,7 +183,7 @@ export default class LayoutConfig {
|
|||
result.push(newLayer)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
// @ts-ignore
|
||||
let names = layer.builtin;
|
||||
if (typeof names === "string") {
|
||||
|
@ -191,11 +191,11 @@ export default class LayoutConfig {
|
|||
}
|
||||
names.forEach(name => {
|
||||
|
||||
if(name === "type_node"){
|
||||
if (name === "type_node") {
|
||||
// This is a very special layer which triggers special behaviour
|
||||
exportAllNodes = true;
|
||||
}
|
||||
|
||||
|
||||
const shared = AllKnownLayers.sharedLayersJson.get(name);
|
||||
if (shared === undefined) {
|
||||
throw `Unknown shared/builtin layer ${name} at ${context}.layers[${i}]. Available layers are ${Array.from(AllKnownLayers.sharedLayersJson.keys()).join(", ")}`;
|
||||
|
@ -287,8 +287,8 @@ export default class LayoutConfig {
|
|||
})
|
||||
return new LayoutConfig(JSON.parse(originalJson), false, "Layout rewriting")
|
||||
}
|
||||
|
||||
public isLeftRightSensitive(){
|
||||
|
||||
public isLeftRightSensitive() {
|
||||
return this.layers.some(l => l.isLeftRightSensitive())
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
import PointRenderingConfigJson from "./Json/PointRenderingConfigJson";
|
||||
import WithContextLoader from "./WithContextLoader";
|
||||
import {UIEventSource} from "../../Logic/UIEventSource";
|
||||
import TagRenderingConfig from "./TagRenderingConfig";
|
||||
import {Utils} from "../../Utils";
|
||||
import LineRenderingConfigJson from "./Json/LineRenderingConfigJson";
|
||||
|
|
|
@ -15,7 +15,7 @@ import {VariableUiElement} from "../../UI/Base/VariableUIElement";
|
|||
|
||||
export default class PointRenderingConfig extends WithContextLoader {
|
||||
|
||||
private static readonly allowed_location_codes = new Set<string>(["point", "centroid","start","end"])
|
||||
private static readonly allowed_location_codes = new Set<string>(["point", "centroid", "start", "end"])
|
||||
public readonly location: Set<"point" | "centroid" | "start" | "end">
|
||||
|
||||
public readonly icon: TagRenderingConfig;
|
||||
|
@ -26,34 +26,34 @@ export default class PointRenderingConfig extends WithContextLoader {
|
|||
|
||||
constructor(json: PointRenderingConfigJson, context: string) {
|
||||
super(json, context)
|
||||
|
||||
if(typeof json.location === "string"){
|
||||
|
||||
if (typeof json.location === "string") {
|
||||
json.location = [json.location]
|
||||
}
|
||||
|
||||
|
||||
this.location = new Set(json.location)
|
||||
|
||||
|
||||
this.location.forEach(l => {
|
||||
const allowed = PointRenderingConfig.allowed_location_codes
|
||||
if(!allowed.has(l)){
|
||||
if (!allowed.has(l)) {
|
||||
throw `A point rendering has an invalid location: '${l}' is not one of ${Array.from(allowed).join(", ")} (at ${context}.location)`
|
||||
}
|
||||
})
|
||||
|
||||
if(json.icon === undefined && json.label === undefined){
|
||||
|
||||
if (json.icon === undefined && json.label === undefined) {
|
||||
throw `A point rendering should define at least an icon or a label`
|
||||
}
|
||||
|
||||
if(this.location.size == 0){
|
||||
throw "A pointRendering should have at least one 'location' to defined where it should be rendered. (At "+context+".location)"
|
||||
if (this.location.size == 0) {
|
||||
throw "A pointRendering should have at least one 'location' to defined where it should be rendered. (At " + context + ".location)"
|
||||
}
|
||||
this.icon = this.tr("icon", "");
|
||||
this.iconBadges = (json.iconBadges ?? []).map((overlay, i) => {
|
||||
let tr : TagRenderingConfig;
|
||||
let tr: TagRenderingConfig;
|
||||
if (typeof overlay.then === "string" &&
|
||||
SharedTagRenderings.SharedIcons.get(overlay.then) !== undefined) {
|
||||
tr = SharedTagRenderings.SharedIcons.get(overlay.then);
|
||||
}else{
|
||||
} else {
|
||||
tr = new TagRenderingConfig(
|
||||
overlay.then,
|
||||
`iconBadges.${i}`
|
||||
|
@ -77,6 +77,43 @@ export default class PointRenderingConfig extends WithContextLoader {
|
|||
this.rotation = this.tr("rotation", "0");
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a single HTML spec (either a single image path OR "image_path_to_known_svg:fill-colour", returns a fixedUIElement containing that
|
||||
* The element will fill 100% and be positioned absolutely with top:0 and left: 0
|
||||
*/
|
||||
private static FromHtmlSpec(htmlSpec: string, style: string, isBadge = false): BaseUIElement {
|
||||
if (htmlSpec === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
const match = htmlSpec.match(/([a-zA-Z0-9_]*):([^;]*)/);
|
||||
if (match !== null && Svg.All[match[1] + ".svg"] !== undefined) {
|
||||
const svg = (Svg.All[match[1] + ".svg"] as string)
|
||||
const targetColor = match[2]
|
||||
const img = new Img(svg.replace(/#000000/g, targetColor), true)
|
||||
.SetStyle(style)
|
||||
if (isBadge) {
|
||||
img.SetClass("badge")
|
||||
}
|
||||
return img
|
||||
} else {
|
||||
return new FixedUiElement(`<img src="${htmlSpec}" style="${style}" />`);
|
||||
}
|
||||
}
|
||||
|
||||
private static FromHtmlMulti(multiSpec: string, rotation: string, isBadge: boolean, defaultElement: BaseUIElement = undefined) {
|
||||
if (multiSpec === undefined) {
|
||||
return defaultElement
|
||||
}
|
||||
const style = `width:100%;height:100%;transform: rotate( ${rotation} );display:block;position: absolute; top: 0; left: 0`;
|
||||
|
||||
const htmlDefs = multiSpec.trim()?.split(";") ?? []
|
||||
const elements = Utils.NoEmpty(htmlDefs).map(def => PointRenderingConfig.FromHtmlSpec(def, style, isBadge))
|
||||
if (elements.length === 0) {
|
||||
return defaultElement
|
||||
} else {
|
||||
return new Combine(elements).SetClass("relative block w-full h-full")
|
||||
}
|
||||
}
|
||||
|
||||
public ExtractImages(): Set<string> {
|
||||
const parts: Set<string>[] = [];
|
||||
|
@ -92,44 +129,6 @@ export default class PointRenderingConfig extends WithContextLoader {
|
|||
return allIcons;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a single HTML spec (either a single image path OR "image_path_to_known_svg:fill-colour", returns a fixedUIElement containing that
|
||||
* The element will fill 100% and be positioned absolutely with top:0 and left: 0
|
||||
*/
|
||||
private static FromHtmlSpec(htmlSpec: string, style: string, isBadge = false): BaseUIElement {
|
||||
if (htmlSpec === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
const match = htmlSpec.match(/([a-zA-Z0-9_]*):([^;]*)/);
|
||||
if (match !== null && Svg.All[match[1] + ".svg"] !== undefined) {
|
||||
const svg = (Svg.All[match[1] + ".svg"] as string)
|
||||
const targetColor = match[2]
|
||||
const img = new Img(svg.replace(/#000000/g, targetColor), true)
|
||||
.SetStyle(style)
|
||||
if(isBadge){
|
||||
img.SetClass("badge")
|
||||
}
|
||||
return img
|
||||
} else {
|
||||
return new FixedUiElement(`<img src="${htmlSpec}" style="${style}" />`);
|
||||
}
|
||||
}
|
||||
|
||||
private static FromHtmlMulti(multiSpec: string, rotation: string , isBadge: boolean, defaultElement: BaseUIElement = undefined){
|
||||
if(multiSpec === undefined){
|
||||
return defaultElement
|
||||
}
|
||||
const style = `width:100%;height:100%;transform: rotate( ${rotation} );display:block;position: absolute; top: 0; left: 0`;
|
||||
|
||||
const htmlDefs = multiSpec.trim()?.split(";") ?? []
|
||||
const elements = Utils.NoEmpty(htmlDefs).map(def => PointRenderingConfig.FromHtmlSpec(def, style, isBadge))
|
||||
if (elements.length === 0) {
|
||||
return defaultElement
|
||||
} else {
|
||||
return new Combine(elements).SetClass("relative block w-full h-full")
|
||||
}
|
||||
}
|
||||
|
||||
public GetSimpleIcon(tags: UIEventSource<any>): BaseUIElement {
|
||||
const self = this;
|
||||
if (this.icon === undefined) {
|
||||
|
@ -137,58 +136,16 @@ export default class PointRenderingConfig extends WithContextLoader {
|
|||
}
|
||||
return new VariableUiElement(tags.map(tags => {
|
||||
const rotation = Utils.SubstituteKeys(self.rotation?.GetRenderValue(tags)?.txt ?? "0deg", tags)
|
||||
|
||||
|
||||
const htmlDefs = Utils.SubstituteKeys(self.icon.GetRenderValue(tags)?.txt, tags)
|
||||
let defaultPin : BaseUIElement = undefined
|
||||
if(self.label === undefined){
|
||||
defaultPin = Svg.teardrop_with_hole_green_svg()
|
||||
let defaultPin: BaseUIElement = undefined
|
||||
if (self.label === undefined) {
|
||||
defaultPin = Svg.teardrop_with_hole_green_svg()
|
||||
}
|
||||
return PointRenderingConfig.FromHtmlMulti(htmlDefs, rotation,false, defaultPin)
|
||||
return PointRenderingConfig.FromHtmlMulti(htmlDefs, rotation, false, defaultPin)
|
||||
})).SetClass("w-full h-full block")
|
||||
}
|
||||
|
||||
private GetBadges(tags: UIEventSource<any>): BaseUIElement {
|
||||
if (this.iconBadges.length === 0) {
|
||||
return undefined
|
||||
}
|
||||
return new VariableUiElement(
|
||||
tags.map(tags => {
|
||||
|
||||
const badgeElements = this.iconBadges.map(badge => {
|
||||
|
||||
if (!badge.if.matchesProperties(tags)) {
|
||||
// Doesn't match...
|
||||
return undefined
|
||||
}
|
||||
|
||||
const htmlDefs = Utils.SubstituteKeys(badge.then.GetRenderValue(tags)?.txt, tags)
|
||||
const badgeElement= PointRenderingConfig.FromHtmlMulti(htmlDefs, "0", true)?.SetClass("block relative")
|
||||
if(badgeElement === undefined){
|
||||
return undefined;
|
||||
}
|
||||
return new Combine([badgeElement]).SetStyle("width: 1.5rem").SetClass("block")
|
||||
|
||||
})
|
||||
|
||||
return new Combine(badgeElements).SetClass("inline-flex h-full")
|
||||
})).SetClass("absolute bottom-0 right-1/3 h-1/2 w-0")
|
||||
}
|
||||
|
||||
private GetLabel(tags: UIEventSource<any>): BaseUIElement {
|
||||
if (this.label === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
const self = this;
|
||||
return new VariableUiElement(tags.map(tags => {
|
||||
const label = self.label
|
||||
?.GetRenderValue(tags)
|
||||
?.Subs(tags)
|
||||
?.SetClass("block text-center")
|
||||
return new Combine([label]).SetClass("flex flex-col items-center mt-1")
|
||||
}))
|
||||
|
||||
}
|
||||
|
||||
public GenerateLeafletStyle(
|
||||
tags: UIEventSource<any>,
|
||||
clickable: boolean,
|
||||
|
@ -246,9 +203,9 @@ export default class PointRenderingConfig extends WithContextLoader {
|
|||
const iconAndBadges = new Combine([this.GetSimpleIcon(tags), this.GetBadges(tags)])
|
||||
.SetClass("block relative")
|
||||
|
||||
if(!options?.noSize){
|
||||
if (!options?.noSize) {
|
||||
iconAndBadges.SetStyle(`width: ${iconW}px; height: ${iconH}px`)
|
||||
}else{
|
||||
} else {
|
||||
iconAndBadges.SetClass("w-full h-full")
|
||||
}
|
||||
|
||||
|
@ -264,4 +221,46 @@ export default class PointRenderingConfig extends WithContextLoader {
|
|||
};
|
||||
}
|
||||
|
||||
private GetBadges(tags: UIEventSource<any>): BaseUIElement {
|
||||
if (this.iconBadges.length === 0) {
|
||||
return undefined
|
||||
}
|
||||
return new VariableUiElement(
|
||||
tags.map(tags => {
|
||||
|
||||
const badgeElements = this.iconBadges.map(badge => {
|
||||
|
||||
if (!badge.if.matchesProperties(tags)) {
|
||||
// Doesn't match...
|
||||
return undefined
|
||||
}
|
||||
|
||||
const htmlDefs = Utils.SubstituteKeys(badge.then.GetRenderValue(tags)?.txt, tags)
|
||||
const badgeElement = PointRenderingConfig.FromHtmlMulti(htmlDefs, "0", true)?.SetClass("block relative")
|
||||
if (badgeElement === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
return new Combine([badgeElement]).SetStyle("width: 1.5rem").SetClass("block")
|
||||
|
||||
})
|
||||
|
||||
return new Combine(badgeElements).SetClass("inline-flex h-full")
|
||||
})).SetClass("absolute bottom-0 right-1/3 h-1/2 w-0")
|
||||
}
|
||||
|
||||
private GetLabel(tags: UIEventSource<any>): BaseUIElement {
|
||||
if (this.label === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
const self = this;
|
||||
return new VariableUiElement(tags.map(tags => {
|
||||
const label = self.label
|
||||
?.GetRenderValue(tags)
|
||||
?.Subs(tags)
|
||||
?.SetClass("block text-center")
|
||||
return new Combine([label]).SetClass("flex flex-col items-center mt-1")
|
||||
}))
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -8,7 +8,7 @@ export default class SourceConfig {
|
|||
public readonly geojsonSource?: string;
|
||||
public readonly geojsonZoomLevel?: number;
|
||||
public readonly isOsmCacheLayer: boolean;
|
||||
public readonly mercatorCrs: boolean;
|
||||
public readonly mercatorCrs: boolean;
|
||||
|
||||
constructor(params: {
|
||||
mercatorCrs?: boolean;
|
||||
|
@ -36,11 +36,12 @@ export default class SourceConfig {
|
|||
console.error(params)
|
||||
throw `Source said it is a OSM-cached layer, but didn't define the actual source of the cache (in context ${context})`
|
||||
}
|
||||
if(params.geojsonSource !== undefined && params.geojsonSourceLevel !== undefined){
|
||||
if(! ["x","y","x_min","x_max","y_min","Y_max"].some(toSearch => params.geojsonSource.indexOf(toSearch) > 0)){
|
||||
if (params.geojsonSource !== undefined && params.geojsonSourceLevel !== undefined) {
|
||||
if (!["x", "y", "x_min", "x_max", "y_min", "Y_max"].some(toSearch => params.geojsonSource.indexOf(toSearch) > 0)) {
|
||||
throw `Source defines a geojson-zoomLevel, but does not specify {x} nor {y} (or equivalent), this is probably a bug (in context ${context})`
|
||||
}}
|
||||
this.osmTags = params.osmTags ?? new RegexTag("id",/.*/);
|
||||
}
|
||||
}
|
||||
this.osmTags = params.osmTags ?? new RegexTag("id", /.*/);
|
||||
this.overpassScript = params.overpassScript;
|
||||
this.geojsonSource = params.geojsonSource;
|
||||
this.geojsonZoomLevel = params.geojsonSourceLevel;
|
||||
|
|
|
@ -49,14 +49,14 @@ export default class TagRenderingConfig {
|
|||
this.question = null;
|
||||
this.condition = null;
|
||||
}
|
||||
|
||||
|
||||
if(typeof json === "number"){
|
||||
this.render = Translations.WT( ""+json)
|
||||
|
||||
|
||||
if (typeof json === "number") {
|
||||
this.render = Translations.WT("" + json)
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
if (json === undefined) {
|
||||
throw "Initing a TagRenderingConfig with undefined in " + context;
|
||||
}
|
||||
|
@ -66,7 +66,7 @@ export default class TagRenderingConfig {
|
|||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
this.id = json.id ?? "";
|
||||
this.group = json.group ?? "";
|
||||
this.render = Translations.T(json.render, context + ".render");
|
||||
|
@ -74,7 +74,7 @@ export default class TagRenderingConfig {
|
|||
this.condition = TagUtils.Tag(json.condition ?? {"and": []}, `${context}.condition`);
|
||||
if (json.freeform) {
|
||||
|
||||
if(json.freeform.addExtraTags !== undefined && json.freeform.addExtraTags.map === undefined){
|
||||
if (json.freeform.addExtraTags !== undefined && json.freeform.addExtraTags.map === undefined) {
|
||||
throw `Freeform.addExtraTags should be a list of strings - not a single string (at ${context})`
|
||||
}
|
||||
this.freeform = {
|
||||
|
@ -134,8 +134,8 @@ export default class TagRenderingConfig {
|
|||
if (typeof mapping.if !== "string" && mapping.if["length"] !== undefined) {
|
||||
throw `${ctx}: Invalid mapping: "if" is defined as an array. Use {"and": <your conditions>} or {"or": <your conditions>} instead`
|
||||
}
|
||||
|
||||
if(mapping.addExtraTags !== undefined && this.multiAnswer){
|
||||
|
||||
if (mapping.addExtraTags !== undefined && this.multiAnswer) {
|
||||
throw `${ctx}: Invalid mapping: got a multi-Answer with addExtraTags; this is not allowed`
|
||||
}
|
||||
|
||||
|
@ -150,7 +150,7 @@ export default class TagRenderingConfig {
|
|||
ifnot: (mapping.ifnot !== undefined ? TagUtils.Tag(mapping.ifnot, `${ctx}.ifnot`) : undefined),
|
||||
then: Translations.T(mapping.then, `${ctx}.then`),
|
||||
hideInAnswer: hideInAnswer,
|
||||
addExtraTags: (mapping.addExtraTags??[]).map((str, j) => TagUtils.SimpleTag(str, `${ctx}.addExtraTags[${j}]`))
|
||||
addExtraTags: (mapping.addExtraTags ?? []).map((str, j) => TagUtils.SimpleTag(str, `${ctx}.addExtraTags[${j}]`))
|
||||
};
|
||||
if (this.question) {
|
||||
if (hideInAnswer !== true && mp.if !== undefined && !mp.if.isUsableAsAnswer()) {
|
||||
|
@ -260,6 +260,7 @@ export default class TagRenderingConfig {
|
|||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all the render values. Will return multiple render values if 'multianswer' is enabled.
|
||||
* The result will equal [GetRenderValue] if not 'multiAnswer'
|
||||
|
|
|
@ -19,7 +19,7 @@ export default class TilesourceConfig {
|
|||
this.minzoom = config.minZoom ?? 0
|
||||
this.maxzoom = config.maxZoom ?? 999
|
||||
this.defaultState = config.defaultState ?? true;
|
||||
if(this.id === undefined){
|
||||
if (this.id === undefined) {
|
||||
throw "An id is obligated"
|
||||
}
|
||||
if (this.minzoom > this.maxzoom) {
|
||||
|
@ -34,7 +34,7 @@ export default class TilesourceConfig {
|
|||
if (this.source.indexOf("{zoom}") >= 0) {
|
||||
throw "Invalid source url: use {z} instead of {zoom} (at " + ctx + ".source)"
|
||||
}
|
||||
if(!this.defaultState && config.name === undefined){
|
||||
if (!this.defaultState && config.name === undefined) {
|
||||
throw "Disabling an overlay without a name is not possible"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,8 +4,8 @@ import {TagRenderingConfigJson} from "./Json/TagRenderingConfigJson";
|
|||
import {Utils} from "../../Utils";
|
||||
|
||||
export default class WithContextLoader {
|
||||
private readonly _json: any;
|
||||
protected readonly _context: string;
|
||||
private readonly _json: any;
|
||||
|
||||
constructor(json: any, context: string) {
|
||||
this._json = json;
|
||||
|
@ -47,7 +47,7 @@ export default class WithContextLoader {
|
|||
tagRenderings?: (string | { builtin: string, override: any } | TagRenderingConfigJson)[],
|
||||
readOnly = false,
|
||||
prepConfig: ((config: TagRenderingConfigJson) => TagRenderingConfigJson) = undefined
|
||||
) : TagRenderingConfig[]{
|
||||
): TagRenderingConfig[] {
|
||||
if (tagRenderings === undefined) {
|
||||
return [];
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import {control} from "leaflet";
|
||||
import zoom = control.zoom;
|
||||
|
||||
|
||||
export interface TileRange {
|
||||
xstart: number,
|
||||
|
@ -15,7 +15,7 @@ export class Tiles {
|
|||
public static MapRange<T>(tileRange: TileRange, f: (x: number, y: number) => T): T[] {
|
||||
const result: T[] = []
|
||||
const total = tileRange.total
|
||||
if(total > 100000){
|
||||
if (total > 100000) {
|
||||
throw "Tilerange too big"
|
||||
}
|
||||
for (let x = tileRange.xstart; x <= tileRange.xend; x++) {
|
||||
|
@ -27,24 +27,6 @@ export class Tiles {
|
|||
return result;
|
||||
}
|
||||
|
||||
|
||||
private static tile2long(x, z) {
|
||||
return (x / Math.pow(2, z) * 360 - 180);
|
||||
}
|
||||
|
||||
private static tile2lat(y, z) {
|
||||
const n = Math.PI - 2 * Math.PI * y / Math.pow(2, z);
|
||||
return (180 / Math.PI * Math.atan(0.5 * (Math.exp(n) - Math.exp(-n))));
|
||||
}
|
||||
|
||||
private static lon2tile(lon, zoom) {
|
||||
return (Math.floor((lon + 180) / 360 * Math.pow(2, zoom)));
|
||||
}
|
||||
|
||||
private static lat2tile(lat, zoom) {
|
||||
return (Math.floor((1 - Math.log(Math.tan(lat * Math.PI / 180) + 1 / Math.cos(lat * Math.PI / 180)) / Math.PI) / 2 * Math.pow(2, zoom)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the tile bounds of the
|
||||
* @param z
|
||||
|
@ -56,7 +38,6 @@ export class Tiles {
|
|||
return [[Tiles.tile2lat(y, z), Tiles.tile2long(x, z)], [Tiles.tile2lat(y + 1, z), Tiles.tile2long(x + 1, z)]]
|
||||
}
|
||||
|
||||
|
||||
static tile_bounds_lon_lat(z: number, x: number, y: number): [[number, number], [number, number]] {
|
||||
return [[Tiles.tile2long(x, z), Tiles.tile2lat(y, z)], [Tiles.tile2long(x + 1, z), Tiles.tile2lat(y + 1, z)]]
|
||||
}
|
||||
|
@ -67,13 +48,14 @@ export class Tiles {
|
|||
* @param x
|
||||
* @param y
|
||||
*/
|
||||
static centerPointOf(z: number, x: number, y: number): [number, number]{
|
||||
return [(Tiles.tile2long(x, z) + Tiles.tile2long(x+1, z)) / 2, (Tiles.tile2lat(y, z) + Tiles.tile2lat(y+1, z)) / 2]
|
||||
static centerPointOf(z: number, x: number, y: number): [number, number] {
|
||||
return [(Tiles.tile2long(x, z) + Tiles.tile2long(x + 1, z)) / 2, (Tiles.tile2lat(y, z) + Tiles.tile2lat(y + 1, z)) / 2]
|
||||
}
|
||||
|
||||
|
||||
static tile_index(z: number, x: number, y: number): number {
|
||||
return ((x * (2 << z)) + y) * 100 + z
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a tile index number, returns [z, x, y]
|
||||
* @param index
|
||||
|
@ -93,7 +75,7 @@ export class Tiles {
|
|||
static embedded_tile(lat: number, lon: number, z: number): { x: number, y: number, z: number } {
|
||||
return {x: Tiles.lon2tile(lon, z), y: Tiles.lat2tile(lat, z), z: z}
|
||||
}
|
||||
|
||||
|
||||
static TileRangeBetween(zoomlevel: number, lat0: number, lon0: number, lat1: number, lon1: number): TileRange {
|
||||
const t0 = Tiles.embedded_tile(lat0, lon0, zoomlevel)
|
||||
const t1 = Tiles.embedded_tile(lat1, lon1, zoomlevel)
|
||||
|
@ -114,5 +96,22 @@ export class Tiles {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
private static tile2long(x, z) {
|
||||
return (x / Math.pow(2, z) * 360 - 180);
|
||||
}
|
||||
|
||||
private static tile2lat(y, z) {
|
||||
const n = Math.PI - 2 * Math.PI * y / Math.pow(2, z);
|
||||
return (180 / Math.PI * Math.atan(0.5 * (Math.exp(n) - Math.exp(-n))));
|
||||
}
|
||||
|
||||
private static lon2tile(lon, zoom) {
|
||||
return (Math.floor((lon + 180) / 360 * Math.pow(2, zoom)));
|
||||
}
|
||||
|
||||
private static lat2tile(lat, zoom) {
|
||||
return (Math.floor((1 - Math.log(Math.tan(lat * Math.PI / 180) + 1 / Math.cos(lat * Math.PI / 180)) / Math.PI) / 2 * Math.pow(2, zoom)));
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -37,7 +37,9 @@ export class Unit {
|
|||
const possiblePostFixes = new Set<string>()
|
||||
|
||||
function addPostfixesOf(str) {
|
||||
if(str === undefined){return}
|
||||
if (str === undefined) {
|
||||
return
|
||||
}
|
||||
str = str.toLowerCase()
|
||||
for (let i = 0; i < str.length + 1; i++) {
|
||||
const substr = str.substring(0, i)
|
||||
|
@ -54,8 +56,8 @@ export class Unit {
|
|||
this.possiblePostFixes.sort((a, b) => b.length - a.length)
|
||||
}
|
||||
|
||||
|
||||
static fromJson(json: UnitConfigJson, ctx: string){
|
||||
|
||||
static fromJson(json: UnitConfigJson, ctx: string) {
|
||||
const appliesTo = json.appliesToKey
|
||||
for (let i = 0; i < appliesTo.length; i++) {
|
||||
let key = appliesTo[i];
|
||||
|
@ -82,7 +84,7 @@ export class Unit {
|
|||
const applicable = json.applicableUnits.map((u, i) => new Denomination(u, `${ctx}.units[${i}]`))
|
||||
return new Unit(appliesTo, applicable, json.eraseInvalidValues ?? false)
|
||||
}
|
||||
|
||||
|
||||
isApplicableToKey(key: string | undefined): boolean {
|
||||
if (key === undefined) {
|
||||
return false;
|
||||
|
@ -112,7 +114,7 @@ export class Unit {
|
|||
return undefined;
|
||||
}
|
||||
const [stripped, denom] = this.findDenomination(value)
|
||||
const human = stripped === "1" ? denom?.humanSingular : denom?.human
|
||||
const human = stripped === "1" ? denom?.humanSingular : denom?.human
|
||||
if (human === undefined) {
|
||||
return new FixedUiElement(stripped ?? value);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue