forked from MapComplete/MapComplete
Merge branch 'develop' into feature/goejson-export
This commit is contained in:
commit
18e27c32be
13 changed files with 166 additions and 127 deletions
|
@ -341,7 +341,7 @@ export class InitUiElements {
|
|||
|
||||
private static InitBaseMap() {
|
||||
|
||||
State.state.availableBackgroundLayers = new AvailableBaseLayers(State.state.locationControl).availableEditorLayers;
|
||||
State.state.availableBackgroundLayers = AvailableBaseLayers.AvailableLayersAt(State.state.locationControl);
|
||||
|
||||
State.state.backgroundLayer = State.state.backgroundLayerId
|
||||
.map((selectedId: string) => {
|
||||
|
|
|
@ -7,7 +7,6 @@ import {UIEventSource} from "../UIEventSource";
|
|||
import {GeoOperations} from "../GeoOperations";
|
||||
import {Utils} from "../../Utils";
|
||||
import Loc from "../../Models/Loc";
|
||||
import {isBoolean} from "util";
|
||||
|
||||
/**
|
||||
* Calculates which layers are available at the current location
|
||||
|
@ -31,42 +30,82 @@ export default class AvailableBaseLayers {
|
|||
category: "osmbasedmap"
|
||||
}
|
||||
|
||||
|
||||
public static layerOverview = AvailableBaseLayers.LoadRasterIndex().concat(AvailableBaseLayers.LoadProviderIndex());
|
||||
public availableEditorLayers: UIEventSource<BaseLayer[]>;
|
||||
|
||||
constructor(location: UIEventSource<Loc>) {
|
||||
const self = this;
|
||||
this.availableEditorLayers =
|
||||
location.map(
|
||||
(currentLocation) => {
|
||||
public static AvailableLayersAt(location: UIEventSource<Loc>): UIEventSource<BaseLayer[]> {
|
||||
const source = location.map(
|
||||
(currentLocation) => {
|
||||
|
||||
if (currentLocation === undefined) {
|
||||
return AvailableBaseLayers.layerOverview;
|
||||
}
|
||||
if (currentLocation === undefined) {
|
||||
return AvailableBaseLayers.layerOverview;
|
||||
}
|
||||
|
||||
const currentLayers = self.availableEditorLayers?.data;
|
||||
const newLayers = AvailableBaseLayers.AvailableLayersAt(currentLocation?.lon, currentLocation?.lat);
|
||||
const currentLayers = source?.data; // A bit unorthodox - I know
|
||||
const newLayers = AvailableBaseLayers.CalculateAvailableLayersAt(currentLocation?.lon, currentLocation?.lat);
|
||||
|
||||
if (currentLayers === undefined) {
|
||||
if (currentLayers === undefined) {
|
||||
return newLayers;
|
||||
}
|
||||
if (newLayers.length !== currentLayers.length) {
|
||||
return newLayers;
|
||||
}
|
||||
for (let i = 0; i < newLayers.length; i++) {
|
||||
if (newLayers[i].name !== currentLayers[i].name) {
|
||||
return newLayers;
|
||||
}
|
||||
if (newLayers.length !== currentLayers.length) {
|
||||
return newLayers;
|
||||
}
|
||||
for (let i = 0; i < newLayers.length; i++) {
|
||||
if (newLayers[i].name !== currentLayers[i].name) {
|
||||
return newLayers;
|
||||
}
|
||||
}
|
||||
|
||||
return currentLayers;
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
return currentLayers;
|
||||
});
|
||||
return source;
|
||||
}
|
||||
|
||||
private static AvailableLayersAt(lon: number, lat: number): BaseLayer[] {
|
||||
public static SelectBestLayerAccordingTo(location: UIEventSource<Loc>, preferedCategory: UIEventSource<string | string[]>): UIEventSource<BaseLayer> {
|
||||
return AvailableBaseLayers.AvailableLayersAt(location).map(available => {
|
||||
// First float all 'best layers' to the top
|
||||
available.sort((a, b) => {
|
||||
if (a.isBest && b.isBest) {
|
||||
return 0;
|
||||
}
|
||||
if (!a.isBest) {
|
||||
return 1
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
)
|
||||
|
||||
if (preferedCategory.data === undefined) {
|
||||
return available[0]
|
||||
}
|
||||
|
||||
let prefered: string []
|
||||
if (typeof preferedCategory.data === "string") {
|
||||
prefered = [preferedCategory.data]
|
||||
} else {
|
||||
prefered = preferedCategory.data;
|
||||
}
|
||||
|
||||
prefered.reverse();
|
||||
for (const category of prefered) {
|
||||
//Then sort all 'photo'-layers to the top. Stability of the sorting will force a 'best' photo layer on top
|
||||
available.sort((a, b) => {
|
||||
if (a.category === category && b.category === category) {
|
||||
return 0;
|
||||
}
|
||||
if (a.category !== category) {
|
||||
return 1
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
)
|
||||
}
|
||||
return available[0]
|
||||
})
|
||||
}
|
||||
|
||||
private static CalculateAvailableLayersAt(lon: number, lat: number): BaseLayer[] {
|
||||
const availableLayers = [AvailableBaseLayers.osmCarto]
|
||||
const globalLayers = [];
|
||||
for (const layerOverviewItem of AvailableBaseLayers.layerOverview) {
|
||||
|
@ -146,7 +185,7 @@ export default class AvailableBaseLayers {
|
|||
layer: leafletLayer,
|
||||
feature: layer,
|
||||
isBest: props.best ?? false,
|
||||
category: props.category
|
||||
category: props.category
|
||||
});
|
||||
}
|
||||
return layers;
|
||||
|
|
|
@ -52,7 +52,7 @@ export default class Minimap extends BaseUIElement {
|
|||
return wrapper;
|
||||
|
||||
}
|
||||
|
||||
|
||||
private InitMap() {
|
||||
if (this._constructedHtmlElement === undefined) {
|
||||
// This element isn't initialized yet
|
||||
|
|
|
@ -19,6 +19,7 @@ import {Translation} from "../i18n/Translation";
|
|||
import LocationInput from "../Input/LocationInput";
|
||||
import {InputElement} from "../Input/InputElement";
|
||||
import Loc from "../../Models/Loc";
|
||||
import AvailableBaseLayers from "../../Logic/Actors/AvailableBaseLayers";
|
||||
|
||||
/*
|
||||
* The SimpleAddUI is a single panel, which can have multiple states:
|
||||
|
@ -115,14 +116,21 @@ export default class SimpleAddUI extends Toggle {
|
|||
let location = State.state.LastClickLocation;
|
||||
let preciseInput: InputElement<Loc> = undefined
|
||||
if (preset.preciseInput !== undefined) {
|
||||
const locationSrc = new UIEventSource({
|
||||
lat: location.data.lat,
|
||||
lon: location.data.lon,
|
||||
zoom: 19
|
||||
});
|
||||
|
||||
let backgroundLayer = undefined;
|
||||
if(preset.preciseInput.preferredBackground){
|
||||
backgroundLayer= AvailableBaseLayers.SelectBestLayerAccordingTo(locationSrc, new UIEventSource<string | string[]>(preset.preciseInput.preferredBackground))
|
||||
}
|
||||
|
||||
preciseInput = new LocationInput({
|
||||
preferCategory: preset.preciseInput.preferredBackground ?? State.state.backgroundLayer,
|
||||
centerLocation:
|
||||
new UIEventSource({
|
||||
lat: location.data.lat,
|
||||
lon: location.data.lon,
|
||||
zoom: 19
|
||||
})
|
||||
mapBackground: backgroundLayer,
|
||||
centerLocation:locationSrc
|
||||
|
||||
})
|
||||
preciseInput.SetClass("h-32 rounded-xl overflow-hidden border border-gray").SetStyle("height: 12rem;")
|
||||
}
|
||||
|
|
|
@ -2,30 +2,27 @@ import {InputElement} from "./InputElement";
|
|||
import Loc from "../../Models/Loc";
|
||||
import {UIEventSource} from "../../Logic/UIEventSource";
|
||||
import Minimap from "../Base/Minimap";
|
||||
import AvailableBaseLayers from "../../Logic/Actors/AvailableBaseLayers";
|
||||
import BaseLayer from "../../Models/BaseLayer";
|
||||
import Combine from "../Base/Combine";
|
||||
import Svg from "../../Svg";
|
||||
import State from "../../State";
|
||||
|
||||
export default class LocationInput extends InputElement<Loc> {
|
||||
|
||||
IsSelected: UIEventSource<boolean> = new UIEventSource<boolean>(false);
|
||||
private _centerLocation: UIEventSource<Loc>;
|
||||
private readonly preferCategory;
|
||||
private readonly mapBackground : UIEventSource<BaseLayer>;
|
||||
|
||||
constructor(options?: {
|
||||
mapBackground?: UIEventSource<BaseLayer>,
|
||||
centerLocation?: UIEventSource<Loc>,
|
||||
preferCategory?: string | UIEventSource<string>,
|
||||
}) {
|
||||
super();
|
||||
options = options ?? {}
|
||||
options.centerLocation = options.centerLocation ?? new UIEventSource<Loc>({lat: 0, lon: 0, zoom: 1})
|
||||
this._centerLocation = options.centerLocation;
|
||||
|
||||
if(typeof options.preferCategory === "string"){
|
||||
options.preferCategory = new UIEventSource<string>(options.preferCategory);
|
||||
}
|
||||
this.preferCategory = options.preferCategory ?? new UIEventSource<string>(undefined)
|
||||
this.mapBackground = options.mapBackground ?? State.state.backgroundLayer
|
||||
this.SetClass("block h-full")
|
||||
}
|
||||
|
||||
|
@ -38,43 +35,10 @@ export default class LocationInput extends InputElement<Loc> {
|
|||
}
|
||||
|
||||
protected InnerConstructElement(): HTMLElement {
|
||||
const layer: UIEventSource<BaseLayer> = new AvailableBaseLayers(this._centerLocation).availableEditorLayers.map(allLayers => {
|
||||
// First float all 'best layers' to the top
|
||||
allLayers.sort((a, b) => {
|
||||
if (a.isBest && b.isBest) {
|
||||
return 0;
|
||||
}
|
||||
if (!a.isBest) {
|
||||
return 1
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
)
|
||||
if (this.preferCategory) {
|
||||
const self = this;
|
||||
//Then sort all 'photo'-layers to the top. Stability of the sorting will force a 'best' photo layer on top
|
||||
allLayers.sort((a, b) => {
|
||||
const preferred = self.preferCategory.data
|
||||
if (a.category === preferred && b.category === preferred) {
|
||||
return 0;
|
||||
}
|
||||
if (a.category !== preferred) {
|
||||
return 1
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
)
|
||||
}
|
||||
return allLayers[0]
|
||||
}, [this.preferCategory]
|
||||
)
|
||||
layer.addCallbackAndRunD(layer => console.log(layer))
|
||||
const map = new Minimap(
|
||||
{
|
||||
location: this._centerLocation,
|
||||
background: layer
|
||||
background: this.mapBackground
|
||||
}
|
||||
)
|
||||
map.leafletMap.addCallbackAndRunD(leaflet => {
|
||||
|
@ -84,7 +48,7 @@ export default class LocationInput extends InputElement<Loc> {
|
|||
)
|
||||
})
|
||||
|
||||
layer.map(layer => {
|
||||
this.mapBackground.map(layer => {
|
||||
|
||||
const leaflet = map.leafletMap.data
|
||||
if (leaflet === undefined || layer === undefined) {
|
||||
|
|
|
@ -25,9 +25,9 @@
|
|||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="2.8284271"
|
||||
inkscape:cx="67.47399"
|
||||
inkscape:cy="29.788021"
|
||||
inkscape:zoom="5.6568542"
|
||||
inkscape:cx="27.044982"
|
||||
inkscape:cy="77.667126"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
|
@ -68,40 +68,39 @@
|
|||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(0,-270.54165)">
|
||||
<circle
|
||||
style="fill:none;fill-opacity:1;stroke:#5555ec;stroke-width:2.64583335;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.98823529"
|
||||
id="path815"
|
||||
cx="13.16302"
|
||||
cy="283.77081"
|
||||
r="8.8715391" />
|
||||
<path
|
||||
style="fill:none;stroke:#5555ec;stroke-width:2.09723878;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.98823529"
|
||||
d="M 3.2841366,283.77082 H 1.0418969"
|
||||
id="path817"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="fill:none;stroke:#5555ec;stroke-width:2.11666679;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.98823529"
|
||||
d="M 25.405696,283.77082 H 23.286471"
|
||||
id="path817-3"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="fill:none;stroke:#5555ec;stroke-width:2.11666679;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.98823529"
|
||||
d="m 13.229167,295.9489 v -2.11763"
|
||||
id="path817-3-6"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="fill:none;stroke:#5555ec;stroke-width:2.11666668;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.98823529"
|
||||
d="m 13.229167,275.05759 v -3.44507"
|
||||
id="path817-3-6-7"
|
||||
inkscape:connector-curvature="0" />
|
||||
<g
|
||||
id="g824"
|
||||
transform="matrix(0.63953151,0,0,0.64343684,6.732623,276.71502)"
|
||||
style="fill:#ffcc33">
|
||||
id="g827">
|
||||
<circle
|
||||
r="8.8715391"
|
||||
cy="283.77081"
|
||||
cx="13.16302"
|
||||
id="path815"
|
||||
style="fill:none;fill-opacity:1;stroke:#5555ec;stroke-width:2.64583335;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.98823529" />
|
||||
<path
|
||||
id="path822"
|
||||
d="M 16.07,8 H 15 V 5 C 15,5 15,0 10,0 5,0 5,5 5,5 V 8 H 3.93 A 1.93,1.93 0 0 0 2,9.93 v 8.15 A 1.93,1.93 0 0 0 3.93,20 H 16.07 A 1.93,1.93 0 0 0 18,18.07 V 9.93 A 1.93,1.93 0 0 0 16.07,8 Z M 10,16 a 2,2 0 1 1 2,-2 2,2 0 0 1 -2,2 z M 13,8 H 7 V 5.5 C 7,4 7,2 10,2 c 3,0 3,2 3,3.5 z"
|
||||
inkscape:connector-curvature="0" />
|
||||
inkscape:connector-curvature="0"
|
||||
id="path817"
|
||||
d="M 3.2841366,283.77082 H 1.0418969"
|
||||
style="fill:none;stroke:#5555ec;stroke-width:2.09723878;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.98823529" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path817-3"
|
||||
d="M 25.405696,283.77082 H 23.286471"
|
||||
style="fill:none;stroke:#5555ec;stroke-width:2.11666679;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.98823529" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path817-3-6"
|
||||
d="m 13.229167,295.9489 v -2.11763"
|
||||
style="fill:none;stroke:#5555ec;stroke-width:2.11666679;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.98823529" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path817-3-6-7"
|
||||
d="m 13.229167,275.05759 v -3.44507"
|
||||
style="fill:none;stroke:#5555ec;stroke-width:2.11666668;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.98823529" />
|
||||
</g>
|
||||
<path
|
||||
style="fill:#5555ec;fill-opacity:0.98823529;stroke-width:0.6151033"
|
||||
inkscape:connector-curvature="0"
|
||||
d="m 16.850267,281.91543 h -0.65616 v -1.85094 c 0,0 0,-3.08489 -3.066169,-3.08489 -3.066169,0 -3.066169,3.08489 -3.066169,3.08489 v 1.85094 H 9.4056091 a 1.1835412,1.1907685 0 0 0 -1.1835412,1.19077 v 5.02838 a 1.1835412,1.1907685 0 0 0 1.1835412,1.1846 h 7.4446579 a 1.1835412,1.1907685 0 0 0 1.183541,-1.19078 v -5.0222 a 1.1835412,1.1907685 0 0 0 -1.183541,-1.19077 z m -3.722329,4.93583 a 1.2264675,1.233957 0 1 1 1.226468,-1.23395 1.2264675,1.233957 0 0 1 -1.226468,1.23395 z m 1.839702,-4.93583 h -3.679403 v -1.54245 c 0,-0.92546 0,-2.15942 1.839701,-2.15942 1.839702,0 1.839702,1.23396 1.839702,2.15942 z"
|
||||
id="path822" />
|
||||
</g>
|
||||
</svg>
|
||||
|
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 4.3 KiB |
|
@ -162,6 +162,18 @@
|
|||
"license": "CC0; trivial",
|
||||
"sources": []
|
||||
},
|
||||
{
|
||||
"authors": [],
|
||||
"path": "crosshair-empty.svg",
|
||||
"license": "CC0; trivial",
|
||||
"sources": []
|
||||
},
|
||||
{
|
||||
"authors": [],
|
||||
"path": "crosshair-locked.svg",
|
||||
"license": "CC0; trivial",
|
||||
"sources": []
|
||||
},
|
||||
{
|
||||
"authors": [
|
||||
"Dave Gandy"
|
||||
|
@ -204,7 +216,7 @@
|
|||
"license": "CC0",
|
||||
"sources": [
|
||||
"https://commons.wikimedia.org/wiki/File:Media-floppy.svg",
|
||||
" http://tango.freedesktop.org/Tango_Desktop_Project"
|
||||
"http://tango.freedesktop.org/Tango_Desktop_Project"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
|
@ -374,7 +374,7 @@
|
|||
"en": "Is there a website with more information about this artwork?",
|
||||
"nl": "Op welke website kan men meer informatie vinden over dit kunstwerk?",
|
||||
"fr": "Sur quel site web pouvons-nous trouver plus d'informations sur cette œuvre d'art?",
|
||||
"de": "Auf welcher Website gibt es mehr Informationen über dieses Kunstwerk?",
|
||||
"de": "Gibt es eine Website mit weiteren Informationen über dieses Kunstwerk?",
|
||||
"it": "Esiste un sito web con maggiori informazioni su quest’opera?",
|
||||
"ru": "Есть ли сайт с более подробной информацией об этой работе?",
|
||||
"ja": "この作品についての詳しい情報はどのウェブサイトにありますか?",
|
||||
|
|
|
@ -10,7 +10,8 @@
|
|||
"ja",
|
||||
"fr",
|
||||
"zh_Hant",
|
||||
"nb_NO"
|
||||
"nb_NO",
|
||||
"de"
|
||||
],
|
||||
"title": {
|
||||
"en": "Bicycle libraries",
|
||||
|
@ -20,7 +21,8 @@
|
|||
"ja": "自転車ライブラリ",
|
||||
"fr": "Vélothèques",
|
||||
"zh_Hant": "單車圖書館",
|
||||
"nb_NO": "Sykkelbibliotek"
|
||||
"nb_NO": "Sykkelbibliotek",
|
||||
"de": "Fahrradbibliothek"
|
||||
},
|
||||
"description": {
|
||||
"nl": "Een fietsbibliotheek is een plaats waar men een fiets kan lenen, vaak voor een klein bedrag per jaar. Een typisch voorbeeld zijn kinderfietsbibliotheken, waar men een fiets op maat van het kind kan lenen. Is het kind de fiets ontgroeid, dan kan het te kleine fietsje omgeruild worden voor een grotere.",
|
||||
|
|
|
@ -56,7 +56,7 @@
|
|||
"tagRenderings": [
|
||||
{
|
||||
"render": {
|
||||
"en": "The power output of this wind turbine is {canonical(generator:output:electricity)}."
|
||||
"en": "The power output of this wind turbine is {generator:output:electricity}."
|
||||
},
|
||||
"question": {
|
||||
"en": "What is the power output of this wind turbine? (e.g. 2.3 MW)"
|
||||
|
@ -79,7 +79,7 @@
|
|||
},
|
||||
{
|
||||
"render": {
|
||||
"en": "The total height (including rotor radius) of this wind turbine is {canonical(height)}."
|
||||
"en": "The total height (including rotor radius) of this wind turbine is {height} metres."
|
||||
},
|
||||
"question": {
|
||||
"en": "What is the total height of this wind turbine (including rotor radius), in metres?"
|
||||
|
@ -91,7 +91,7 @@
|
|||
},
|
||||
{
|
||||
"render": {
|
||||
"en": "The rotor diameter of this wind turbine is {canonical(rotor:diameter)}."
|
||||
"en": "The rotor diameter of this wind turbine is {rotor:diameter} metres."
|
||||
},
|
||||
"question": {
|
||||
"en": "What is the rotor diameter of this wind turbine, in metres?"
|
||||
|
@ -129,7 +129,7 @@
|
|||
}
|
||||
],
|
||||
"units": [
|
||||
{
|
||||
{
|
||||
"appliesToKey": [
|
||||
"generator:output:electricity"
|
||||
],
|
||||
|
@ -183,7 +183,8 @@
|
|||
},
|
||||
{
|
||||
"appliesToKey": [
|
||||
"height","rotor:diameter"
|
||||
"height",
|
||||
"rotor:diameter"
|
||||
],
|
||||
"applicableUnits": [
|
||||
{
|
||||
|
|
|
@ -87,6 +87,9 @@
|
|||
"shortDescription": "Eine Karte aller Sitzbänke",
|
||||
"description": "Diese Karte zeigt alle Sitzbänke, die in OpenStreetMap eingetragen sind: Einzeln stehende Bänke und Bänke, die zu Haltestellen oder Unterständen gehören. Mit einem OpenStreetMap-Account können Sie neue Bänke eintragen oder Detailinformationen existierender Bänke bearbeiten."
|
||||
},
|
||||
"bicyclelib": {
|
||||
"title": "Fahrradbibliothek"
|
||||
},
|
||||
"bookcases": {
|
||||
"title": "Öffentliche Bücherschränke Karte",
|
||||
"description": "Ein öffentlicher Bücherschrank ist ein kleiner Bücherschrank am Straßenrand, ein Kasten, eine alte Telefonzelle oder andere Gegenstände, in denen Bücher aufbewahrt werden. Jeder kann ein Buch hinstellen oder mitnehmen. Diese Karte zielt darauf ab, all diese Bücherschränke zu sammeln. Sie können neue Bücherschränke in der Nähe entdecken und mit einem kostenlosen OpenStreetMap-Account schnell Ihre Lieblingsbücherschränke hinzufügen."
|
||||
|
@ -327,8 +330,5 @@
|
|||
"toilets": {
|
||||
"title": "Offene Toilette Karte",
|
||||
"description": "Eine Karte der öffentlichen Toiletten"
|
||||
},
|
||||
"bicyclelib": {
|
||||
"title": "Fahrradbibliothek"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1143,6 +1143,13 @@
|
|||
"human": " gigawatts"
|
||||
}
|
||||
}
|
||||
},
|
||||
"1": {
|
||||
"applicableUnits": {
|
||||
"0": {
|
||||
"human": " meter"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -919,6 +919,13 @@
|
|||
"human": " gigawatt"
|
||||
}
|
||||
}
|
||||
},
|
||||
"1": {
|
||||
"applicableUnits": {
|
||||
"0": {
|
||||
"human": " meter"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
Loading…
Add table
Reference in a new issue