forked from MapComplete/MapComplete
		
	Feature(distancePicker): revive geographical distance picker
This commit is contained in:
		
							parent
							
								
									e997653284
								
							
						
					
					
						commit
						5095bffc50
					
				
					 16 changed files with 245 additions and 107 deletions
				
			
		|  | @ -494,6 +494,7 @@ | ||||||
|       "id": "Cycle barrier type" |       "id": "Cycle barrier type" | ||||||
|     }, |     }, | ||||||
|     { |     { | ||||||
|  |       "id": "MaxWidth", | ||||||
|       "render": { |       "render": { | ||||||
|         "en": "Maximum width: {maxwidth:physical} m", |         "en": "Maximum width: {maxwidth:physical} m", | ||||||
|         "nl": "Maximumbreedte: {maxwidth:physical} m", |         "nl": "Maximumbreedte: {maxwidth:physical} m", | ||||||
|  | @ -532,12 +533,11 @@ | ||||||
|       "freeform": { |       "freeform": { | ||||||
|         "key": "maxwidth:physical", |         "key": "maxwidth:physical", | ||||||
|         "type": "distance", |         "type": "distance", | ||||||
|         "helperArgs": [ |         "helperArgs": { | ||||||
|           "20", |           "zoom": 20, | ||||||
|           "map" |           "background": "map" | ||||||
|         ] |         } | ||||||
|       }, |       } | ||||||
|       "id": "MaxWidth" |  | ||||||
|     }, |     }, | ||||||
|     { |     { | ||||||
|       "render": { |       "render": { | ||||||
|  | @ -575,10 +575,10 @@ | ||||||
|       "freeform": { |       "freeform": { | ||||||
|         "key": "width:separation", |         "key": "width:separation", | ||||||
|         "type": "distance", |         "type": "distance", | ||||||
|         "helperArgs": [ |         "helperArgs": { | ||||||
|           "21", |           "zoom": 21, | ||||||
|           "map" |           "background": "map" | ||||||
|         ] |         } | ||||||
|       }, |       }, | ||||||
|       "id": "Space between barrier (cyclebarrier)" |       "id": "Space between barrier (cyclebarrier)" | ||||||
|     }, |     }, | ||||||
|  | @ -618,10 +618,10 @@ | ||||||
|       "freeform": { |       "freeform": { | ||||||
|         "key": "width:opening", |         "key": "width:opening", | ||||||
|         "type": "distance", |         "type": "distance", | ||||||
|         "helperArgs": [ |         "helperArgs": { | ||||||
|           "21", |           "zoom": 21, | ||||||
|           "map" |           "background": "map" | ||||||
|         ] |         } | ||||||
|       }, |       }, | ||||||
|       "id": "Width of opening (cyclebarrier)" |       "id": "Width of opening (cyclebarrier)" | ||||||
|     }, |     }, | ||||||
|  | @ -661,10 +661,10 @@ | ||||||
|       "freeform": { |       "freeform": { | ||||||
|         "key": "overlap", |         "key": "overlap", | ||||||
|         "type": "distance", |         "type": "distance", | ||||||
|         "helperArgs": [ |         "helperArgs": { | ||||||
|           "21", |           "zoom": 21, | ||||||
|           "map" |           "background": "map" | ||||||
|         ] |         } | ||||||
|       }, |       }, | ||||||
|       "id": "Overlap (cyclebarrier)" |       "id": "Overlap (cyclebarrier)" | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -1283,10 +1283,10 @@ | ||||||
|       "freeform": { |       "freeform": { | ||||||
|         "key": "width", |         "key": "width", | ||||||
|         "type": "distance", |         "type": "distance", | ||||||
|         "helperArgs": [ |         "helperArgs": { | ||||||
|           "20", |           "zoom": 20, | ||||||
|           "map" |           "background": "map" | ||||||
|         ] |         } | ||||||
|       }, |       }, | ||||||
|       "question": { |       "question": { | ||||||
|         "en": "What is the carriage width of this road (in meters)?", |         "en": "What is the carriage width of this road (in meters)?", | ||||||
|  | @ -1808,10 +1808,10 @@ | ||||||
|       "freeform": { |       "freeform": { | ||||||
|         "key": "cycleway:buffer", |         "key": "cycleway:buffer", | ||||||
|         "type": "distance", |         "type": "distance", | ||||||
|         "helperArgs": [ |         "helperArgs": { | ||||||
|           "20", |           "background": "map", | ||||||
|           "map" |           "zoom": 20 | ||||||
|         ] |         } | ||||||
|       }, |       }, | ||||||
|       "id": "cycleways_and_roads-cycleway:buffer" |       "id": "cycleways_and_roads-cycleway:buffer" | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|  | @ -69,10 +69,10 @@ | ||||||
|           "freeform": { |           "freeform": { | ||||||
|             "key": "width:carriageway", |             "key": "width:carriageway", | ||||||
|             "type": "distance", |             "type": "distance", | ||||||
|             "helperArgs": [ |             "helperArgs": { | ||||||
|               21, |               "zoom": 21, | ||||||
|               "map" |               "background": "map" | ||||||
|             ] |             } | ||||||
|           } |           } | ||||||
|         }, |         }, | ||||||
|         { |         { | ||||||
|  |  | ||||||
|  | @ -628,6 +628,11 @@ | ||||||
|         "recentThemes": "Recently visited themes", |         "recentThemes": "Recently visited themes", | ||||||
|         "title": "MapComplete" |         "title": "MapComplete" | ||||||
|     }, |     }, | ||||||
|  |     "input_helpers": { | ||||||
|  |         "distance": { | ||||||
|  |             "setFirst": "Measure from current location" | ||||||
|  |         } | ||||||
|  |     }, | ||||||
|     "inspector": { |     "inspector": { | ||||||
|         "aggregateView": "Aggregate", |         "aggregateView": "Aggregate", | ||||||
|         "answeredCountTimes": "Answered {count} times", |         "answeredCountTimes": "Answered {count} times", | ||||||
|  |  | ||||||
|  | @ -32,6 +32,27 @@ export class AvailableRasterLayers { | ||||||
| 
 | 
 | ||||||
|     public static readonly globalLayers: ReadonlyArray<RasterLayerPolygon> = |     public static readonly globalLayers: ReadonlyArray<RasterLayerPolygon> = | ||||||
|         AvailableRasterLayers.initGlobalLayers() |         AvailableRasterLayers.initGlobalLayers() | ||||||
|  |     public static bing = <RasterLayerPolygon>bingJson | ||||||
|  |     public static readonly osmCartoProperties: RasterLayerProperties = { | ||||||
|  |         id: "osm", | ||||||
|  |         name: "OpenStreetMap", | ||||||
|  |         url: "https://tile.openstreetmap.org/{z}/{x}/{y}.png", | ||||||
|  |         attribution: { | ||||||
|  |             text: "OpenStreetMap", | ||||||
|  |             url: "https://openStreetMap.org/copyright" | ||||||
|  |         }, | ||||||
|  |         best: true, | ||||||
|  |         max_zoom: 19, | ||||||
|  |         min_zoom: 0, | ||||||
|  |         category: "osmbasedmap" | ||||||
|  |     } | ||||||
|  |     public static readonly osmCarto: RasterLayerPolygon = { | ||||||
|  |         type: "Feature", | ||||||
|  |         properties: AvailableRasterLayers.osmCartoProperties, | ||||||
|  |         geometry: BBox.global.asGeometry() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public static allAvailableGlobalLayers = new Set([...AvailableRasterLayers.globalLayers, AvailableRasterLayers.osmCarto, AvailableRasterLayers.bing]) | ||||||
| 
 | 
 | ||||||
|     private static initGlobalLayers(): RasterLayerPolygon[] { |     private static initGlobalLayers(): RasterLayerPolygon[] { | ||||||
|         const gl: RasterLayerProperties[] = (globallayers["default"] ?? globallayers).layers.filter( |         const gl: RasterLayerProperties[] = (globallayers["default"] ?? globallayers).layers.filter( | ||||||
|  | @ -54,26 +75,7 @@ export class AvailableRasterLayers { | ||||||
|         ) |         ) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public static bing = <RasterLayerPolygon>bingJson |  | ||||||
|     public static readonly osmCartoProperties: RasterLayerProperties = { |  | ||||||
|         id: "osm", |  | ||||||
|         name: "OpenStreetMap", |  | ||||||
|         url: "https://tile.openstreetmap.org/{z}/{x}/{y}.png", |  | ||||||
|         attribution: { |  | ||||||
|             text: "OpenStreetMap", |  | ||||||
|             url: "https://openStreetMap.org/copyright", |  | ||||||
|         }, |  | ||||||
|         best: true, |  | ||||||
|         max_zoom: 19, |  | ||||||
|         min_zoom: 0, |  | ||||||
|         category: "osmbasedmap", |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     public static readonly osmCarto: RasterLayerPolygon = { |  | ||||||
|         type: "Feature", |  | ||||||
|         properties: AvailableRasterLayers.osmCartoProperties, |  | ||||||
|         geometry: BBox.global.asGeometry(), |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * The default background layer that any theme uses which does not explicitly define a background |      * The default background layer that any theme uses which does not explicitly define a background | ||||||
|  |  | ||||||
|  | @ -1,10 +1,7 @@ | ||||||
| import { DesugaringStep } from "./Conversion" | import { DesugaringStep } from "./Conversion" | ||||||
| import { TagRenderingConfigJson } from "../Json/TagRenderingConfigJson" | import { TagRenderingConfigJson } from "../Json/TagRenderingConfigJson" | ||||||
| import { LayerConfigJson } from "../Json/LayerConfigJson" | import { LayerConfigJson } from "../Json/LayerConfigJson" | ||||||
| import { | import { MappingConfigJson, QuestionableTagRenderingConfigJson } from "../Json/QuestionableTagRenderingConfigJson" | ||||||
|     MappingConfigJson, |  | ||||||
|     QuestionableTagRenderingConfigJson, |  | ||||||
| } from "../Json/QuestionableTagRenderingConfigJson" |  | ||||||
| import { ConversionContext } from "./ConversionContext" | import { ConversionContext } from "./ConversionContext" | ||||||
| import { Translation } from "../../../UI/i18n/Translation" | import { Translation } from "../../../UI/i18n/Translation" | ||||||
| import { TagUtils } from "../../../Logic/Tags/TagUtils" | import { TagUtils } from "../../../Logic/Tags/TagUtils" | ||||||
|  | @ -216,6 +213,14 @@ export class MiscTagRenderingChecks extends DesugaringStep<TagRenderingConfigJso | ||||||
|                         "No need to explicitly set type to 'NSI', autodetected based on freeform type" |                         "No need to explicitly set type to 'NSI', autodetected based on freeform type" | ||||||
|                     ) |                     ) | ||||||
|             } |             } | ||||||
|  | 
 | ||||||
|  |             if (json.freeform["type"] && json.freeform["helperArgs"]) { | ||||||
|  |                 const validator = Validators.get(json.freeform["type"]) | ||||||
|  |                 const feedback = validator?.validateArguments(json.freeform["helperArgs"]) | ||||||
|  |                 if (feedback) { | ||||||
|  |                     context.enters("freeform", "helperArgs").err(feedback) | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|         if (json.render && json["question"] && json.freeform === undefined) { |         if (json.render && json["question"] && json.freeform === undefined) { | ||||||
|             context.err( |             context.err( | ||||||
|  |  | ||||||
							
								
								
									
										100
									
								
								src/UI/InputElement/Helpers/DistanceInput.svelte
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										100
									
								
								src/UI/InputElement/Helpers/DistanceInput.svelte
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,100 @@ | ||||||
|  | <script lang="ts"> | ||||||
|  | 
 | ||||||
|  |   /** | ||||||
|  |    * Used to quickly calculate a distance by dragging a map (and selecting start- and endpoints) | ||||||
|  |    */ | ||||||
|  | 
 | ||||||
|  |   import LocationInput from "./LocationInput.svelte" | ||||||
|  |   import { UIEventSource, Store } from "../../../Logic/UIEventSource" | ||||||
|  |   import type { MapProperties } from "../../../Models/MapProperties" | ||||||
|  |   import ThemeViewState from "../../../Models/ThemeViewState" | ||||||
|  |   import type { Feature } from "geojson" | ||||||
|  |   import type { RasterLayerPolygon } from "../../../Models/RasterLayers" | ||||||
|  |   import { RasterLayerUtils } from "../../../Models/RasterLayers" | ||||||
|  |   import { eliCategory } from "../../../Models/RasterLayerProperties" | ||||||
|  |   import { GeoOperations } from "../../../Logic/GeoOperations" | ||||||
|  |   import OpenBackgroundSelectorButton from "../../BigComponents/OpenBackgroundSelectorButton.svelte" | ||||||
|  |   import { Map as MlMap } from "maplibre-gl" | ||||||
|  |   import StaticFeatureSource from "../../../Logic/FeatureSource/Sources/StaticFeatureSource" | ||||||
|  |   import ShowDataLayer from "../../Map/ShowDataLayer" | ||||||
|  |   import * as conflation from "../../../assets/generated/layers/conflation.json" | ||||||
|  |   import LayerConfig from "../../../Models/ThemeConfig/LayerConfig" | ||||||
|  |   import Translations from "../../i18n/Translations" | ||||||
|  |   import Tr from "../../Base/Tr.svelte" | ||||||
|  | 
 | ||||||
|  |   export let value: UIEventSource<number> | ||||||
|  |   export let feature: Feature | ||||||
|  |   export let args: { background?: string, zoom?: number } | ||||||
|  |   export let state: ThemeViewState = undefined | ||||||
|  |   export let map: UIEventSource<MlMap> = new UIEventSource<MlMap>(undefined) | ||||||
|  | 
 | ||||||
|  |   let center = GeoOperations.centerpointCoordinates(feature) | ||||||
|  |   export let initialCoordinate: { lon: number, lat: number } = { lon: center[0], lat: center[1] } | ||||||
|  |   let mapLocation: UIEventSource<{ lon: number, lat: number }> = new UIEventSource(initialCoordinate) | ||||||
|  |   let bg = args?.background | ||||||
|  |   let rasterLayer = state?.mapProperties.rasterLayer | ||||||
|  |   if (bg !== undefined) { | ||||||
|  |     if (eliCategory.indexOf(bg) >= 0) { | ||||||
|  |       const availableLayers = state.availableLayers.store.data | ||||||
|  |       const startLayer: RasterLayerPolygon = RasterLayerUtils.SelectBestLayerAccordingTo(availableLayers, bg) | ||||||
|  |       rasterLayer = new UIEventSource(startLayer) | ||||||
|  |       state?.mapProperties.rasterLayer.addCallbackD(layer => rasterLayer.set(layer)) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |   } | ||||||
|  |   let mapProperties: Partial<MapProperties> = { | ||||||
|  |     rasterLayer: rasterLayer, | ||||||
|  |     location: mapLocation, | ||||||
|  |     zoom: new UIEventSource(args?.zoom ?? 18) | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   let start: UIEventSource<{ lon: number, lat: number }> = new UIEventSource(undefined) | ||||||
|  | 
 | ||||||
|  |   function selectStart() { | ||||||
|  |     start.set(mapLocation.data) | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   let lengthFeature: Store<Feature[]> = start.map(start => { | ||||||
|  |     if (!start) { | ||||||
|  |       return [] | ||||||
|  |     } | ||||||
|  |     // A bit of a double task: calculate the actual value _and_ the map rendering | ||||||
|  |     const end = mapLocation.data | ||||||
|  |     const distance = GeoOperations.distanceBetween([start.lon, start.lat], [end.lon, end.lat]) | ||||||
|  |     value.set(distance.toFixed(2)) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     return <Feature[]>[ | ||||||
|  | 
 | ||||||
|  |       { | ||||||
|  |         type: "Feature", | ||||||
|  |         properties: { | ||||||
|  |           id: "distance_line_" + distance, | ||||||
|  |           distance: "" + distance | ||||||
|  |         }, | ||||||
|  |         geometry: { | ||||||
|  |           type: "LineString", | ||||||
|  |           coordinates: [[start.lon, start.lat], [end.lon, end.lat]] | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     ] | ||||||
|  | 
 | ||||||
|  |   }, [mapLocation]) | ||||||
|  | 
 | ||||||
|  |   new ShowDataLayer(map, { | ||||||
|  |     layer: new LayerConfig(conflation), | ||||||
|  |     features: new StaticFeatureSource(lengthFeature) | ||||||
|  |   }) | ||||||
|  | 
 | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <div class="relative w-full h-64"> | ||||||
|  |   <LocationInput value={mapLocation} {mapProperties} {map} /> | ||||||
|  |   <div class="absolute bottom-0 left-0 p-4"> | ||||||
|  |     <OpenBackgroundSelectorButton {state} {map} /> | ||||||
|  |   </div> | ||||||
|  | </div> | ||||||
|  | 
 | ||||||
|  | <button class="primary" on:click={() => selectStart()}> | ||||||
|  |   <Tr t={Translations.t.input_helpers.distance.setFirst} /> | ||||||
|  | </button> | ||||||
|  | @ -19,12 +19,13 @@ | ||||||
|   import SlopeInput from "./Helpers/SlopeInput.svelte" |   import SlopeInput from "./Helpers/SlopeInput.svelte" | ||||||
|   import type { SpecialVisualizationState } from "../SpecialVisualization" |   import type { SpecialVisualizationState } from "../SpecialVisualization" | ||||||
|   import WikidataInputHelper from "./WikidataInputHelper.svelte" |   import WikidataInputHelper from "./WikidataInputHelper.svelte" | ||||||
|  |   import DistanceInput from "./Helpers/DistanceInput.svelte" | ||||||
| 
 | 
 | ||||||
|   export let type: ValidatorType |   export let type: ValidatorType | ||||||
|   export let value: UIEventSource<string | object> |   export let value: UIEventSource<string | object> | ||||||
| 
 | 
 | ||||||
|   export let feature: Feature = undefined |   export let feature: Feature = undefined | ||||||
|   export let args: (string | number | boolean)[] = undefined |   export let args: (string | number | boolean)[] | any = undefined | ||||||
|   export let state: SpecialVisualizationState = undefined |   export let state: SpecialVisualizationState = undefined | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
|  | @ -52,6 +53,8 @@ | ||||||
|   <SlopeInput {value} {feature} {state} /> |   <SlopeInput {value} {feature} {state} /> | ||||||
| {:else if type === "wikidata"} | {:else if type === "wikidata"} | ||||||
|   <WikidataInputHelper {value} {feature} {state} {args} /> |   <WikidataInputHelper {value} {feature} {state} {args} /> | ||||||
|  | {:else if type === "distance"} | ||||||
|  |   <DistanceInput {value} {state} {feature} {args} /> | ||||||
| {:else} | {:else} | ||||||
|   <slot name="fallback" /> |   <slot name="fallback" /> | ||||||
| {/if} | {/if} | ||||||
|  |  | ||||||
|  | @ -27,7 +27,6 @@ export interface InputHelperProperties { | ||||||
| export default class InputHelpers { | export default class InputHelpers { | ||||||
|     public static hideInputField: string[] = ["translation", "simple_tag", "tag"] |     public static hideInputField: string[] = ["translation", "simple_tag", "tag"] | ||||||
| 
 | 
 | ||||||
|     // noinspection JSUnusedLocalSymbols
 |  | ||||||
|     /** |     /** | ||||||
|      * Constructs a mapProperties-object for the given properties. |      * Constructs a mapProperties-object for the given properties. | ||||||
|      * Assumes that the first helper-args contains the desired zoom-level |      * Assumes that the first helper-args contains the desired zoom-level | ||||||
|  |  | ||||||
|  | @ -14,7 +14,8 @@ export abstract class Validator { | ||||||
|      * */ |      * */ | ||||||
|     public readonly explanation: string |     public readonly explanation: string | ||||||
|     /** |     /** | ||||||
|      * What HTML-inputmode to use |      * What HTML-inputmode to use? | ||||||
|  |      * Note: some inputHelpers will completely hide the default text field. This is kept in InputHelpers.hideInputField | ||||||
|      */ |      */ | ||||||
|     public readonly inputmode?: |     public readonly inputmode?: | ||||||
|         | "none" |         | "none" | ||||||
|  | @ -81,4 +82,14 @@ export abstract class Validator { | ||||||
|     public reformat(s: string, _?: () => string): string { |     public reformat(s: string, _?: () => string): string { | ||||||
|         return s |         return s | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Checks that the helper arguments are correct. | ||||||
|  |      * This is called while preparing the themes. | ||||||
|  |      * Returns 'undefined' if everything is fine, or feedback if an error is detected | ||||||
|  |      * @param args the args for the input helper | ||||||
|  |      */ | ||||||
|  |     public validateArguments(args: string): undefined | string { | ||||||
|  |         return undefined | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -4,7 +4,7 @@ import TextValidator from "./Validators/TextValidator" | ||||||
| import DateValidator from "./Validators/DateValidator" | import DateValidator from "./Validators/DateValidator" | ||||||
| import NatValidator from "./Validators/NatValidator" | import NatValidator from "./Validators/NatValidator" | ||||||
| import IntValidator from "./Validators/IntValidator" | import IntValidator from "./Validators/IntValidator" | ||||||
| import LengthValidator from "./Validators/LengthValidator" | import DistanceValidator from "./Validators/DistanceValidator" | ||||||
| import DirectionValidator from "./Validators/DirectionValidator" | import DirectionValidator from "./Validators/DirectionValidator" | ||||||
| import WikidataValidator from "./Validators/WikidataValidator" | import WikidataValidator from "./Validators/WikidataValidator" | ||||||
| import PNatValidator from "./Validators/PNatValidator" | import PNatValidator from "./Validators/PNatValidator" | ||||||
|  | @ -71,7 +71,7 @@ export default class Validators { | ||||||
|         new DateValidator(), |         new DateValidator(), | ||||||
|         new NatValidator(), |         new NatValidator(), | ||||||
|         new IntValidator(), |         new IntValidator(), | ||||||
|         new LengthValidator(), |         new DistanceValidator(), | ||||||
|         new DirectionValidator(), |         new DirectionValidator(), | ||||||
|         new WikidataValidator(), |         new WikidataValidator(), | ||||||
|         new PNatValidator(), |         new PNatValidator(), | ||||||
|  |  | ||||||
							
								
								
									
										55
									
								
								src/UI/InputElement/Validators/DistanceValidator.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								src/UI/InputElement/Validators/DistanceValidator.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,55 @@ | ||||||
|  | import { Validator } from "../Validator" | ||||||
|  | import { Utils } from "../../../Utils" | ||||||
|  | import { eliCategory } from "../../../Models/RasterLayerProperties" | ||||||
|  | 
 | ||||||
|  | export default class DistanceValidator extends Validator { | ||||||
|  |     private readonly docs: string = [ | ||||||
|  |         "#### Helper-arguments", | ||||||
|  |         "Options are:", | ||||||
|  |         ["````json", | ||||||
|  |             "  \"background\": \"some_background_id or category, e.g. 'map'\"", | ||||||
|  |             "  \"zoom\": 20 # initial zoom level of the map", | ||||||
|  |             "}", | ||||||
|  |             "```"].join("\n") | ||||||
|  |     ].join("\n\n") | ||||||
|  | 
 | ||||||
|  |     constructor() { | ||||||
|  |         super( | ||||||
|  |             "distance", | ||||||
|  |             "A geographical distance in meters (rounded at two points). Will give an extra minimap with a measurement tool. Arguments: [ zoomlevel, preferredBackgroundMapType (comma separated) ], e.g. `[\"21\", \"map,photo\"]", | ||||||
|  |             "decimal" | ||||||
|  |         ) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     isValid = (str) => { | ||||||
|  |         const t = Number(str) | ||||||
|  |         return !isNaN(t) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     validateArguments(args: any): undefined | string { | ||||||
|  |         if (args === undefined) { | ||||||
|  |             return undefined | ||||||
|  |         } | ||||||
|  |         if (typeof args !== "object" || Array.isArray(args)) { | ||||||
|  |             return "Expected an object of type `{background?: string, zoom?: number}`" | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         const optionalKeys = ["background", "zoom"] | ||||||
|  |         const keys = Object.keys(args).filter(k => optionalKeys.indexOf(k) < 0) | ||||||
|  |         if (keys.length > 0) { | ||||||
|  |             return "Unknown key " + keys.join("; ") + "; use " + optionalKeys.join("; ") + " instead" | ||||||
|  |         } | ||||||
|  |         const bg = args["background"] | ||||||
|  |         if (bg && eliCategory.indexOf(bg) < 0) { | ||||||
|  |             return "The given background layer is not a recognized ELI-type. Perhaps you meant one of " + | ||||||
|  |                 Utils.sortedByLevenshteinDistance(bg, eliCategory, x => x).slice(0, 5) | ||||||
|  |         } | ||||||
|  |         if (typeof args["zoom"] !== "number") { | ||||||
|  |             return "zoom must be a number, got a " + typeof args["zoom"] | ||||||
|  |         } | ||||||
|  |         if (typeof args["zoom"] !== "number" || args["zoom"] <= 1 || args["zoom"] > 25) { | ||||||
|  |             return "zoom must be a number between 2 and 25, got " + args["zoom"] | ||||||
|  |         } | ||||||
|  |         return undefined | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -4,7 +4,7 @@ import { Validator } from "../Validator" | ||||||
| import { ValidatorType } from "../Validators" | import { ValidatorType } from "../Validators" | ||||||
| 
 | 
 | ||||||
| export default class FloatValidator extends Validator { | export default class FloatValidator extends Validator { | ||||||
|     inputmode: "decimal" = "decimal" |     inputmode: "decimal" = "decimal" as const | ||||||
| 
 | 
 | ||||||
|     constructor(name?: ValidatorType, explanation?: string) { |     constructor(name?: ValidatorType, explanation?: string) { | ||||||
|         super(name ?? "float", explanation ?? "A decimal number", "decimal") |         super(name ?? "float", explanation ?? "A decimal number", "decimal") | ||||||
|  |  | ||||||
|  | @ -1,16 +0,0 @@ | ||||||
| import { Validator } from "../Validator" |  | ||||||
| 
 |  | ||||||
| export default class LengthValidator extends Validator { |  | ||||||
|     constructor() { |  | ||||||
|         super( |  | ||||||
|             "distance", |  | ||||||
|             'A geographical distance in meters (rounded at two points). Will give an extra minimap with a measurement tool. Arguments: [ zoomlevel, preferredBackgroundMapType (comma separated) ], e.g. `["21", "map,photo"]', |  | ||||||
|             "decimal" |  | ||||||
|         ) |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     isValid = (str) => { |  | ||||||
|         const t = Number(str) |  | ||||||
|         return !isNaN(t) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -1,40 +1,14 @@ | ||||||
| import Title from "../../Base/Title" |  | ||||||
| import Combine from "../../Base/Combine" |  | ||||||
| import { Validator } from "../Validator" | import { Validator } from "../Validator" | ||||||
| import Table from "../../Base/Table" |  | ||||||
| 
 | 
 | ||||||
| export default class NameSuggestionIndexValidator extends Validator { | export default class NameSuggestionIndexValidator extends Validator { | ||||||
|     constructor() { |     constructor() { | ||||||
|         super( |         super( | ||||||
|             "nsi", |             "nsi", | ||||||
|             new Combine([ |             "Gives a list of possible suggestions for a brand or operator tag. Note: this is detected automatically; there is no need to explicitly set this" | ||||||
|                 "Gives a list of possible suggestions for a brand or operator tag.", |  | ||||||
|                 new Title("Helper arguments"), |  | ||||||
|                 new Table( |  | ||||||
|                     ["name", "doc"], |  | ||||||
|                     [ |  | ||||||
|                         [ |  | ||||||
|                             "options", |  | ||||||
|                             new Combine([ |  | ||||||
|                                 "A JSON-object of type `{ main: string, key: string }`. ", |  | ||||||
|                                 new Table( |  | ||||||
|                                     ["subarg", "doc"], |  | ||||||
|                                     [ |  | ||||||
|                                         [ |  | ||||||
|                                             "main", |  | ||||||
|                                             "The main tag to give suggestions for, e.g. `amenity=restaurant`.", |  | ||||||
|                                         ], |  | ||||||
|                                         [ |  | ||||||
|                                             "addExtraTags", |  | ||||||
|                                             "Extra tags to add to the suggestions, e.g. `nobrand=yes`.", |  | ||||||
|                                         ], |  | ||||||
|                                     ] |  | ||||||
|                                 ), |  | ||||||
|                             ]), |  | ||||||
|                         ], |  | ||||||
|                     ] |  | ||||||
|                 ), |  | ||||||
|             ]) |  | ||||||
|         ) |         ) | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     validateArguments(args: string): string | undefined { | ||||||
|  |         return "No arguments needed" | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -352,7 +352,7 @@ class LineRenderingLayer { | ||||||
|         // After waiting 'till the map has loaded, the data might have changed already
 |         // After waiting 'till the map has loaded, the data might have changed already
 | ||||||
|         // As such, we only now read the features from the featureSource and compare with the previously set data
 |         // As such, we only now read the features from the featureSource and compare with the previously set data
 | ||||||
|         const features = featureSource.data |         const features = featureSource.data | ||||||
|         if (features.length === 0) { |         if (!features || features.length === 0) { | ||||||
|             // This is a very ugly workaround for https://source.mapcomplete.org/MapComplete/MapComplete/issues/2312,
 |             // This is a very ugly workaround for https://source.mapcomplete.org/MapComplete/MapComplete/issues/2312,
 | ||||||
|             // but I couldn't find the root cause
 |             // but I couldn't find the root cause
 | ||||||
|             return |             return | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue