Units: add possibility to have an "inverted" time unit, e.g. for charge; add charge units to bike_parking
This commit is contained in:
parent
4ccfe3efe4
commit
bf523848fb
8 changed files with 94 additions and 32 deletions
|
@ -998,6 +998,16 @@
|
||||||
"weeks",
|
"weeks",
|
||||||
"months"
|
"months"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"charge": {
|
||||||
|
"quantity": "duration",
|
||||||
|
"inverted": true,
|
||||||
|
"denominations": [
|
||||||
|
"days",
|
||||||
|
"weeks",
|
||||||
|
"months",
|
||||||
|
"years"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -435,7 +435,7 @@ export default class SimpleMetaTaggers {
|
||||||
() => feature.properties["_country"]
|
() => feature.properties["_country"]
|
||||||
)
|
)
|
||||||
let canonical =
|
let canonical =
|
||||||
denomination?.canonicalValue(value, defaultDenom == denomination) ??
|
denomination?.canonicalValue(value, defaultDenom == denomination, unit.inverted) ??
|
||||||
undefined
|
undefined
|
||||||
if (canonical === value) {
|
if (canonical === value) {
|
||||||
break
|
break
|
||||||
|
|
|
@ -110,19 +110,21 @@ export class Denomination {
|
||||||
* @param value the value from OSM
|
* @param value the value from OSM
|
||||||
* @param actAsDefault if set and the value can be parsed as number, will be parsed and trimmed
|
* @param actAsDefault if set and the value can be parsed as number, will be parsed and trimmed
|
||||||
*
|
*
|
||||||
|
* import Validators from "../UI/InputElement/Validators"
|
||||||
|
*
|
||||||
* const unit = Denomination.fromJson({
|
* const unit = Denomination.fromJson({
|
||||||
* canonicalDenomination: "m",
|
* canonicalDenomination: "m",
|
||||||
* alternativeDenomination: ["meter"],
|
* alternativeDenomination: ["meter"],
|
||||||
* human: {
|
* human: {
|
||||||
* en: "{quantity} meter"
|
* en: "{quantity} meter"
|
||||||
* }
|
* }
|
||||||
* }, "test")
|
* }, Validators.get("float"), "test")
|
||||||
* unit.canonicalValue("42m", true) // =>"42 m"
|
* unit.canonicalValue("42m", true, false) // =>"42 m"
|
||||||
* unit.canonicalValue("42", true) // =>"42 m"
|
* unit.canonicalValue("42", true, false) // =>"42 m"
|
||||||
* unit.canonicalValue("42 m", true) // =>"42 m"
|
* unit.canonicalValue("42 m", true, false) // =>"42 m"
|
||||||
* unit.canonicalValue("42 meter", true) // =>"42 m"
|
* unit.canonicalValue("42 meter", true, false) // =>"42 m"
|
||||||
* unit.canonicalValue("42m", true) // =>"42 m"
|
* unit.canonicalValue("42m", true, false) // =>"42 m"
|
||||||
* unit.canonicalValue("42", true) // =>"42 m"
|
* unit.canonicalValue("42", true, false) // =>"42 m"
|
||||||
*
|
*
|
||||||
* // Should be trimmed if canonical is empty
|
* // Should be trimmed if canonical is empty
|
||||||
* const unit = Denomination.fromJson({
|
* const unit = Denomination.fromJson({
|
||||||
|
@ -131,22 +133,26 @@ export class Denomination {
|
||||||
* human: {
|
* human: {
|
||||||
* en: "{quantity} meter"
|
* en: "{quantity} meter"
|
||||||
* }
|
* }
|
||||||
* }, "test")
|
* }, Validators.get("float"), "test")
|
||||||
* unit.canonicalValue("42m", true) // =>"42"
|
* unit.canonicalValue("42m", true, false) // =>"42"
|
||||||
* unit.canonicalValue("42", true) // =>"42"
|
* unit.canonicalValue("42", true, false) // =>"42"
|
||||||
* unit.canonicalValue("42 m", true) // =>"42"
|
* unit.canonicalValue("42 m", true, false) // =>"42"
|
||||||
* unit.canonicalValue("42 meter", true) // =>"42"
|
* unit.canonicalValue("42 meter", true, false) // =>"42"
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public canonicalValue(value: string, actAsDefault: boolean): string {
|
public canonicalValue(value: string, actAsDefault: boolean, inverted: boolean): string {
|
||||||
if (value === undefined) {
|
if (value === undefined) {
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
const stripped = this.StrippedValue(value, actAsDefault)
|
const stripped = this.StrippedValue(value, actAsDefault, inverted)
|
||||||
if (stripped === null) {
|
if (stripped === null) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
if(inverted){
|
||||||
|
return (stripped + "/" + this.canonical).trim()
|
||||||
|
|
||||||
|
}
|
||||||
if (stripped === "1" && this._canonicalSingular !== undefined) {
|
if (stripped === "1" && this._canonicalSingular !== undefined) {
|
||||||
return ("1 " + this._canonicalSingular).trim()
|
return ("1 " + this._canonicalSingular).trim()
|
||||||
}
|
}
|
||||||
|
@ -160,7 +166,7 @@ export class Denomination {
|
||||||
*
|
*
|
||||||
* Returns null if it doesn't match this unit
|
* Returns null if it doesn't match this unit
|
||||||
*/
|
*/
|
||||||
public StrippedValue(value: string, actAsDefault: boolean): string {
|
public StrippedValue(value: string, actAsDefault: boolean, inverted: boolean): string {
|
||||||
if (value === undefined) {
|
if (value === undefined) {
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
|
@ -178,10 +184,16 @@ export class Denomination {
|
||||||
|
|
||||||
function substr(key) {
|
function substr(key) {
|
||||||
if (self.prefix) {
|
if (self.prefix) {
|
||||||
return value.substr(key.length).trim()
|
return value.substring(key.length).trim()
|
||||||
} else {
|
|
||||||
return value.substring(0, value.length - key.length).trim()
|
|
||||||
}
|
}
|
||||||
|
let trimmed = value.substring(0, value.length - key.length).trim()
|
||||||
|
if(!inverted){
|
||||||
|
return trimmed
|
||||||
|
}
|
||||||
|
if(trimmed.endsWith("/")){
|
||||||
|
trimmed = trimmed.substring(0, trimmed.length - 1).trim()
|
||||||
|
}
|
||||||
|
return trimmed
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.canonical !== "" && startsWith(this.canonical.toLowerCase())) {
|
if (this.canonical !== "" && startsWith(this.canonical.toLowerCase())) {
|
||||||
|
|
|
@ -519,6 +519,7 @@ export interface LayerConfigJson {
|
||||||
/**
|
/**
|
||||||
* Either a list with [{"key": "unitname", "key2": {"quantity": "unitname", "denominations": ["denom", "denom"]}}]
|
* Either a list with [{"key": "unitname", "key2": {"quantity": "unitname", "denominations": ["denom", "denom"]}}]
|
||||||
*
|
*
|
||||||
|
* Use `"inverted": true` if the amount should be _divided_ by the denomination, e.g. for charge over time (`€5/day`)
|
||||||
*
|
*
|
||||||
* @see UnitConfigJson
|
* @see UnitConfigJson
|
||||||
*
|
*
|
||||||
|
@ -526,7 +527,7 @@ export interface LayerConfigJson {
|
||||||
*/
|
*/
|
||||||
units?: (
|
units?: (
|
||||||
| UnitConfigJson
|
| UnitConfigJson
|
||||||
| Record<string, string | { quantity: string; denominations: string[]; canonical?: string }>
|
| Record<string, string | { quantity: string; denominations: string[]; canonical?: string, inverted?: boolean }>
|
||||||
)[]
|
)[]
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -15,16 +15,19 @@ export class Unit {
|
||||||
public readonly eraseInvalid: boolean
|
public readonly eraseInvalid: boolean
|
||||||
public readonly quantity: string
|
public readonly quantity: string
|
||||||
private readonly _validator: Validator
|
private readonly _validator: Validator
|
||||||
|
public readonly inverted: boolean
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
quantity: string,
|
quantity: string,
|
||||||
appliesToKeys: string[],
|
appliesToKeys: string[],
|
||||||
applicableDenominations: Denomination[],
|
applicableDenominations: Denomination[],
|
||||||
eraseInvalid: boolean,
|
eraseInvalid: boolean,
|
||||||
validator: Validator
|
validator: Validator,
|
||||||
|
inverted: boolean = false
|
||||||
) {
|
) {
|
||||||
this.quantity = quantity
|
this.quantity = quantity
|
||||||
this._validator = validator
|
this._validator = validator
|
||||||
|
this.inverted = inverted
|
||||||
this.appliesToKeys = new Set(appliesToKeys)
|
this.appliesToKeys = new Set(appliesToKeys)
|
||||||
this.denominations = applicableDenominations
|
this.denominations = applicableDenominations
|
||||||
this.eraseInvalid = eraseInvalid
|
this.eraseInvalid = eraseInvalid
|
||||||
|
@ -73,7 +76,7 @@ export class Unit {
|
||||||
static fromJson(
|
static fromJson(
|
||||||
json:
|
json:
|
||||||
| UnitConfigJson
|
| UnitConfigJson
|
||||||
| Record<string, string | { quantity: string; denominations: string[] }>,
|
| Record<string, string | { quantity: string; denominations: string[], inverted?: boolean }>,
|
||||||
tagRenderings: TagRenderingConfig[],
|
tagRenderings: TagRenderingConfig[],
|
||||||
ctx: string
|
ctx: string
|
||||||
): Unit[] {
|
): Unit[] {
|
||||||
|
@ -210,7 +213,7 @@ export class Unit {
|
||||||
private static loadFromLibrary(
|
private static loadFromLibrary(
|
||||||
spec: Record<
|
spec: Record<
|
||||||
string,
|
string,
|
||||||
string | { quantity: string; denominations: string[]; canonical?: string }
|
string | { quantity: string; denominations: string[]; canonical?: string, inverted?: boolean }
|
||||||
>,
|
>,
|
||||||
types: Record<string, ValidatorType>,
|
types: Record<string, ValidatorType>,
|
||||||
ctx: string
|
ctx: string
|
||||||
|
@ -222,7 +225,7 @@ export class Unit {
|
||||||
if (typeof toLoad === "string") {
|
if (typeof toLoad === "string") {
|
||||||
const loaded = this.getFromLibrary(toLoad, ctx)
|
const loaded = this.getFromLibrary(toLoad, ctx)
|
||||||
units.push(
|
units.push(
|
||||||
new Unit(loaded.quantity, [key], loaded.denominations, loaded.eraseInvalid, validator)
|
new Unit(loaded.quantity, [key], loaded.denominations, loaded.eraseInvalid, validator, toLoad["inverted"])
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -252,7 +255,7 @@ export class Unit {
|
||||||
const canonical = fetchDenom(toLoad.canonical).withValidator(validator)
|
const canonical = fetchDenom(toLoad.canonical).withValidator(validator)
|
||||||
denoms.unshift(canonical.withBlankCanonical())
|
denoms.unshift(canonical.withBlankCanonical())
|
||||||
}
|
}
|
||||||
units.push(new Unit(loaded.quantity, [key], denoms, loaded.eraseInvalid, validator))
|
units.push(new Unit(loaded.quantity, [key], denoms, loaded.eraseInvalid, validator, toLoad["inverted"]))
|
||||||
}
|
}
|
||||||
return units
|
return units
|
||||||
}
|
}
|
||||||
|
@ -274,7 +277,7 @@ export class Unit {
|
||||||
}
|
}
|
||||||
const defaultDenom = this.getDefaultDenomination(country)
|
const defaultDenom = this.getDefaultDenomination(country)
|
||||||
for (const denomination of this.denominationsSorted) {
|
for (const denomination of this.denominationsSorted) {
|
||||||
const bare = denomination.StrippedValue(valueWithDenom, defaultDenom === denomination)
|
const bare = denomination.StrippedValue(valueWithDenom, defaultDenom === denomination, this.inverted)
|
||||||
if (bare !== null) {
|
if (bare !== null) {
|
||||||
return [bare, denomination]
|
return [bare, denomination]
|
||||||
}
|
}
|
||||||
|
@ -287,10 +290,13 @@ export class Unit {
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
const [stripped, denom] = this.findDenomination(value, country)
|
const [stripped, denom] = this.findDenomination(value, country)
|
||||||
|
const human = denom?.human
|
||||||
|
if(this.inverted ){
|
||||||
|
return human.Subs({quantity: stripped+"/"})
|
||||||
|
}
|
||||||
if (stripped === "1") {
|
if (stripped === "1") {
|
||||||
return denom?.humanSingular ?? stripped
|
return denom?.humanSingular ?? stripped
|
||||||
}
|
}
|
||||||
const human = denom?.human
|
|
||||||
if (human === undefined) {
|
if (human === undefined) {
|
||||||
return stripped ?? value
|
return stripped ?? value
|
||||||
}
|
}
|
||||||
|
@ -300,6 +306,10 @@ export class Unit {
|
||||||
|
|
||||||
public toOsm(value: string, denomination: string) {
|
public toOsm(value: string, denomination: string) {
|
||||||
const denom = this.denominations.find((d) => d.canonical === denomination)
|
const denom = this.denominations.find((d) => d.canonical === denomination)
|
||||||
|
if(this.inverted){
|
||||||
|
return value+"/"+denom._canonicalSingular
|
||||||
|
}
|
||||||
|
|
||||||
const space = denom.addSpace ? " " : ""
|
const space = denom.addSpace ? " " : ""
|
||||||
if (denom.prefix) {
|
if (denom.prefix) {
|
||||||
return denom.canonical + space + value
|
return denom.canonical + space + value
|
||||||
|
@ -307,7 +317,7 @@ export class Unit {
|
||||||
return value + space + denom.canonical
|
return value + space + denom.canonical
|
||||||
}
|
}
|
||||||
|
|
||||||
public getDefaultDenomination(country: () => string) {
|
public getDefaultDenomination(country: () => string): Denomination {
|
||||||
for (const denomination of this.denominations) {
|
for (const denomination of this.denominations) {
|
||||||
if (denomination.useIfNoUnitGiven === true) {
|
if (denomination.useIfNoUnitGiven === true) {
|
||||||
return denomination
|
return denomination
|
||||||
|
|
|
@ -178,7 +178,9 @@
|
||||||
checkedMappings,
|
checkedMappings,
|
||||||
tags.data
|
tags.data
|
||||||
)
|
)
|
||||||
console.log("Constructing change spec from", {freeform: $freeformInput, selectedMapping, checkedMappings, currentTags: tags.data}, " --> ", selectedTags)
|
if(state.featureSwitches.featureSwitchIsDebugging.data){
|
||||||
|
console.log("Constructing change spec from", {freeform: $freeformInput, selectedMapping, checkedMappings, currentTags: tags.data}, " --> ", selectedTags)
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Could not calculate changeSpecification:", e)
|
console.error("Could not calculate changeSpecification:", e)
|
||||||
selectedTags = undefined
|
selectedTags = undefined
|
||||||
|
|
|
@ -64,10 +64,14 @@
|
||||||
)
|
)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
{#if unit.inverted}
|
||||||
|
<div class="bold px-2">/</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<select bind:value={$selectedUnit}>
|
<select bind:value={$selectedUnit}>
|
||||||
{#each unit.denominations as denom (denom.canonical)}
|
{#each unit.denominations as denom (denom.canonical)}
|
||||||
<option value={denom.canonical}>
|
<option value={denom.canonical}>
|
||||||
{#if $isSingle}
|
{#if $isSingle || unit.inverted}
|
||||||
<Tr t={denom.humanSingular} />
|
<Tr t={denom.humanSingular} />
|
||||||
{:else}
|
{:else}
|
||||||
<Tr t={denom.human.Subs({ quantity: "" })} />
|
<Tr t={denom.human.Subs({ quantity: "" })} />
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { Unit } from "../../src/Models/Unit"
|
import { Unit } from "../../src/Models/Unit"
|
||||||
import { Denomination } from "../../src/Models/Denomination"
|
import { Denomination } from "../../src/Models/Denomination"
|
||||||
import { describe, expect, it } from "vitest"
|
import { describe, expect, it } from "vitest"
|
||||||
|
import Validators from "../../src/UI/InputElement/Validators"
|
||||||
|
|
||||||
describe("Unit", () => {
|
describe("Unit", () => {
|
||||||
it("should convert a value back and forth", () => {
|
it("should convert a value back and forth", () => {
|
||||||
|
@ -13,14 +14,36 @@ describe("Unit", () => {
|
||||||
nl: "{quantity} megawatt",
|
nl: "{quantity} megawatt",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
Validators.get("float"),
|
||||||
"test"
|
"test"
|
||||||
)
|
)
|
||||||
|
|
||||||
const canonical = denomintion.canonicalValue("5", true)
|
const canonical = denomintion.canonicalValue("5", true, false)
|
||||||
expect(canonical).toBe("5 MW")
|
expect(canonical).toBe("5 MW")
|
||||||
const units = new Unit("quantity", ["key"], [denomintion], false)
|
const units = new Unit("quantity", ["key"], [denomintion], false, Validators.get("float"))
|
||||||
const [detected, detectedDenom] = units.findDenomination("5 MW", () => "be")
|
const [detected, detectedDenom] = units.findDenomination("5 MW", () => "be")
|
||||||
expect(detected).toBe("5")
|
expect(detected).toBe("5")
|
||||||
expect(detectedDenom).toBe(denomintion)
|
expect(detectedDenom).toBe(denomintion)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("should convert an inverted value back and forth", () => {
|
||||||
|
const denomintion = Denomination.fromJson(
|
||||||
|
{
|
||||||
|
canonicalDenomination: "year",
|
||||||
|
human: {
|
||||||
|
en: "{quantity} year",
|
||||||
|
nl: "{quantity} year",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Validators.get("float"),
|
||||||
|
"test"
|
||||||
|
)
|
||||||
|
|
||||||
|
const canonical = denomintion.canonicalValue("5", true, true)
|
||||||
|
expect(canonical).toBe("5/year")
|
||||||
|
const unit = new Unit("quantity", ["key"], [denomintion], false, Validators.get("float"), true)
|
||||||
|
const [detected, detectedDenom] = unit.findDenomination("5/year", () => "be")
|
||||||
|
expect(detected).toBe("5")
|
||||||
|
expect(detectedDenom).toBe(denomintion)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in a new issue