forked from MapComplete/MapComplete
		
	Merge branch 'develop'
This commit is contained in:
		
						commit
						217c103adc
					
				
					 75 changed files with 4344 additions and 2521 deletions
				
			
		
							
								
								
									
										30
									
								
								Docs/Hotkeys.md
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								Docs/Hotkeys.md
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,30 @@ | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |  Hotkeys  | ||||||
|  | ========= | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | ## Table of contents | ||||||
|  | 
 | ||||||
|  | 1. [Hotkeys](#hotkeys) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | MapComplete supports the following keys: | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | Key combination | Action | ||||||
|  | ----------------- | -------- | ||||||
|  | B | Opens the Background, layers and filters panel | ||||||
|  | Escape | Close the sidebar | ||||||
|  | L | Pan the map to the current location or zoom the map to the current location. Requests geopermission | ||||||
|  | M | Switch to a background layer of category map | ||||||
|  | O | Switch to a background layer of category osmbasedmap | ||||||
|  | P | Switch to a background layer of category photo | ||||||
|  | ctrl+F | Select the search bar to search locations | ||||||
|  | shift+O | Switch to default Mapnik-OpenStreetMap background | ||||||
|  |   | ||||||
|  | 
 | ||||||
|  | This document is autogenerated from  | ||||||
|  | @ -1,230 +1,135 @@ | ||||||
| import { Store, UIEventSource } from "../UIEventSource" |  | ||||||
| import Svg from "../../Svg" |  | ||||||
| import { LocalStorageSource } from "../Web/LocalStorageSource" |  | ||||||
| import { VariableUiElement } from "../../UI/Base/VariableUIElement" |  | ||||||
| import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig" |  | ||||||
| import { QueryParameters } from "../Web/QueryParameters" | import { QueryParameters } from "../Web/QueryParameters" | ||||||
| import { BBox } from "../BBox" | import { BBox } from "../BBox" | ||||||
| import Constants from "../../Models/Constants" | import Constants from "../../Models/Constants" | ||||||
| import SimpleFeatureSource from "../FeatureSource/Sources/SimpleFeatureSource" | import { GeoLocationPointProperties, GeoLocationState } from "../State/GeoLocationState" | ||||||
|  | import State from "../../State" | ||||||
|  | import { UIEventSource } from "../UIEventSource" | ||||||
| 
 | 
 | ||||||
| export interface GeoLocationPointProperties { | /** | ||||||
|     id: "gps" |  * The geolocation-handler takes a map-location and a geolocation state. | ||||||
|     "user:location": "yes" |  * It'll move the map as appropriate given the state of the geolocation-API | ||||||
|     date: string |  * It will also copy the geolocation into the appropriate FeatureSource to display on the map | ||||||
|     latitude: number |  */ | ||||||
|     longitude: number | export default class GeoLocationHandler { | ||||||
|     speed: number |     public readonly geolocationState: GeoLocationState | ||||||
|     accuracy: number |     private readonly _state: State | ||||||
|     heading: number |     public readonly mapHasMoved: UIEventSource<boolean> = new UIEventSource<boolean>(false) | ||||||
|     altitude: number |  | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| export default class GeoLocationHandler extends VariableUiElement { |     constructor( | ||||||
|     private readonly currentLocation?: SimpleFeatureSource |         geolocationState: GeoLocationState, | ||||||
| 
 |         state: State // { locationControl: UIEventSource<Loc>, selectedElement: UIEventSource<any>, leafletMap?: UIEventSource<any> })
 | ||||||
|     /** |     ) { | ||||||
|      * Wether or not the geolocation is active, aka the user requested the current location |         this.geolocationState = geolocationState | ||||||
|      */ |         this._state = state | ||||||
|     private readonly _isActive: UIEventSource<boolean> |         const mapLocation = state.locationControl | ||||||
| 
 |         // Did an interaction move the map?
 | ||||||
|     /** |         let self = this | ||||||
|      * Wether or not the geolocation is locked, aka the user requested the current location and wants the crosshair to follow the user |         let initTime = new Date() | ||||||
|      */ |         mapLocation.addCallbackD((_) => { | ||||||
|     private readonly _isLocked: UIEventSource<boolean> |             if (new Date().getTime() - initTime.getTime() < 250) { | ||||||
| 
 |                 return | ||||||
|     /** |  | ||||||
|      * The callback over the permission API |  | ||||||
|      * @private |  | ||||||
|      */ |  | ||||||
|     private readonly _permission: UIEventSource<string> |  | ||||||
|     /** |  | ||||||
|      * Literally: _currentGPSLocation.data != undefined |  | ||||||
|      * @private |  | ||||||
|      */ |  | ||||||
|     private readonly _hasLocation: Store<boolean> |  | ||||||
|     private readonly _currentGPSLocation: UIEventSource<GeolocationCoordinates> |  | ||||||
|     /** |  | ||||||
|      * Kept in order to update the marker |  | ||||||
|      * @private |  | ||||||
|      */ |  | ||||||
|     private readonly _leafletMap: UIEventSource<L.Map> |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * The date when the user requested the geolocation. If we have a location, it'll autozoom to it the first 30 secs |  | ||||||
|      */ |  | ||||||
|     private _lastUserRequest: UIEventSource<Date> |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * A small flag on localstorage. If the user previously granted the geolocation, it will be set. |  | ||||||
|      * On firefox, the permissions api is broken (probably fingerprint resistiance) and "granted + don't ask again" doesn't stick between sessions. |  | ||||||
|      * |  | ||||||
|      * Instead, we set this flag. If this flag is set upon loading the page, we start geolocating immediately. |  | ||||||
|      * If the user denies the geolocation this time, we unset this flag |  | ||||||
|      * @private |  | ||||||
|      */ |  | ||||||
|     private readonly _previousLocationGrant: UIEventSource<string> |  | ||||||
|     private readonly _layoutToUse: LayoutConfig |  | ||||||
| 
 |  | ||||||
|     constructor(state: { |  | ||||||
|         selectedElement: UIEventSource<any> |  | ||||||
|         currentUserLocation?: SimpleFeatureSource |  | ||||||
|         leafletMap: UIEventSource<any> |  | ||||||
|         layoutToUse: LayoutConfig |  | ||||||
|         featureSwitchGeolocation: UIEventSource<boolean> |  | ||||||
|     }) { |  | ||||||
|         const currentGPSLocation = new UIEventSource<GeolocationCoordinates>( |  | ||||||
|             undefined, |  | ||||||
|             "GPS-coordinate" |  | ||||||
|         ) |  | ||||||
|         const leafletMap = state.leafletMap |  | ||||||
|         const initedAt = new Date() |  | ||||||
|         let autozoomDone = false |  | ||||||
|         const hasLocation = currentGPSLocation.map((location) => location !== undefined) |  | ||||||
|         const previousLocationGrant = LocalStorageSource.Get("geolocation-permissions") |  | ||||||
|         const isActive = new UIEventSource<boolean>(false) |  | ||||||
|         const isLocked = new UIEventSource<boolean>(false) |  | ||||||
|         const permission = new UIEventSource<string>("") |  | ||||||
|         const lastClick = new UIEventSource<Date>(undefined) |  | ||||||
|         const lastClickWithinThreeSecs = lastClick.map((lastClick) => { |  | ||||||
|             if (lastClick === undefined) { |  | ||||||
|                 return false |  | ||||||
|             } |             } | ||||||
|             const timeDiff = (new Date().getTime() - lastClick.getTime()) / 1000 |             self.mapHasMoved.setData(true) | ||||||
|             return timeDiff <= 3 |             return true // Unsubscribe
 | ||||||
|         }) |         }) | ||||||
| 
 | 
 | ||||||
|         const latLonGiven = |         const latLonGivenViaUrl = | ||||||
|             QueryParameters.wasInitialized("lat") && QueryParameters.wasInitialized("lon") |             QueryParameters.wasInitialized("lat") || QueryParameters.wasInitialized("lon") | ||||||
|         const willFocus = lastClick.map((lastUserRequest) => { |         if (latLonGivenViaUrl) { | ||||||
|             const timeDiffInited = (new Date().getTime() - initedAt.getTime()) / 1000 |             // The URL counts as a 'user interaction'
 | ||||||
|             if (!latLonGiven && !autozoomDone && timeDiffInited < Constants.zoomToLocationTimeout) { |             this.mapHasMoved.setData(true) | ||||||
|                 return true |         } | ||||||
|             } |  | ||||||
|             if (lastUserRequest === undefined) { |  | ||||||
|                 return false |  | ||||||
|             } |  | ||||||
|             const timeDiff = (new Date().getTime() - lastUserRequest.getTime()) / 1000 |  | ||||||
|             return timeDiff <= Constants.zoomToLocationTimeout |  | ||||||
|         }) |  | ||||||
| 
 | 
 | ||||||
|         lastClick.addCallbackAndRunD((_) => { |         this.geolocationState.currentGPSLocation.addCallbackAndRunD((newLocation) => { | ||||||
|             window.setTimeout(() => { |             const timeSinceLastRequest = | ||||||
|                 if (lastClickWithinThreeSecs.data || willFocus.data) { |                 (new Date().getTime() - geolocationState.requestMoment.data?.getTime() ?? 0) / 1000 | ||||||
|                     lastClick.ping() |             if (!this.mapHasMoved.data) { | ||||||
|  |                 // The map hasn't moved yet; we received our first coordinates, so let's move there!
 | ||||||
|  |                 console.log( | ||||||
|  |                     "Moving the map to an initial location; time since last request is", | ||||||
|  |                     timeSinceLastRequest | ||||||
|  |                 ) | ||||||
|  |                 if (timeSinceLastRequest < Constants.zoomToLocationTimeout) { | ||||||
|  |                     self.MoveMapToCurrentLocation() | ||||||
|                 } |                 } | ||||||
|             }, 500) |             } | ||||||
|  | 
 | ||||||
|  |             if (this.geolocationState.isLocked.data) { | ||||||
|  |                 // Jup, the map is locked to the bound location: move automatically
 | ||||||
|  |                 self.MoveMapToCurrentLocation() | ||||||
|  |                 return | ||||||
|  |             } | ||||||
|         }) |         }) | ||||||
| 
 | 
 | ||||||
|         super( |         geolocationState.isLocked.map( | ||||||
|             hasLocation.map( |             (isLocked) => { | ||||||
|                 (hasLocationData) => { |                 if (isLocked) { | ||||||
|                     if (permission.data === "denied") { |                     state.leafletMap?.data?.dragging?.disable() | ||||||
|                         return Svg.location_refused_svg() |  | ||||||
|                     } |  | ||||||
| 
 |  | ||||||
|                     if (!isActive.data) { |  | ||||||
|                         return Svg.location_empty_svg() |  | ||||||
|                     } |  | ||||||
|                     if (!hasLocationData) { |  | ||||||
|                         // Position not yet found but we are active: we spin to indicate activity
 |  | ||||||
|                         // If will focus is active too, we indicate this differently
 |  | ||||||
|                         const icon = willFocus.data ? Svg.location_svg() : Svg.location_empty_svg() |  | ||||||
|                         icon.SetStyle("animation: spin 4s linear infinite;") |  | ||||||
|                         return icon |  | ||||||
|                     } |  | ||||||
|                     if (isLocked.data) { |  | ||||||
|                         return Svg.location_locked_svg() |  | ||||||
|                     } |  | ||||||
|                     if (lastClickWithinThreeSecs.data) { |  | ||||||
|                         return Svg.location_unlocked_svg() |  | ||||||
|                     } |  | ||||||
| 
 |  | ||||||
|                     // We have a location, so we show a dot in the center
 |  | ||||||
|                     return Svg.location_svg() |  | ||||||
|                 }, |  | ||||||
|                 [isActive, isLocked, permission, lastClickWithinThreeSecs, willFocus] |  | ||||||
|             ) |  | ||||||
|         ) |  | ||||||
|         this.SetClass("mapcontrol") |  | ||||||
|         this._isActive = isActive |  | ||||||
|         this._isLocked = isLocked |  | ||||||
|         this._permission = permission |  | ||||||
|         this._previousLocationGrant = previousLocationGrant |  | ||||||
|         this._currentGPSLocation = currentGPSLocation |  | ||||||
|         this._leafletMap = leafletMap |  | ||||||
|         this._layoutToUse = state.layoutToUse |  | ||||||
|         this._hasLocation = hasLocation |  | ||||||
|         this._lastUserRequest = lastClick |  | ||||||
|         const self = this |  | ||||||
| 
 |  | ||||||
|         const currentPointer = this._isActive.map( |  | ||||||
|             (isActive) => { |  | ||||||
|                 if (isActive && !self._hasLocation.data) { |  | ||||||
|                     return "cursor-wait" |  | ||||||
|                 } |  | ||||||
|                 return "cursor-pointer" |  | ||||||
|             }, |  | ||||||
|             [this._hasLocation] |  | ||||||
|         ) |  | ||||||
|         currentPointer.addCallbackAndRun((pointerClass) => { |  | ||||||
|             self.RemoveClass("cursor-wait") |  | ||||||
|             self.RemoveClass("cursor-pointer") |  | ||||||
|             self.SetClass(pointerClass) |  | ||||||
|         }) |  | ||||||
| 
 |  | ||||||
|         this.onClick(() => { |  | ||||||
|             /* |  | ||||||
|              * If the previous click was within 3 seconds (and we have an active location), then we lock to the location |  | ||||||
|              */ |  | ||||||
|             if (self._hasLocation.data) { |  | ||||||
|                 if (isLocked.data) { |  | ||||||
|                     isLocked.setData(false) |  | ||||||
|                 } else if (lastClick.data !== undefined) { |  | ||||||
|                     const timeDiff = (new Date().getTime() - lastClick.data.getTime()) / 1000 |  | ||||||
|                     if (timeDiff <= 3) { |  | ||||||
|                         isLocked.setData(true) |  | ||||||
|                         lastClick.setData(undefined) |  | ||||||
|                     } else { |  | ||||||
|                         lastClick.setData(new Date()) |  | ||||||
|                     } |  | ||||||
|                 } else { |                 } else { | ||||||
|                     lastClick.setData(new Date()) |                     state.leafletMap?.data?.dragging?.enable() | ||||||
|                 } |                 } | ||||||
|  |             }, | ||||||
|  |             [state.leafletMap] | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |         this.CopyGeolocationIntoMapstate() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Move the map to the GPS-location, except: | ||||||
|  |      * - If there is a selected element | ||||||
|  |      * - The location is out of the locked bound | ||||||
|  |      * - The GPS-location iss NULL-island | ||||||
|  |      * @constructor | ||||||
|  |      */ | ||||||
|  |     public MoveMapToCurrentLocation() { | ||||||
|  |         const newLocation = this.geolocationState.currentGPSLocation.data | ||||||
|  |         const mapLocation = this._state.locationControl | ||||||
|  |         const state = this._state | ||||||
|  |         // We got a new location.
 | ||||||
|  |         // Do we move the map to it?
 | ||||||
|  | 
 | ||||||
|  |         if (state.selectedElement.data !== undefined) { | ||||||
|  |             // Nope, there is something selected, so we don't move to the current GPS-location
 | ||||||
|  |             return | ||||||
|  |         } | ||||||
|  |         if (newLocation.latitude === 0 && newLocation.longitude === 0) { | ||||||
|  |             console.debug("Not moving to GPS-location: it is null island") | ||||||
|  |             return | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // We check that the GPS location is not out of bounds
 | ||||||
|  |         const bounds = state.layoutToUse.lockLocation | ||||||
|  |         if (bounds && bounds !== true) { | ||||||
|  |             // B is an array with our lock-location
 | ||||||
|  |             const inRange = new BBox(bounds).contains([newLocation.longitude, newLocation.latitude]) | ||||||
|  |             if (!inRange) { | ||||||
|  |                 return | ||||||
|             } |             } | ||||||
|  |         } | ||||||
| 
 | 
 | ||||||
|             self.init(true, true) |         mapLocation.setData({ | ||||||
|  |             zoom: mapLocation.data.zoom, | ||||||
|  |             lon: newLocation.longitude, | ||||||
|  |             lat: newLocation.latitude, | ||||||
|         }) |         }) | ||||||
|  |         this.mapHasMoved.setData(true) | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|         const doAutoZoomToLocation = |     private CopyGeolocationIntoMapstate() { | ||||||
|             !latLonGiven && |         const state = this._state | ||||||
|             state.featureSwitchGeolocation.data && |         this.geolocationState.currentGPSLocation.addCallbackAndRun((location) => { | ||||||
|             state.selectedElement.data !== undefined |             if (location === undefined) { | ||||||
|         this.init(false, doAutoZoomToLocation) |                 state.currentUserLocation?.features?.setData([]) | ||||||
| 
 |                 return | ||||||
|         isLocked.addCallbackAndRunD((isLocked) => { |  | ||||||
|             if (isLocked) { |  | ||||||
|                 leafletMap.data?.dragging?.disable() |  | ||||||
|             } else { |  | ||||||
|                 leafletMap.data?.dragging?.enable() |  | ||||||
|             } |             } | ||||||
|         }) |  | ||||||
| 
 |  | ||||||
|         this.currentLocation = state.currentUserLocation |  | ||||||
|         this._currentGPSLocation.addCallback((location) => { |  | ||||||
|             self._previousLocationGrant.setData("granted") |  | ||||||
|             const feature = { |             const feature = { | ||||||
|                 type: "Feature", |                 type: "Feature", | ||||||
|                 properties: <GeoLocationPointProperties>{ |                 properties: <GeoLocationPointProperties>{ | ||||||
|                     id: "gps", |                     id: "gps", | ||||||
|                     "user:location": "yes", |                     "user:location": "yes", | ||||||
|                     date: new Date().toISOString(), |                     date: new Date().toISOString(), | ||||||
|                     latitude: location.latitude, |                     ...location, | ||||||
|                     longitude: location.longitude, |  | ||||||
|                     speed: location.speed, |  | ||||||
|                     accuracy: location.accuracy, |  | ||||||
|                     heading: location.heading, |  | ||||||
|                     altitude: location.altitude, |  | ||||||
|                 }, |                 }, | ||||||
|                 geometry: { |                 geometry: { | ||||||
|                     type: "Point", |                     type: "Point", | ||||||
|  | @ -232,164 +137,7 @@ export default class GeoLocationHandler extends VariableUiElement { | ||||||
|                 }, |                 }, | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             self.currentLocation?.features?.setData([{ feature, freshness: new Date() }]) |             state.currentUserLocation?.features?.setData([{ feature, freshness: new Date() }]) | ||||||
| 
 |  | ||||||
|             if (willFocus.data) { |  | ||||||
|                 console.log("Zooming to user location: willFocus is set") |  | ||||||
|                 lastClick.setData(undefined) |  | ||||||
|                 autozoomDone = true |  | ||||||
|                 self.MoveToCurrentLocation(16) |  | ||||||
|             } else if (self._isLocked.data) { |  | ||||||
|                 self.MoveToCurrentLocation() |  | ||||||
|             } |  | ||||||
|         }) |         }) | ||||||
|     } |     } | ||||||
| 
 |  | ||||||
|     private init(askPermission: boolean, zoomToLocation: boolean) { |  | ||||||
|         const self = this |  | ||||||
| 
 |  | ||||||
|         if (self._isActive.data) { |  | ||||||
|             self.MoveToCurrentLocation(16) |  | ||||||
|             return |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         if (typeof navigator === "undefined") { |  | ||||||
|             return |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         try { |  | ||||||
|             navigator?.permissions?.query({ name: "geolocation" })?.then(function (status) { |  | ||||||
|                 console.log("Geolocation permission is ", status.state) |  | ||||||
|                 if (status.state === "granted") { |  | ||||||
|                     self.StartGeolocating(zoomToLocation) |  | ||||||
|                 } |  | ||||||
|                 self._permission.setData(status.state) |  | ||||||
|                 status.onchange = function () { |  | ||||||
|                     self._permission.setData(status.state) |  | ||||||
|                 } |  | ||||||
|             }) |  | ||||||
|         } catch (e) { |  | ||||||
|             console.error(e) |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         if (askPermission) { |  | ||||||
|             self.StartGeolocating(zoomToLocation) |  | ||||||
|         } else if (this._previousLocationGrant.data === "granted") { |  | ||||||
|             this._previousLocationGrant.setData("") |  | ||||||
|             self.StartGeolocating(zoomToLocation) |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Moves to the currently loaded location. |  | ||||||
|      * |  | ||||||
|      * // Should move to any location
 |  | ||||||
|      * let resultingLocation = undefined |  | ||||||
|      * let resultingzoom = 1 |  | ||||||
|      * const state = { |  | ||||||
|      *             selectedElement: new UIEventSource<any>(undefined); |  | ||||||
|      *             currentUserLocation: undefined , |  | ||||||
|      *             leafletMap: new UIEventSource<any>({getZoom: () => resultingzoom; setView: (loc, zoom) => {resultingLocation = loc; resultingzoom = zoom}), |  | ||||||
|      *             layoutToUse: new LayoutConfig(<any>{ |  | ||||||
|      *                 id: 'test', |  | ||||||
|      *                 title: {"en":"test"} |  | ||||||
|      *                description: "A testing theme", |  | ||||||
|      *                layers: [] |  | ||||||
|      *             }), |  | ||||||
|      *             featureSwitchGeolocation : new UIEventSource<boolean>(true) |  | ||||||
|      *         } |  | ||||||
|      * const handler = new GeoLocationHandler(state) |  | ||||||
|      * handler._currentGPSLocation.setData(<any> {latitude : 51.3, longitude: 4.1}) |  | ||||||
|      * handler.MoveToCurrentLocation() |  | ||||||
|      * resultingLocation // => [51.3, 4.1]
 |  | ||||||
|      * handler._currentGPSLocation.setData(<any> {latitude : 60, longitude: 60) // out of bounds
 |  | ||||||
|      * handler.MoveToCurrentLocation() |  | ||||||
|      * resultingLocation // => [60, 60]
 |  | ||||||
|      * |  | ||||||
|      * // should refuse to move if out of bounds
 |  | ||||||
|      * let resultingLocation = undefined |  | ||||||
|      * let resultingzoom = 1 |  | ||||||
|      * const state = { |  | ||||||
|      *             selectedElement: new UIEventSource<any>(undefined); |  | ||||||
|      *             currentUserLocation: undefined , |  | ||||||
|      *             leafletMap: new UIEventSource<any>({getZoom: () => resultingzoom; setView: (loc, zoom) => {resultingLocation = loc; resultingzoom = zoom}), |  | ||||||
|      *             layoutToUse: new LayoutConfig(<any>{ |  | ||||||
|      *                 id: 'test', |  | ||||||
|      *                 title: {"en":"test"} |  | ||||||
|      *                "lockLocation": [ [ 2.1, 50.4], [6.4, 51.54 ]], |  | ||||||
|      *                description: "A testing theme", |  | ||||||
|      *                layers: [] |  | ||||||
|      *             }), |  | ||||||
|      *             featureSwitchGeolocation : new UIEventSource<boolean>(true) |  | ||||||
|      *         } |  | ||||||
|      * const handler = new GeoLocationHandler(state) |  | ||||||
|      * handler._currentGPSLocation.setData(<any> {latitude : 51.3, longitude: 4.1}) |  | ||||||
|      * handler.MoveToCurrentLocation() |  | ||||||
|      * resultingLocation // => [51.3, 4.1]
 |  | ||||||
|      * handler._currentGPSLocation.setData(<any> {latitude : 60, longitude: 60) // out of bounds
 |  | ||||||
|      * handler.MoveToCurrentLocation() |  | ||||||
|      * resultingLocation // => [51.3, 4.1]
 |  | ||||||
|      */ |  | ||||||
|     private MoveToCurrentLocation(targetZoom?: number) { |  | ||||||
|         const location = this._currentGPSLocation.data |  | ||||||
|         this._lastUserRequest.setData(undefined) |  | ||||||
| 
 |  | ||||||
|         if ( |  | ||||||
|             this._currentGPSLocation.data.latitude === 0 && |  | ||||||
|             this._currentGPSLocation.data.longitude === 0 |  | ||||||
|         ) { |  | ||||||
|             console.debug("Not moving to GPS-location: it is null island") |  | ||||||
|             return |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         // We check that the GPS location is not out of bounds
 |  | ||||||
|         const b = this._layoutToUse.lockLocation |  | ||||||
|         let inRange = true |  | ||||||
|         if (b) { |  | ||||||
|             if (b !== true) { |  | ||||||
|                 // B is an array with our locklocation
 |  | ||||||
|                 inRange = new BBox(b).contains([location.longitude, location.latitude]) |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         if (!inRange) { |  | ||||||
|             console.log("Not zooming to GPS location: out of bounds", b, location) |  | ||||||
|         } else { |  | ||||||
|             const currentZoom = this._leafletMap.data.getZoom() |  | ||||||
|             this._leafletMap.data.setView( |  | ||||||
|                 [location.latitude, location.longitude], |  | ||||||
|                 Math.max(targetZoom ?? 0, currentZoom) |  | ||||||
|             ) |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     private StartGeolocating(zoomToGPS = true) { |  | ||||||
|         const self = this |  | ||||||
| 
 |  | ||||||
|         this._lastUserRequest.setData(zoomToGPS ? new Date() : new Date(0)) |  | ||||||
|         if (self._permission.data === "denied") { |  | ||||||
|             self._previousLocationGrant.setData("") |  | ||||||
|             self._isActive.setData(false) |  | ||||||
|             return "" |  | ||||||
|         } |  | ||||||
|         if (this._currentGPSLocation.data !== undefined) { |  | ||||||
|             this.MoveToCurrentLocation(16) |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         if (self._isActive.data) { |  | ||||||
|             return |  | ||||||
|         } |  | ||||||
|         self._isActive.setData(true) |  | ||||||
| 
 |  | ||||||
|         navigator.geolocation.watchPosition( |  | ||||||
|             function (position) { |  | ||||||
|                 self._currentGPSLocation.setData(position.coords) |  | ||||||
|             }, |  | ||||||
|             function () { |  | ||||||
|                 console.warn("Could not get location with navigator.geolocation") |  | ||||||
|             }, |  | ||||||
|             { |  | ||||||
|                 enableHighAccuracy: true, |  | ||||||
|             } |  | ||||||
|         ) |  | ||||||
|     } |  | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,8 +1,6 @@ | ||||||
| import * as L from "leaflet" |  | ||||||
| import { UIEventSource } from "../UIEventSource" | import { UIEventSource } from "../UIEventSource" | ||||||
| import ScrollableFullScreen from "../../UI/Base/ScrollableFullScreen" |  | ||||||
| import FilteredLayer from "../../Models/FilteredLayer" | import FilteredLayer from "../../Models/FilteredLayer" | ||||||
| import Constants from "../../Models/Constants" | import ScrollableFullScreen from "../../UI/Base/ScrollableFullScreen" | ||||||
| import BaseUIElement from "../../UI/BaseUIElement" | import BaseUIElement from "../../UI/BaseUIElement" | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  | @ -10,70 +8,16 @@ import BaseUIElement from "../../UI/BaseUIElement" | ||||||
|  * Shows the given uiToShow-element in the messagebox |  * Shows the given uiToShow-element in the messagebox | ||||||
|  */ |  */ | ||||||
| export default class StrayClickHandler { | export default class StrayClickHandler { | ||||||
|     private _lastMarker |     public static construct = ( | ||||||
| 
 |  | ||||||
|     constructor( |  | ||||||
|         state: { |         state: { | ||||||
|             LastClickLocation: UIEventSource<{ lat: number; lon: number }> |             LastClickLocation: UIEventSource<{ lat: number; lon: number }> | ||||||
|             selectedElement: UIEventSource<string> |             selectedElement: UIEventSource<string> | ||||||
|             filteredLayers: UIEventSource<FilteredLayer[]> |             filteredLayers: UIEventSource<FilteredLayer[]> | ||||||
|             leafletMap: UIEventSource<L.Map> |             leafletMap: UIEventSource<any> | ||||||
|         }, |         }, | ||||||
|         uiToShow: ScrollableFullScreen, |         uiToShow: ScrollableFullScreen, | ||||||
|         iconToShow: BaseUIElement |         iconToShow: BaseUIElement | ||||||
|     ) { |     ) => { | ||||||
|         const self = this |         return undefined | ||||||
|         const leafletMap = state.leafletMap |  | ||||||
|         state.filteredLayers.data.forEach((filteredLayer) => { |  | ||||||
|             filteredLayer.isDisplayed.addCallback((isEnabled) => { |  | ||||||
|                 if (isEnabled && self._lastMarker && leafletMap.data !== undefined) { |  | ||||||
|                     // When a layer is activated, we remove the 'last click location' in order to force the user to reclick
 |  | ||||||
|                     // This reclick might be at a location where a feature now appeared...
 |  | ||||||
|                     state.leafletMap.data.removeLayer(self._lastMarker) |  | ||||||
|                 } |  | ||||||
|             }) |  | ||||||
|         }) |  | ||||||
| 
 |  | ||||||
|         state.LastClickLocation.addCallback(function (lastClick) { |  | ||||||
|             if (self._lastMarker !== undefined) { |  | ||||||
|                 state.leafletMap.data?.removeLayer(self._lastMarker) |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             if (lastClick === undefined) { |  | ||||||
|                 return |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             state.selectedElement.setData(undefined) |  | ||||||
|             const clickCoor: [number, number] = [lastClick.lat, lastClick.lon] |  | ||||||
|             self._lastMarker = L.marker(clickCoor, { |  | ||||||
|                 icon: L.divIcon({ |  | ||||||
|                     html: iconToShow.ConstructElement(), |  | ||||||
|                     iconSize: [50, 50], |  | ||||||
|                     iconAnchor: [25, 50], |  | ||||||
|                     popupAnchor: [0, -45], |  | ||||||
|                 }), |  | ||||||
|             }) |  | ||||||
| 
 |  | ||||||
|             self._lastMarker.addTo(leafletMap.data) |  | ||||||
| 
 |  | ||||||
|             self._lastMarker.on("click", () => { |  | ||||||
|                 if (leafletMap.data.getZoom() < Constants.userJourney.minZoomLevelToAddNewPoints) { |  | ||||||
|                     leafletMap.data.flyTo( |  | ||||||
|                         clickCoor, |  | ||||||
|                         Constants.userJourney.minZoomLevelToAddNewPoints |  | ||||||
|                     ) |  | ||||||
|                     return |  | ||||||
|                 } |  | ||||||
| 
 |  | ||||||
|                 uiToShow.Activate() |  | ||||||
|             }) |  | ||||||
|         }) |  | ||||||
| 
 |  | ||||||
|         state.selectedElement.addCallback(() => { |  | ||||||
|             if (self._lastMarker !== undefined) { |  | ||||||
|                 leafletMap.data.removeLayer(self._lastMarker) |  | ||||||
|                 this._lastMarker = undefined |  | ||||||
|             } |  | ||||||
|         }) |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -16,6 +16,7 @@ export default class SplitAction extends OsmChangeAction { | ||||||
|     private readonly _splitPointsCoordinates: [number, number][] // lon, lat
 |     private readonly _splitPointsCoordinates: [number, number][] // lon, lat
 | ||||||
|     private _meta: { theme: string; changeType: "split" } |     private _meta: { theme: string; changeType: "split" } | ||||||
|     private _toleranceInMeters: number |     private _toleranceInMeters: number | ||||||
|  |     private _withNewCoordinates: (coordinates: [number, number][]) => void | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * Create a changedescription for splitting a point. |      * Create a changedescription for splitting a point. | ||||||
|  | @ -24,17 +25,20 @@ export default class SplitAction extends OsmChangeAction { | ||||||
|      * @param splitPointCoordinates: lon, lat |      * @param splitPointCoordinates: lon, lat | ||||||
|      * @param meta |      * @param meta | ||||||
|      * @param toleranceInMeters: if a splitpoint closer then this amount of meters to an existing point, the existing point will be used to split the line instead of a new point |      * @param toleranceInMeters: if a splitpoint closer then this amount of meters to an existing point, the existing point will be used to split the line instead of a new point | ||||||
|  |      * @param withNewCoordinates: an optional callback which will leak the new coordinates of the original way | ||||||
|      */ |      */ | ||||||
|     constructor( |     constructor( | ||||||
|         wayId: string, |         wayId: string, | ||||||
|         splitPointCoordinates: [number, number][], |         splitPointCoordinates: [number, number][], | ||||||
|         meta: { theme: string }, |         meta: { theme: string }, | ||||||
|         toleranceInMeters = 5 |         toleranceInMeters = 5, | ||||||
|  |         withNewCoordinates?: (coordinates: [number, number][]) => void | ||||||
|     ) { |     ) { | ||||||
|         super(wayId, true) |         super(wayId, true) | ||||||
|         this.wayId = wayId |         this.wayId = wayId | ||||||
|         this._splitPointsCoordinates = splitPointCoordinates |         this._splitPointsCoordinates = splitPointCoordinates | ||||||
|         this._toleranceInMeters = toleranceInMeters |         this._toleranceInMeters = toleranceInMeters | ||||||
|  |         this._withNewCoordinates = withNewCoordinates | ||||||
|         this._meta = { ...meta, changeType: "split" } |         this._meta = { ...meta, changeType: "split" } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -59,7 +63,7 @@ export default class SplitAction extends OsmChangeAction { | ||||||
|         const originalElement = <OsmWay>await OsmObject.DownloadObjectAsync(this.wayId) |         const originalElement = <OsmWay>await OsmObject.DownloadObjectAsync(this.wayId) | ||||||
|         const originalNodes = originalElement.nodes |         const originalNodes = originalElement.nodes | ||||||
| 
 | 
 | ||||||
|         // First, calculate splitpoints and remove points close to one another
 |         // First, calculate the splitpoints and remove points close to one another
 | ||||||
|         const splitInfo = this.CalculateSplitCoordinates(originalElement, this._toleranceInMeters) |         const splitInfo = this.CalculateSplitCoordinates(originalElement, this._toleranceInMeters) | ||||||
|         // Now we have a list with e.g.
 |         // Now we have a list with e.g.
 | ||||||
|         // [ { originalIndex: 0}, {originalIndex: 1, doSplit: true}, {originalIndex: 2}, {originalIndex: undefined, doSplit: true}, {originalIndex: 3}]
 |         // [ { originalIndex: 0}, {originalIndex: 1, doSplit: true}, {originalIndex: 2}, {originalIndex: undefined, doSplit: true}, {originalIndex: 3}]
 | ||||||
|  | @ -90,7 +94,7 @@ export default class SplitAction extends OsmChangeAction { | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         const changeDescription: ChangeDescription[] = [] |         const changeDescription: ChangeDescription[] = [] | ||||||
|         // Let's create the new points as needed
 |         // Let's create the new nodes as needed
 | ||||||
|         for (const element of splitInfo) { |         for (const element of splitInfo) { | ||||||
|             if (element.originalIndex >= 0) { |             if (element.originalIndex >= 0) { | ||||||
|                 continue |                 continue | ||||||
|  | @ -114,17 +118,21 @@ export default class SplitAction extends OsmChangeAction { | ||||||
|         for (const wayPart of wayParts) { |         for (const wayPart of wayParts) { | ||||||
|             let isOriginal = wayPart === longest |             let isOriginal = wayPart === longest | ||||||
|             if (isOriginal) { |             if (isOriginal) { | ||||||
|                 // We change the actual element!
 |                 // We change the existing way
 | ||||||
|                 const nodeIds = wayPart.map((p) => p.originalIndex) |                 const nodeIds = wayPart.map((p) => p.originalIndex) | ||||||
|  |                 const newCoordinates = wayPart.map((p) => p.lngLat) | ||||||
|                 changeDescription.push({ |                 changeDescription.push({ | ||||||
|                     type: "way", |                     type: "way", | ||||||
|                     id: originalElement.id, |                     id: originalElement.id, | ||||||
|                     changes: { |                     changes: { | ||||||
|                         coordinates: wayPart.map((p) => p.lngLat), |                         coordinates: newCoordinates, | ||||||
|                         nodes: nodeIds, |                         nodes: nodeIds, | ||||||
|                     }, |                     }, | ||||||
|                     meta: this._meta, |                     meta: this._meta, | ||||||
|                 }) |                 }) | ||||||
|  |                 if (this._withNewCoordinates) { | ||||||
|  |                     this._withNewCoordinates(newCoordinates) | ||||||
|  |                 } | ||||||
|                 allWayIdsInOrder.push(originalElement.id) |                 allWayIdsInOrder.push(originalElement.id) | ||||||
|                 allWaysNodesInOrder.push(nodeIds) |                 allWaysNodesInOrder.push(nodeIds) | ||||||
|             } else { |             } else { | ||||||
|  | @ -141,6 +149,10 @@ export default class SplitAction extends OsmChangeAction { | ||||||
|                     kv.push({ k: k, v: originalElement.tags[k] }) |                     kv.push({ k: k, v: originalElement.tags[k] }) | ||||||
|                 } |                 } | ||||||
|                 const nodeIds = wayPart.map((p) => p.originalIndex) |                 const nodeIds = wayPart.map((p) => p.originalIndex) | ||||||
|  |                 if (nodeIds.length <= 1) { | ||||||
|  |                     console.error("Got a segment with only one node - skipping") | ||||||
|  |                     continue | ||||||
|  |                 } | ||||||
|                 changeDescription.push({ |                 changeDescription.push({ | ||||||
|                     type: "way", |                     type: "way", | ||||||
|                     id: id, |                     id: id, | ||||||
|  |  | ||||||
|  | @ -8,7 +8,7 @@ import { LocalStorageSource } from "../Web/LocalStorageSource" | ||||||
| import SimpleMetaTagger from "../SimpleMetaTagger" | import SimpleMetaTagger from "../SimpleMetaTagger" | ||||||
| import FeatureSource from "../FeatureSource/FeatureSource" | import FeatureSource from "../FeatureSource/FeatureSource" | ||||||
| import { ElementStorage } from "../ElementStorage" | import { ElementStorage } from "../ElementStorage" | ||||||
| import { GeoLocationPointProperties } from "../Actors/GeoLocationHandler" | import { GeoLocationPointProperties } from "../State/GeoLocationState" | ||||||
| import { GeoOperations } from "../GeoOperations" | import { GeoOperations } from "../GeoOperations" | ||||||
| import { ChangesetHandler, ChangesetTag } from "./ChangesetHandler" | import { ChangesetHandler, ChangesetTag } from "./ChangesetHandler" | ||||||
| import { OsmConnection } from "./OsmConnection" | import { OsmConnection } from "./OsmConnection" | ||||||
|  |  | ||||||
							
								
								
									
										144
									
								
								Logic/State/GeoLocationState.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										144
									
								
								Logic/State/GeoLocationState.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,144 @@ | ||||||
|  | import { UIEventSource } from "../UIEventSource" | ||||||
|  | import { LocalStorageSource } from "../Web/LocalStorageSource" | ||||||
|  | 
 | ||||||
|  | type GeolocationState = "prompt" | "requested" | "granted" | "denied" | ||||||
|  | 
 | ||||||
|  | export interface GeoLocationPointProperties extends GeolocationCoordinates { | ||||||
|  |     id: "gps" | ||||||
|  |     "user:location": "yes" | ||||||
|  |     date: string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * An abstract representation of the current state of the geolocation. | ||||||
|  |  * | ||||||
|  |  * | ||||||
|  |  */ | ||||||
|  | export class GeoLocationState { | ||||||
|  |     /** | ||||||
|  |      * What do we know about the current state of having access to the GPS? | ||||||
|  |      * If 'prompt', then we just started and didn't request access yet | ||||||
|  |      * 'requested' means the user tapped the 'locate me' button at least once | ||||||
|  |      * 'granted' means that it is granted | ||||||
|  |      * 'denied' means that we don't have access | ||||||
|  |      * | ||||||
|  |      */ | ||||||
|  |     public readonly permission: UIEventSource<GeolocationState> = new UIEventSource("prompt") | ||||||
|  | 
 | ||||||
|  |     public readonly requestMoment: UIEventSource<Date | undefined> = new UIEventSource(undefined) | ||||||
|  |     /** | ||||||
|  |      * If true: the map will center (and re-center) to this location | ||||||
|  |      */ | ||||||
|  |     public readonly isLocked: UIEventSource<boolean> = new UIEventSource<boolean>(false) | ||||||
|  | 
 | ||||||
|  |     public readonly currentGPSLocation: UIEventSource<GeolocationCoordinates | undefined> = | ||||||
|  |         new UIEventSource<GeolocationCoordinates | undefined>(undefined) | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * A small flag on localstorage. If the user previously granted the geolocation, it will be set. | ||||||
|  |      * On firefox, the permissions api is broken (probably fingerprint resistiance) and "granted + don't ask again" doesn't stick between sessions. | ||||||
|  |      * | ||||||
|  |      * Instead, we set this flag. If this flag is set upon loading the page, we start geolocating immediately. | ||||||
|  |      * If the user denies the geolocation this time, we unset this flag | ||||||
|  |      * @private | ||||||
|  |      */ | ||||||
|  |     private readonly _previousLocationGrant: UIEventSource<"true" | "false"> = <any>( | ||||||
|  |         LocalStorageSource.Get("geolocation-permissions") | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Used to detect a permission retraction | ||||||
|  |      */ | ||||||
|  |     private readonly _grantedThisSession: UIEventSource<boolean> = new UIEventSource<boolean>(false) | ||||||
|  |     constructor() { | ||||||
|  |         const self = this | ||||||
|  | 
 | ||||||
|  |         this.permission.addCallbackAndRunD(async (state) => { | ||||||
|  |             if (state === "granted") { | ||||||
|  |                 self._previousLocationGrant.setData("true") | ||||||
|  |                 self._grantedThisSession.setData(true) | ||||||
|  |             } | ||||||
|  |             if (state === "prompt" && self._grantedThisSession.data) { | ||||||
|  |                 // This is _really_ weird: we had a grant earlier, but it's 'prompt' now?
 | ||||||
|  |                 // This means that the rights have been revoked again!
 | ||||||
|  |                 //   self.permission.setData("denied")
 | ||||||
|  |                 self._previousLocationGrant.setData("false") | ||||||
|  |                 self.permission.setData("denied") | ||||||
|  |                 self.currentGPSLocation.setData(undefined) | ||||||
|  |                 console.warn("Detected a downgrade in permissions!") | ||||||
|  |             } | ||||||
|  |             if (state === "denied") { | ||||||
|  |                 self._previousLocationGrant.setData("false") | ||||||
|  |             } | ||||||
|  |         }) | ||||||
|  | 
 | ||||||
|  |         console.log("Previous location grant:", this._previousLocationGrant.data) | ||||||
|  |         if (this._previousLocationGrant.data === "true") { | ||||||
|  |             // A previous visit successfully granted permission. Chance is high that we are allowed to use it again!
 | ||||||
|  | 
 | ||||||
|  |             // We set the flag to false again. If the user only wanted to share their location once, we are not gonna keep bothering them
 | ||||||
|  |             this._previousLocationGrant.setData("false") | ||||||
|  |             console.log("Requesting access to GPS as this was previously granted") | ||||||
|  |             this.requestPermission() | ||||||
|  |         } | ||||||
|  |         window["geolocation_state"] = this | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Installs the listener for updates | ||||||
|  |      * @private | ||||||
|  |      */ | ||||||
|  |     private async startWatching() { | ||||||
|  |         const self = this | ||||||
|  |         navigator.geolocation.watchPosition( | ||||||
|  |             function (position) { | ||||||
|  |                 self.currentGPSLocation.setData(position.coords) | ||||||
|  |                 self._previousLocationGrant.setData("true") | ||||||
|  |             }, | ||||||
|  |             function () { | ||||||
|  |                 console.warn("Could not get location with navigator.geolocation") | ||||||
|  |             }, | ||||||
|  |             { | ||||||
|  |                 enableHighAccuracy: true, | ||||||
|  |             } | ||||||
|  |         ) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Requests the user to allow access to their position. | ||||||
|  |      * When granted, will be written to the 'geolocationState'. | ||||||
|  |      * This class will start watching | ||||||
|  |      */ | ||||||
|  |     public requestPermission() { | ||||||
|  |         if (typeof navigator === "undefined") { | ||||||
|  |             // Not compatible with this browser
 | ||||||
|  |             this.permission.setData("denied") | ||||||
|  |             return | ||||||
|  |         } | ||||||
|  |         if (this.permission.data !== "prompt" && this.permission.data !== "requested") { | ||||||
|  |             // If the user denies the first prompt, revokes the deny and then tries again, we have to run the flow as well
 | ||||||
|  |             // Hence that we continue the flow if it is "requested"
 | ||||||
|  |             return | ||||||
|  |         } | ||||||
|  |         this.requestMoment.setData(new Date()) | ||||||
|  |         this.permission.setData("requested") | ||||||
|  |         try { | ||||||
|  |             navigator?.permissions | ||||||
|  |                 ?.query({ name: "geolocation" }) | ||||||
|  |                 .then((status) => { | ||||||
|  |                     console.log("Status update: received geolocation permission is ", status.state) | ||||||
|  |                     this.permission.setData(status.state) | ||||||
|  |                     const self = this | ||||||
|  |                     status.onchange = function () { | ||||||
|  |                         self.permission.setData(status.state) | ||||||
|  |                     } | ||||||
|  |                     this.permission.setData("requested") | ||||||
|  |                     // We _must_ call 'startWatching', as that is the actual trigger for the popup...
 | ||||||
|  |                     self.startWatching() | ||||||
|  |                 }) | ||||||
|  |                 .catch((e) => console.error("Could not get geopermission", e)) | ||||||
|  |         } catch (e) { | ||||||
|  |             console.error("Could not get permission:", e) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -1,7 +1,7 @@ | ||||||
| import { Utils } from "../Utils" | import { Utils } from "../Utils" | ||||||
| 
 | 
 | ||||||
| export default class Constants { | export default class Constants { | ||||||
|     public static vNumber = "0.25.0" |     public static vNumber = "0.25.2" | ||||||
| 
 | 
 | ||||||
|     public static ImgurApiKey = "7070e7167f0a25a" |     public static ImgurApiKey = "7070e7167f0a25a" | ||||||
|     public static readonly mapillary_client_token_v4 = |     public static readonly mapillary_client_token_v4 = | ||||||
|  |  | ||||||
|  | @ -260,7 +260,7 @@ export interface LayerConfigJson { | ||||||
|                   /** |                   /** | ||||||
|                    * The type of background picture |                    * The type of background picture | ||||||
|                    */ |                    */ | ||||||
|                   preferredBackground: |                   preferredBackground?: | ||||||
|                       | "osmbasedmap" |                       | "osmbasedmap" | ||||||
|                       | "photo" |                       | "photo" | ||||||
|                       | "historicphoto" |                       | "historicphoto" | ||||||
|  |  | ||||||
							
								
								
									
										124
									
								
								UI/Base/Hotkeys.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										124
									
								
								UI/Base/Hotkeys.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,124 @@ | ||||||
|  | import { Utils } from "../../Utils" | ||||||
|  | import Combine from "./Combine" | ||||||
|  | import BaseUIElement from "../BaseUIElement" | ||||||
|  | import Title from "./Title" | ||||||
|  | import Table from "./Table" | ||||||
|  | import { UIEventSource } from "../../Logic/UIEventSource" | ||||||
|  | import { VariableUiElement } from "./VariableUIElement" | ||||||
|  | import doc = Mocha.reporters.doc | ||||||
|  | 
 | ||||||
|  | export default class Hotkeys { | ||||||
|  |     private static readonly _docs: UIEventSource< | ||||||
|  |         { | ||||||
|  |             key: { ctrl?: string; shift?: string; alt?: string; nomod?: string; onUp?: boolean } | ||||||
|  |             documentation: string | ||||||
|  |         }[] | ||||||
|  |     > = new UIEventSource< | ||||||
|  |         { | ||||||
|  |             key: { ctrl?: string; shift?: string; alt?: string; nomod?: string; onUp?: boolean } | ||||||
|  |             documentation: string | ||||||
|  |         }[] | ||||||
|  |     >([]) | ||||||
|  | 
 | ||||||
|  |     private static textElementSelected(): boolean { | ||||||
|  |         console.log(document.activeElement) | ||||||
|  |         return document?.activeElement?.tagName?.toLowerCase() === "input" | ||||||
|  |     } | ||||||
|  |     public static RegisterHotkey( | ||||||
|  |         key: ( | ||||||
|  |             | { | ||||||
|  |                   ctrl: string | ||||||
|  |               } | ||||||
|  |             | { | ||||||
|  |                   shift: string | ||||||
|  |               } | ||||||
|  |             | { | ||||||
|  |                   alt: string | ||||||
|  |               } | ||||||
|  |             | { | ||||||
|  |                   nomod: string | ||||||
|  |               } | ||||||
|  |         ) & { | ||||||
|  |             onUp?: boolean | ||||||
|  |         }, | ||||||
|  |         documentation: string, | ||||||
|  |         action: () => void | ||||||
|  |     ) { | ||||||
|  |         const type = key["onUp"] ? "keyup" : "keypress" | ||||||
|  |         let keycode: string = key["ctrl"] ?? key["shift"] ?? key["alt"] ?? key["nomod"] | ||||||
|  |         if (keycode.length == 1) { | ||||||
|  |             keycode = keycode.toLowerCase() | ||||||
|  |             if (key["shift"] !== undefined) { | ||||||
|  |                 keycode = keycode.toUpperCase() | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         this._docs.data.push({ key, documentation }) | ||||||
|  |         this._docs.ping() | ||||||
|  |         if (Utils.runningFromConsole) { | ||||||
|  |             return | ||||||
|  |         } | ||||||
|  |         if (key["ctrl"] !== undefined) { | ||||||
|  |             document.addEventListener("keydown", function (event) { | ||||||
|  |                 if (event.ctrlKey && event.key === keycode) { | ||||||
|  |                     action() | ||||||
|  |                     event.preventDefault() | ||||||
|  |                 } | ||||||
|  |             }) | ||||||
|  |         } else if (key["shift"] !== undefined) { | ||||||
|  |             document.addEventListener(type, function (event) { | ||||||
|  |                 if (Hotkeys.textElementSelected()) { | ||||||
|  |                     // A text element is selected, we don't do anything special
 | ||||||
|  |                     return | ||||||
|  |                 } | ||||||
|  |                 if (event.shiftKey && event.key === keycode) { | ||||||
|  |                     action() | ||||||
|  |                     event.preventDefault() | ||||||
|  |                 } | ||||||
|  |             }) | ||||||
|  |         } else if (key["alt"] !== undefined) { | ||||||
|  |             document.addEventListener(type, function (event) { | ||||||
|  |                 if (event.altKey && event.key === keycode) { | ||||||
|  |                     action() | ||||||
|  |                     event.preventDefault() | ||||||
|  |                 } | ||||||
|  |             }) | ||||||
|  |         } else if (key["nomod"] !== undefined) { | ||||||
|  |             document.addEventListener(type, function (event) { | ||||||
|  |                 if (Hotkeys.textElementSelected()) { | ||||||
|  |                     // A text element is selected, we don't do anything special
 | ||||||
|  |                     return | ||||||
|  |                 } | ||||||
|  |                 if (event.key === keycode) { | ||||||
|  |                     action() | ||||||
|  |                     event.preventDefault() | ||||||
|  |                 } | ||||||
|  |             }) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     static generateDocumentation(): BaseUIElement { | ||||||
|  |         return new Combine([ | ||||||
|  |             new Title("Hotkeys", 1), | ||||||
|  |             "MapComplete supports the following keys:", | ||||||
|  |             new Table( | ||||||
|  |                 ["Key combination", "Action"], | ||||||
|  |                 Hotkeys._docs.data | ||||||
|  |                     .map(({ key, documentation }) => { | ||||||
|  |                         const modifiers = Object.keys(key).filter( | ||||||
|  |                             (k) => k !== "nomod" && k !== "onUp" | ||||||
|  |                         ) | ||||||
|  |                         const keycode: string = | ||||||
|  |                             key["ctrl"] ?? key["shift"] ?? key["alt"] ?? key["nomod"] | ||||||
|  |                         modifiers.push(keycode) | ||||||
|  |                         return [modifiers.join("+"), documentation] | ||||||
|  |                     }) | ||||||
|  |                     .sort() | ||||||
|  |             ), | ||||||
|  |         ]) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     static generateDocumentationDynamic(): BaseUIElement { | ||||||
|  |         return new VariableUiElement(Hotkeys._docs.map((_) => Hotkeys.generateDocumentation())) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -14,7 +14,83 @@ import BackgroundMapSwitch from "../BigComponents/BackgroundMapSwitch" | ||||||
| import AvailableBaseLayersImplementation from "../../Logic/Actors/AvailableBaseLayersImplementation" | import AvailableBaseLayersImplementation from "../../Logic/Actors/AvailableBaseLayersImplementation" | ||||||
| import ShowDataLayer from "../ShowDataLayer/ShowDataLayer" | import ShowDataLayer from "../ShowDataLayer/ShowDataLayer" | ||||||
| import ShowDataLayerImplementation from "../ShowDataLayer/ShowDataLayerImplementation" | import ShowDataLayerImplementation from "../ShowDataLayer/ShowDataLayerImplementation" | ||||||
|  | import FilteredLayer from "../../Models/FilteredLayer" | ||||||
|  | import ScrollableFullScreen from "./ScrollableFullScreen" | ||||||
|  | import Constants from "../../Models/Constants" | ||||||
|  | import StrayClickHandler from "../../Logic/Actors/StrayClickHandler" | ||||||
| 
 | 
 | ||||||
|  | /** | ||||||
|  |  * The stray-click-hanlders adds a marker to the map if no feature was clicked. | ||||||
|  |  * Shows the given uiToShow-element in the messagebox | ||||||
|  |  */ | ||||||
|  | export class StrayClickHandlerImplementation { | ||||||
|  |     private _lastMarker | ||||||
|  | 
 | ||||||
|  |     constructor( | ||||||
|  |         state: { | ||||||
|  |             LastClickLocation: UIEventSource<{ lat: number; lon: number }> | ||||||
|  |             selectedElement: UIEventSource<string> | ||||||
|  |             filteredLayers: UIEventSource<FilteredLayer[]> | ||||||
|  |             leafletMap: UIEventSource<L.Map> | ||||||
|  |         }, | ||||||
|  |         uiToShow: ScrollableFullScreen, | ||||||
|  |         iconToShow: BaseUIElement | ||||||
|  |     ) { | ||||||
|  |         const self = this | ||||||
|  |         const leafletMap = state.leafletMap | ||||||
|  |         state.filteredLayers.data.forEach((filteredLayer) => { | ||||||
|  |             filteredLayer.isDisplayed.addCallback((isEnabled) => { | ||||||
|  |                 if (isEnabled && self._lastMarker && leafletMap.data !== undefined) { | ||||||
|  |                     // When a layer is activated, we remove the 'last click location' in order to force the user to reclick
 | ||||||
|  |                     // This reclick might be at a location where a feature now appeared...
 | ||||||
|  |                     state.leafletMap.data.removeLayer(self._lastMarker) | ||||||
|  |                 } | ||||||
|  |             }) | ||||||
|  |         }) | ||||||
|  | 
 | ||||||
|  |         state.LastClickLocation.addCallback(function (lastClick) { | ||||||
|  |             if (self._lastMarker !== undefined) { | ||||||
|  |                 state.leafletMap.data?.removeLayer(self._lastMarker) | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             if (lastClick === undefined) { | ||||||
|  |                 return | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             state.selectedElement.setData(undefined) | ||||||
|  |             const clickCoor: [number, number] = [lastClick.lat, lastClick.lon] | ||||||
|  |             self._lastMarker = L.marker(clickCoor, { | ||||||
|  |                 icon: L.divIcon({ | ||||||
|  |                     html: iconToShow.ConstructElement(), | ||||||
|  |                     iconSize: [50, 50], | ||||||
|  |                     iconAnchor: [25, 50], | ||||||
|  |                     popupAnchor: [0, -45], | ||||||
|  |                 }), | ||||||
|  |             }) | ||||||
|  | 
 | ||||||
|  |             self._lastMarker.addTo(leafletMap.data) | ||||||
|  | 
 | ||||||
|  |             self._lastMarker.on("click", () => { | ||||||
|  |                 if (leafletMap.data.getZoom() < Constants.userJourney.minZoomLevelToAddNewPoints) { | ||||||
|  |                     leafletMap.data.flyTo( | ||||||
|  |                         clickCoor, | ||||||
|  |                         Constants.userJourney.minZoomLevelToAddNewPoints | ||||||
|  |                     ) | ||||||
|  |                     return | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 uiToShow.Activate() | ||||||
|  |             }) | ||||||
|  |         }) | ||||||
|  | 
 | ||||||
|  |         state.selectedElement.addCallback(() => { | ||||||
|  |             if (self._lastMarker !== undefined) { | ||||||
|  |                 leafletMap.data.removeLayer(self._lastMarker) | ||||||
|  |                 this._lastMarker = undefined | ||||||
|  |             } | ||||||
|  |         }) | ||||||
|  |     } | ||||||
|  | } | ||||||
| export default class MinimapImplementation extends BaseUIElement implements MinimapObj { | export default class MinimapImplementation extends BaseUIElement implements MinimapObj { | ||||||
|     private static _nextId = 0 |     private static _nextId = 0 | ||||||
|     public readonly leafletMap: UIEventSource<Map> |     public readonly leafletMap: UIEventSource<Map> | ||||||
|  | @ -53,6 +129,18 @@ export default class MinimapImplementation extends BaseUIElement implements Mini | ||||||
|         AvailableBaseLayers.implement(new AvailableBaseLayersImplementation()) |         AvailableBaseLayers.implement(new AvailableBaseLayersImplementation()) | ||||||
|         Minimap.createMiniMap = (options) => new MinimapImplementation(options) |         Minimap.createMiniMap = (options) => new MinimapImplementation(options) | ||||||
|         ShowDataLayer.actualContstructor = (options) => new ShowDataLayerImplementation(options) |         ShowDataLayer.actualContstructor = (options) => new ShowDataLayerImplementation(options) | ||||||
|  |         StrayClickHandler.construct = ( | ||||||
|  |             state: { | ||||||
|  |                 LastClickLocation: UIEventSource<{ lat: number; lon: number }> | ||||||
|  |                 selectedElement: UIEventSource<string> | ||||||
|  |                 filteredLayers: UIEventSource<FilteredLayer[]> | ||||||
|  |                 leafletMap: UIEventSource<L.Map> | ||||||
|  |             }, | ||||||
|  |             uiToShow: ScrollableFullScreen, | ||||||
|  |             iconToShow: BaseUIElement | ||||||
|  |         ) => { | ||||||
|  |             return new StrayClickHandlerImplementation(state, uiToShow, iconToShow) | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public installBounds(factor: number | BBox, showRange?: boolean) { |     public installBounds(factor: number | BBox, showRange?: boolean) { | ||||||
|  |  | ||||||
|  | @ -5,6 +5,7 @@ import { UIEventSource } from "../../Logic/UIEventSource" | ||||||
| import Hash from "../../Logic/Web/Hash" | import Hash from "../../Logic/Web/Hash" | ||||||
| import BaseUIElement from "../BaseUIElement" | import BaseUIElement from "../BaseUIElement" | ||||||
| import Title from "./Title" | import Title from "./Title" | ||||||
|  | import Hotkeys from "./Hotkeys" | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * |  * | ||||||
|  | @ -82,12 +83,11 @@ export default class ScrollableFullScreen { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private static initEmpty(): FixedUiElement { |     private static initEmpty(): FixedUiElement { | ||||||
|         document.addEventListener("keyup", function (event) { |         Hotkeys.RegisterHotkey( | ||||||
|             if (event.code === "Escape") { |             { nomod: "Escape", onUp: true }, | ||||||
|                 ScrollableFullScreen.collapse() |             "Close the sidebar", | ||||||
|                 event.preventDefault() |             ScrollableFullScreen.collapse | ||||||
|             } |         ) | ||||||
|         }) |  | ||||||
| 
 | 
 | ||||||
|         return new FixedUiElement("") |         return new FixedUiElement("") | ||||||
|     } |     } | ||||||
|  | @ -117,7 +117,7 @@ export default class ScrollableFullScreen { | ||||||
|         this._fullscreencomponent.AttachTo("fullscreen") |         this._fullscreencomponent.AttachTo("fullscreen") | ||||||
|         const fs = document.getElementById("fullscreen") |         const fs = document.getElementById("fullscreen") | ||||||
|         ScrollableFullScreen._currentlyOpen = this |         ScrollableFullScreen._currentlyOpen = this | ||||||
|         fs.classList.remove("hidden") |         fs?.classList?.remove("hidden") | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private BuildComponent(title: BaseUIElement, content: BaseUIElement): BaseUIElement { |     private BuildComponent(title: BaseUIElement, content: BaseUIElement): BaseUIElement { | ||||||
|  |  | ||||||
|  | @ -24,6 +24,10 @@ export default abstract class BaseUIElement { | ||||||
|     AttachTo(divId: string) { |     AttachTo(divId: string) { | ||||||
|         let element = document.getElementById(divId) |         let element = document.getElementById(divId) | ||||||
|         if (element === null) { |         if (element === null) { | ||||||
|  |             if (Utils.runningFromConsole) { | ||||||
|  |                 this.ConstructElement() | ||||||
|  |                 return | ||||||
|  |             } | ||||||
|             throw "SEVERE: could not attach UIElement to " + divId |             throw "SEVERE: could not attach UIElement to " + divId | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -7,6 +7,7 @@ import BaseLayer from "../../Models/BaseLayer" | ||||||
| import AvailableBaseLayers from "../../Logic/Actors/AvailableBaseLayers" | import AvailableBaseLayers from "../../Logic/Actors/AvailableBaseLayers" | ||||||
| import BaseUIElement from "../BaseUIElement" | import BaseUIElement from "../BaseUIElement" | ||||||
| import { GeoOperations } from "../../Logic/GeoOperations" | import { GeoOperations } from "../../Logic/GeoOperations" | ||||||
|  | import Hotkeys from "../Base/Hotkeys" | ||||||
| 
 | 
 | ||||||
| class SingleLayerSelectionButton extends Toggle { | class SingleLayerSelectionButton extends Toggle { | ||||||
|     public readonly activate: () => void |     public readonly activate: () => void | ||||||
|  | @ -48,13 +49,13 @@ class SingleLayerSelectionButton extends Toggle { | ||||||
|         let toggle: BaseUIElement = new Toggle( |         let toggle: BaseUIElement = new Toggle( | ||||||
|             selected, |             selected, | ||||||
|             unselected, |             unselected, | ||||||
|             options.currentBackground.map((bg) => bg.category === options.preferredType) |             options.currentBackground.map((bg) => bg?.category === options.preferredType) | ||||||
|         ) |         ) | ||||||
| 
 | 
 | ||||||
|         super( |         super( | ||||||
|             toggle, |             toggle, | ||||||
|             undefined, |             undefined, | ||||||
|             available.map((av) => av.category === options.preferredType) |             available.map((av) => av?.category === options.preferredType) | ||||||
|         ) |         ) | ||||||
| 
 | 
 | ||||||
|         /** |         /** | ||||||
|  | @ -174,6 +175,7 @@ export default class BackgroundMapSwitch extends Combine { | ||||||
|         options?: { |         options?: { | ||||||
|             preferredCategory?: string |             preferredCategory?: string | ||||||
|             allowedCategories?: ("osmbasedmap" | "photo" | "map")[] |             allowedCategories?: ("osmbasedmap" | "photo" | "map")[] | ||||||
|  |             enableHotkeys?: boolean | ||||||
|         } |         } | ||||||
|     ) { |     ) { | ||||||
|         const allowedCategories = options?.allowedCategories ?? ["osmbasedmap", "photo", "map"] |         const allowedCategories = options?.allowedCategories ?? ["osmbasedmap", "photo", "map"] | ||||||
|  | @ -183,7 +185,7 @@ export default class BackgroundMapSwitch extends Combine { | ||||||
|         let activatePrevious: () => void = undefined |         let activatePrevious: () => void = undefined | ||||||
|         for (const category of allowedCategories) { |         for (const category of allowedCategories) { | ||||||
|             let preferredLayer = undefined |             let preferredLayer = undefined | ||||||
|             if (previousLayer.category === category) { |             if (previousLayer?.category === category) { | ||||||
|                 preferredLayer = previousLayer |                 preferredLayer = previousLayer | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|  | @ -198,6 +200,16 @@ export default class BackgroundMapSwitch extends Combine { | ||||||
|             if (category === options?.preferredCategory) { |             if (category === options?.preferredCategory) { | ||||||
|                 button.activate() |                 button.activate() | ||||||
|             } |             } | ||||||
|  | 
 | ||||||
|  |             if (options?.enableHotkeys) { | ||||||
|  |                 Hotkeys.RegisterHotkey( | ||||||
|  |                     { nomod: category.charAt(0).toUpperCase() }, | ||||||
|  |                     "Switch to a background layer of category " + category, | ||||||
|  |                     () => { | ||||||
|  |                         button.activate() | ||||||
|  |                     } | ||||||
|  |                 ) | ||||||
|  |             } | ||||||
|             buttons.push(button) |             buttons.push(button) | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -26,7 +26,7 @@ export class DownloadPanel extends Toggle { | ||||||
|         currentBounds: UIEventSource<BBox> |         currentBounds: UIEventSource<BBox> | ||||||
|     }) { |     }) { | ||||||
|         const t = Translations.t.general.download |         const t = Translations.t.general.download | ||||||
|         const name = State.state.layoutToUse.id |         const name = state.layoutToUse.id | ||||||
| 
 | 
 | ||||||
|         const includeMetaToggle = new CheckBoxes([t.includeMetaData]) |         const includeMetaToggle = new CheckBoxes([t.includeMetaData]) | ||||||
|         const metaisIncluded = includeMetaToggle.GetValue().map((selected) => selected.length > 0) |         const metaisIncluded = includeMetaToggle.GetValue().map((selected) => selected.length > 0) | ||||||
|  |  | ||||||
|  | @ -20,6 +20,7 @@ import FilteredLayer from "../../Models/FilteredLayer" | ||||||
| import CopyrightPanel from "./CopyrightPanel" | import CopyrightPanel from "./CopyrightPanel" | ||||||
| import FeaturePipeline from "../../Logic/FeatureSource/FeaturePipeline" | import FeaturePipeline from "../../Logic/FeatureSource/FeaturePipeline" | ||||||
| import PrivacyPolicy from "./PrivacyPolicy" | import PrivacyPolicy from "./PrivacyPolicy" | ||||||
|  | import Hotkeys from "../Base/Hotkeys" | ||||||
| 
 | 
 | ||||||
| export default class FullWelcomePaneWithTabs extends ScrollableFullScreen { | export default class FullWelcomePaneWithTabs extends ScrollableFullScreen { | ||||||
|     public static MoreThemesTabIndex = 1 |     public static MoreThemesTabIndex = 1 | ||||||
|  | @ -126,6 +127,7 @@ export default class FullWelcomePaneWithTabs extends ScrollableFullScreen { | ||||||
|                     osmcha_link: Utils.OsmChaLinkFor(7), |                     osmcha_link: Utils.OsmChaLinkFor(7), | ||||||
|                 }), |                 }), | ||||||
|                 "<br/>Version " + Constants.vNumber, |                 "<br/>Version " + Constants.vNumber, | ||||||
|  |                 Hotkeys.generateDocumentationDynamic(), | ||||||
|             ]).SetClass("link-underline"), |             ]).SetClass("link-underline"), | ||||||
|         }) |         }) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
							
								
								
									
										129
									
								
								UI/BigComponents/GeolocationControl.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										129
									
								
								UI/BigComponents/GeolocationControl.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,129 @@ | ||||||
|  | import { VariableUiElement } from "../Base/VariableUIElement" | ||||||
|  | import Svg from "../../Svg" | ||||||
|  | import { UIEventSource } from "../../Logic/UIEventSource" | ||||||
|  | import GeoLocationHandler from "../../Logic/Actors/GeoLocationHandler" | ||||||
|  | import { BBox } from "../../Logic/BBox" | ||||||
|  | import Loc from "../../Models/Loc" | ||||||
|  | import Hotkeys from "../Base/Hotkeys" | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Displays an icon depending on the state of the geolocation. | ||||||
|  |  * Will set the 'lock' if clicked twice | ||||||
|  |  */ | ||||||
|  | export class GeolocationControl extends VariableUiElement { | ||||||
|  |     constructor( | ||||||
|  |         geolocationHandler: GeoLocationHandler, | ||||||
|  |         state: { | ||||||
|  |             locationControl: UIEventSource<Loc> | ||||||
|  |             currentBounds: UIEventSource<BBox> | ||||||
|  |         } | ||||||
|  |     ) { | ||||||
|  |         const lastClick = new UIEventSource<Date>(undefined) | ||||||
|  |         const lastClickWithinThreeSecs = lastClick.map((lastClick) => { | ||||||
|  |             if (lastClick === undefined) { | ||||||
|  |                 return false | ||||||
|  |             } | ||||||
|  |             const timeDiff = (new Date().getTime() - lastClick.getTime()) / 1000 | ||||||
|  |             return timeDiff <= 3 | ||||||
|  |         }) | ||||||
|  |         const geolocationState = geolocationHandler.geolocationState | ||||||
|  |         super( | ||||||
|  |             geolocationState.permission.map( | ||||||
|  |                 (permission) => { | ||||||
|  |                     if (permission === "denied") { | ||||||
|  |                         return Svg.location_refused_svg() | ||||||
|  |                     } | ||||||
|  |                     if (geolocationState.isLocked.data) { | ||||||
|  |                         return Svg.location_locked_svg() | ||||||
|  |                     } | ||||||
|  | 
 | ||||||
|  |                     if (geolocationState.currentGPSLocation.data === undefined) { | ||||||
|  |                         if (permission === "prompt") { | ||||||
|  |                             return Svg.location_empty_svg() | ||||||
|  |                         } | ||||||
|  |                         // Position not yet found, but permission is either requested or granted: we spin to indicate activity
 | ||||||
|  |                         const icon = !geolocationHandler.mapHasMoved.data | ||||||
|  |                             ? Svg.location_svg() | ||||||
|  |                             : Svg.location_empty_svg() | ||||||
|  |                         return icon | ||||||
|  |                             .SetClass("cursor-wait") | ||||||
|  |                             .SetStyle("animation: spin 4s linear infinite;") | ||||||
|  |                     } | ||||||
|  | 
 | ||||||
|  |                     // We have a location, so we show a dot in the center
 | ||||||
|  | 
 | ||||||
|  |                     if ( | ||||||
|  |                         lastClickWithinThreeSecs.data && | ||||||
|  |                         geolocationState.permission.data === "granted" | ||||||
|  |                     ) { | ||||||
|  |                         return Svg.location_unlocked_svg() | ||||||
|  |                     } | ||||||
|  | 
 | ||||||
|  |                     // We have a location, so we show a dot in the center
 | ||||||
|  |                     return Svg.location_svg() | ||||||
|  |                 }, | ||||||
|  |                 [ | ||||||
|  |                     geolocationState.currentGPSLocation, | ||||||
|  |                     geolocationState.isLocked, | ||||||
|  |                     geolocationHandler.mapHasMoved, | ||||||
|  |                     lastClickWithinThreeSecs, | ||||||
|  |                 ] | ||||||
|  |             ) | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |         async function handleClick() { | ||||||
|  |             if (geolocationState.permission.data !== "granted") { | ||||||
|  |                 await geolocationState.requestPermission() | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             if (geolocationState.isLocked.data === true) { | ||||||
|  |                 // Unlock
 | ||||||
|  |                 geolocationState.isLocked.setData(false) | ||||||
|  |                 return | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             if (geolocationState.currentGPSLocation.data === undefined) { | ||||||
|  |                 // No location is known yet, not much we can do
 | ||||||
|  |                 return | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             // A location _is_ known! Let's move to this location
 | ||||||
|  |             const currentLocation = geolocationState.currentGPSLocation.data | ||||||
|  |             const inBounds = state.currentBounds.data.contains([ | ||||||
|  |                 currentLocation.longitude, | ||||||
|  |                 currentLocation.latitude, | ||||||
|  |             ]) | ||||||
|  |             geolocationHandler.MoveMapToCurrentLocation() | ||||||
|  |             if (inBounds) { | ||||||
|  |                 const lc = state.locationControl.data | ||||||
|  |                 state.locationControl.setData({ | ||||||
|  |                     ...lc, | ||||||
|  |                     zoom: lc.zoom + 3, | ||||||
|  |                 }) | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             if (lastClickWithinThreeSecs.data && geolocationState.permission.data === "granted") { | ||||||
|  |                 geolocationState.isLocked.setData(true) | ||||||
|  |                 lastClick.setData(undefined) | ||||||
|  |                 return | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             lastClick.setData(new Date()) | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         this.onClick(handleClick) | ||||||
|  |         Hotkeys.RegisterHotkey( | ||||||
|  |             { nomod: "L" }, | ||||||
|  |             "Pan the map to the current location or zoom the map to the current location. Requests geopermission", | ||||||
|  |             handleClick | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |         lastClick.addCallbackAndRunD((_) => { | ||||||
|  |             window.setTimeout(() => { | ||||||
|  |                 if (lastClickWithinThreeSecs.data) { | ||||||
|  |                     lastClick.ping() | ||||||
|  |                 } | ||||||
|  |             }, 500) | ||||||
|  |         }) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -13,7 +13,7 @@ import { VariableUiElement } from "../Base/VariableUIElement" | ||||||
| import FeatureInfoBox from "../Popup/FeatureInfoBox" | import FeatureInfoBox from "../Popup/FeatureInfoBox" | ||||||
| import CopyrightPanel from "./CopyrightPanel" | import CopyrightPanel from "./CopyrightPanel" | ||||||
| import FeaturePipelineState from "../../Logic/State/FeaturePipelineState" | import FeaturePipelineState from "../../Logic/State/FeaturePipelineState" | ||||||
| import { FixedUiElement } from "../Base/FixedUiElement" | import Hotkeys from "../Base/Hotkeys" | ||||||
| 
 | 
 | ||||||
| export default class LeftControls extends Combine { | export default class LeftControls extends Combine { | ||||||
|     constructor( |     constructor( | ||||||
|  | @ -73,7 +73,7 @@ export default class LeftControls extends Combine { | ||||||
|             guiState.downloadControlIsOpened.setData(true) |             guiState.downloadControlIsOpened.setData(true) | ||||||
|         ) |         ) | ||||||
| 
 | 
 | ||||||
|         const downloadButtonn = new Toggle( |         const downloadButton = new Toggle( | ||||||
|             toggledDownload, |             toggledDownload, | ||||||
|             undefined, |             undefined, | ||||||
|             state.featureSwitchEnableExport.map( |             state.featureSwitchEnableExport.map( | ||||||
|  | @ -94,11 +94,20 @@ export default class LeftControls extends Combine { | ||||||
|         const toggledFilter = new MapControlButton(Svg.layers_svg()).onClick(() => |         const toggledFilter = new MapControlButton(Svg.layers_svg()).onClick(() => | ||||||
|             guiState.filterViewIsOpened.setData(true) |             guiState.filterViewIsOpened.setData(true) | ||||||
|         ) |         ) | ||||||
|  |         state.featureSwitchFilter.addCallbackAndRun((f) => { | ||||||
|  |             Hotkeys.RegisterHotkey( | ||||||
|  |                 { nomod: "B" }, | ||||||
|  |                 "Opens the Background, layers and filters panel", | ||||||
|  |                 () => { | ||||||
|  |                     guiState.filterViewIsOpened.setData(!guiState.filterViewIsOpened.data) | ||||||
|  |                 } | ||||||
|  |             ) | ||||||
|  |         }) | ||||||
| 
 | 
 | ||||||
|         const filterButton = new Toggle(toggledFilter, undefined, state.featureSwitchFilter) |         const filterButton = new Toggle(toggledFilter, undefined, state.featureSwitchFilter) | ||||||
| 
 | 
 | ||||||
|         const mapSwitch = new Toggle( |         const mapSwitch = new Toggle( | ||||||
|             new BackgroundMapSwitch(state, state.backgroundLayer), |             new BackgroundMapSwitch(state, state.backgroundLayer, { enableHotkeys: true }), | ||||||
|             undefined, |             undefined, | ||||||
|             state.featureSwitchBackgroundSelection |             state.featureSwitchBackgroundSelection | ||||||
|         ) |         ) | ||||||
|  | @ -120,7 +129,7 @@ export default class LeftControls extends Combine { | ||||||
|             state.featureSwitchWelcomeMessage |             state.featureSwitchWelcomeMessage | ||||||
|         ) |         ) | ||||||
| 
 | 
 | ||||||
|         super([currentViewAction, filterButton, downloadButtonn, copyright, mapSwitch]) |         super([currentViewAction, filterButton, downloadButton, copyright, mapSwitch]) | ||||||
| 
 | 
 | ||||||
|         this.SetClass("flex flex-col") |         this.SetClass("flex flex-col") | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -92,13 +92,13 @@ export default class MoreScreen extends Combine { | ||||||
| 
 | 
 | ||||||
|         if (onMainScreen) { |         if (onMainScreen) { | ||||||
|             search.focus() |             search.focus() | ||||||
|  |             document.addEventListener("keydown", function (event) { | ||||||
|  |                 if (event.ctrlKey && event.code === "KeyF") { | ||||||
|  |                     search.focus() | ||||||
|  |                     event.preventDefault() | ||||||
|  |                 } | ||||||
|  |             }) | ||||||
|         } |         } | ||||||
|         document.addEventListener("keydown", function (event) { |  | ||||||
|             if (event.ctrlKey && event.code === "KeyF") { |  | ||||||
|                 search.focus() |  | ||||||
|                 event.preventDefault() |  | ||||||
|             } |  | ||||||
|         }) |  | ||||||
| 
 | 
 | ||||||
|         const searchBar = new Combine([ |         const searchBar = new Combine([ | ||||||
|             Svg.search_svg().SetClass("w-8"), |             Svg.search_svg().SetClass("w-8"), | ||||||
|  |  | ||||||
|  | @ -5,18 +5,16 @@ import GeoLocationHandler from "../../Logic/Actors/GeoLocationHandler" | ||||||
| import Svg from "../../Svg" | import Svg from "../../Svg" | ||||||
| import MapState from "../../Logic/State/MapState" | import MapState from "../../Logic/State/MapState" | ||||||
| import FeaturePipeline from "../../Logic/FeatureSource/FeaturePipeline" | import FeaturePipeline from "../../Logic/FeatureSource/FeaturePipeline" | ||||||
| import { Utils } from "../../Utils" |  | ||||||
| import { TagUtils } from "../../Logic/Tags/TagUtils" |  | ||||||
| import { BBox } from "../../Logic/BBox" |  | ||||||
| import { OsmFeature } from "../../Models/OsmFeature" |  | ||||||
| import LevelSelector from "./LevelSelector" | import LevelSelector from "./LevelSelector" | ||||||
|  | import { GeolocationControl } from "./GeolocationControl" | ||||||
| 
 | 
 | ||||||
| export default class RightControls extends Combine { | export default class RightControls extends Combine { | ||||||
|     constructor(state: MapState & { featurePipeline: FeaturePipeline }) { |     constructor( | ||||||
|         const geolocatioHandler = new GeoLocationHandler(state) |         state: MapState & { featurePipeline: FeaturePipeline }, | ||||||
| 
 |         geolocationHandler: GeoLocationHandler | ||||||
|  |     ) { | ||||||
|         const geolocationButton = new Toggle( |         const geolocationButton = new Toggle( | ||||||
|             new MapControlButton(geolocatioHandler, { |             new MapControlButton(new GeolocationControl(geolocationHandler, state), { | ||||||
|                 dontStyle: true, |                 dontStyle: true, | ||||||
|             }).SetClass("p-1"), |             }).SetClass("p-1"), | ||||||
|             undefined, |             undefined, | ||||||
|  |  | ||||||
|  | @ -1,6 +1,5 @@ | ||||||
| import { UIEventSource } from "../../Logic/UIEventSource" | import { UIEventSource } from "../../Logic/UIEventSource" | ||||||
| import { Translation } from "../i18n/Translation" | import { Translation } from "../i18n/Translation" | ||||||
| import { VariableUiElement } from "../Base/VariableUIElement" |  | ||||||
| import Svg from "../../Svg" | import Svg from "../../Svg" | ||||||
| import { TextField } from "../Input/TextField" | import { TextField } from "../Input/TextField" | ||||||
| import { Geocoding } from "../../Logic/Osm/Geocoding" | import { Geocoding } from "../../Logic/Osm/Geocoding" | ||||||
|  | @ -10,6 +9,7 @@ import Combine from "../Base/Combine" | ||||||
| import Locale from "../i18n/Locale" | import Locale from "../i18n/Locale" | ||||||
| 
 | 
 | ||||||
| export default class SearchAndGo extends Combine { | export default class SearchAndGo extends Combine { | ||||||
|  |     private readonly _searchField: TextField | ||||||
|     constructor(state: { leafletMap: UIEventSource<any>; selectedElement?: UIEventSource<any> }) { |     constructor(state: { leafletMap: UIEventSource<any>; selectedElement?: UIEventSource<any> }) { | ||||||
|         const goButton = Svg.search_ui().SetClass("w-8 h-8 full-rounded border-black float-right") |         const goButton = Svg.search_ui().SetClass("w-8 h-8 full-rounded border-black float-right") | ||||||
| 
 | 
 | ||||||
|  | @ -74,6 +74,11 @@ export default class SearchAndGo extends Combine { | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         searchField.enterPressed.addCallback(runSearch) |         searchField.enterPressed.addCallback(runSearch) | ||||||
|  |         this._searchField = searchField | ||||||
|         goButton.onClick(runSearch) |         goButton.onClick(runSearch) | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     focus() { | ||||||
|  |         this._searchField.focus() | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -80,7 +80,7 @@ export default class ShareScreen extends Combine { | ||||||
|             includeCurrentBackground.GetValue().map( |             includeCurrentBackground.GetValue().map( | ||||||
|                 (includeBG) => { |                 (includeBG) => { | ||||||
|                     if (includeBG) { |                     if (includeBG) { | ||||||
|                         return "background=" + currentLayer.data.id |                         return "background=" + currentLayer.data?.id | ||||||
|                     } else { |                     } else { | ||||||
|                         return null |                         return null | ||||||
|                     } |                     } | ||||||
|  | @ -168,7 +168,7 @@ export default class ShareScreen extends Combine { | ||||||
|                 return `<span class='literal-code iframe-code-block'>
 |                 return `<span class='literal-code iframe-code-block'>
 | ||||||
|                          <iframe src="${url}" allow="geolocation" width="100%" height="100%" style="min-width: 250px; min-height: 250px" title="${ |                          <iframe src="${url}" allow="geolocation" width="100%" height="100%" style="min-width: 250px; min-height: 250px" title="${ | ||||||
|                     layout.title?.txt ?? "MapComplete" |                     layout.title?.txt ?? "MapComplete" | ||||||
|                 } with MapComplete"></iframe>  |                 } with MapComplete"></iframe> | ||||||
|                     </span>` |                     </span>` | ||||||
|             }) |             }) | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|  | @ -29,6 +29,10 @@ import Img from "./Base/Img" | ||||||
| import UserInformationPanel from "./BigComponents/UserInformation" | import UserInformationPanel from "./BigComponents/UserInformation" | ||||||
| import { LoginToggle } from "./Popup/LoginButton" | import { LoginToggle } from "./Popup/LoginButton" | ||||||
| import { FixedUiElement } from "./Base/FixedUiElement" | import { FixedUiElement } from "./Base/FixedUiElement" | ||||||
|  | import GeoLocationHandler from "../Logic/Actors/GeoLocationHandler" | ||||||
|  | import { GeoLocationState } from "../Logic/State/GeoLocationState" | ||||||
|  | import Hotkeys from "./Base/Hotkeys" | ||||||
|  | import AvailableBaseLayers from "../Logic/Actors/AvailableBaseLayers" | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * The default MapComplete GUI initializer |  * The default MapComplete GUI initializer | ||||||
|  | @ -38,10 +42,14 @@ import { FixedUiElement } from "./Base/FixedUiElement" | ||||||
| export default class DefaultGUI { | export default class DefaultGUI { | ||||||
|     private readonly guiState: DefaultGuiState |     private readonly guiState: DefaultGuiState | ||||||
|     private readonly state: FeaturePipelineState |     private readonly state: FeaturePipelineState | ||||||
|  |     private readonly geolocationHandler: GeoLocationHandler | undefined | ||||||
| 
 | 
 | ||||||
|     constructor(state: FeaturePipelineState, guiState: DefaultGuiState) { |     constructor(state: FeaturePipelineState, guiState: DefaultGuiState) { | ||||||
|         this.state = state |         this.state = state | ||||||
|         this.guiState = guiState |         this.guiState = guiState | ||||||
|  |         if (this.state.featureSwitchGeolocation.data) { | ||||||
|  |             this.geolocationHandler = new GeoLocationHandler(new GeoLocationState(), state) | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public setup() { |     public setup() { | ||||||
|  | @ -55,6 +63,14 @@ export default class DefaultGUI { | ||||||
|             Utils.LoadCustomCss(this.state.layoutToUse.customCss) |             Utils.LoadCustomCss(this.state.layoutToUse.customCss) | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  |         Hotkeys.RegisterHotkey( | ||||||
|  |             { shift: "O" }, | ||||||
|  |             "Switch to default Mapnik-OpenStreetMap background", | ||||||
|  |             () => { | ||||||
|  |                 this.state.backgroundLayer.setData(AvailableBaseLayers.osmCarto) | ||||||
|  |             } | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|         Utils.downloadJson("./service-worker-version") |         Utils.downloadJson("./service-worker-version") | ||||||
|             .then((data) => console.log("Service worker", data)) |             .then((data) => console.log("Service worker", data)) | ||||||
|             .catch((_) => console.log("Service worker not active")) |             .catch((_) => console.log("Service worker not active")) | ||||||
|  | @ -122,7 +138,7 @@ export default class DefaultGUI { | ||||||
|                     .SetStyle("left: calc( 50% - 15px )") // This is a bit hacky, yes I know!
 |                     .SetStyle("left: calc( 50% - 15px )") // This is a bit hacky, yes I know!
 | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             new StrayClickHandler( |             StrayClickHandler.construct( | ||||||
|                 state, |                 state, | ||||||
|                 addNewPoint, |                 addNewPoint, | ||||||
|                 hasPresets ? new AddNewMarker(state.filteredLayers) : noteMarker |                 hasPresets ? new AddNewMarker(state.filteredLayers) : noteMarker | ||||||
|  | @ -145,6 +161,9 @@ export default class DefaultGUI { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private SetupMap() { |     private SetupMap() { | ||||||
|  |         if (Utils.runningFromConsole) { | ||||||
|  |             return | ||||||
|  |         } | ||||||
|         const state = this.state |         const state = this.state | ||||||
|         const guiState = this.guiState |         const guiState = this.guiState | ||||||
| 
 | 
 | ||||||
|  | @ -232,18 +251,27 @@ export default class DefaultGUI { | ||||||
|             .AttachTo("on-small-screen") |             .AttachTo("on-small-screen") | ||||||
| 
 | 
 | ||||||
|         new Combine([ |         new Combine([ | ||||||
|             Toggle.If(state.featureSwitchSearch, () => |             Toggle.If(state.featureSwitchSearch, () => { | ||||||
|                 new SearchAndGo(state).SetClass( |                 const search = new SearchAndGo(state).SetClass( | ||||||
|                     "shadow rounded-full h-min w-full overflow-hidden sm:max-w-sm pointer-events-auto" |                     "shadow rounded-full h-min w-full overflow-hidden sm:max-w-sm pointer-events-auto" | ||||||
|                 ) |                 ) | ||||||
|             ), |                 Hotkeys.RegisterHotkey( | ||||||
|  |                     { ctrl: "F" }, | ||||||
|  |                     "Select the search bar to search locations", | ||||||
|  |                     () => { | ||||||
|  |                         search.focus() | ||||||
|  |                     } | ||||||
|  |                 ) | ||||||
|  | 
 | ||||||
|  |                 return search | ||||||
|  |             }), | ||||||
|         ]).AttachTo("top-right") |         ]).AttachTo("top-right") | ||||||
| 
 | 
 | ||||||
|         new LeftControls(state, guiState).AttachTo("bottom-left") |         new LeftControls(state, guiState).AttachTo("bottom-left") | ||||||
|         new RightControls(state).AttachTo("bottom-right") |         new RightControls(state, this.geolocationHandler).AttachTo("bottom-right") | ||||||
| 
 | 
 | ||||||
|         new CenterMessageBox(state).AttachTo("centermessage") |         new CenterMessageBox(state).AttachTo("centermessage") | ||||||
|         document.getElementById("centermessage").classList.add("pointer-events-none") |         document?.getElementById("centermessage")?.classList?.add("pointer-events-none") | ||||||
| 
 | 
 | ||||||
|         // We have to ping the welcomeMessageIsOpened and other isOpened-stuff to activate the FullScreenMessage if needed
 |         // We have to ping the welcomeMessageIsOpened and other isOpened-stuff to activate the FullScreenMessage if needed
 | ||||||
|         for (const state of guiState.allFullScreenStates) { |         for (const state of guiState.allFullScreenStates) { | ||||||
|  |  | ||||||
|  | @ -22,8 +22,10 @@ import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig" | ||||||
| import { ElementStorage } from "../../Logic/ElementStorage" | import { ElementStorage } from "../../Logic/ElementStorage" | ||||||
| import BaseLayer from "../../Models/BaseLayer" | import BaseLayer from "../../Models/BaseLayer" | ||||||
| import FilteredLayer from "../../Models/FilteredLayer" | import FilteredLayer from "../../Models/FilteredLayer" | ||||||
|  | import BaseUIElement from "../BaseUIElement" | ||||||
|  | import { VariableUiElement } from "../Base/VariableUIElement" | ||||||
| 
 | 
 | ||||||
| export default class SplitRoadWizard extends Toggle { | export default class SplitRoadWizard extends Combine { | ||||||
|     // @ts-ignore
 |     // @ts-ignore
 | ||||||
|     private static splitLayerStyling = new LayerConfig( |     private static splitLayerStyling = new LayerConfig( | ||||||
|         split_point, |         split_point, | ||||||
|  | @ -63,6 +65,106 @@ export default class SplitRoadWizard extends Toggle { | ||||||
| 
 | 
 | ||||||
|         // Toggle variable between show split button and map
 |         // Toggle variable between show split button and map
 | ||||||
|         const splitClicked = new UIEventSource<boolean>(false) |         const splitClicked = new UIEventSource<boolean>(false) | ||||||
|  | 
 | ||||||
|  |         const leafletMap = new UIEventSource<BaseUIElement>( | ||||||
|  |             SplitRoadWizard.setupMapComponent(id, splitPoints, state) | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |         // Toggle between splitmap
 | ||||||
|  |         const splitButton = new SubtleButton( | ||||||
|  |             Svg.scissors_ui().SetStyle("height: 1.5rem; width: auto"), | ||||||
|  |             new Toggle( | ||||||
|  |                 t.splitAgain.Clone().SetClass("text-lg font-bold"), | ||||||
|  |                 t.inviteToSplit.Clone().SetClass("text-lg font-bold"), | ||||||
|  |                 hasBeenSplit | ||||||
|  |             ) | ||||||
|  |         ) | ||||||
|  |         splitButton.onClick(() => { | ||||||
|  |             splitClicked.setData(true) | ||||||
|  |         }) | ||||||
|  | 
 | ||||||
|  |         // Only show the splitButton if logged in, else show login prompt
 | ||||||
|  |         const loginBtn = t.loginToSplit | ||||||
|  |             .Clone() | ||||||
|  |             .onClick(() => state.osmConnection.AttemptLogin()) | ||||||
|  |             .SetClass("login-button-friendly") | ||||||
|  |         const splitToggle = new Toggle(splitButton, loginBtn, state.osmConnection.isLoggedIn) | ||||||
|  | 
 | ||||||
|  |         // Save button
 | ||||||
|  |         const saveButton = new Button(t.split.Clone(), async () => { | ||||||
|  |             hasBeenSplit.setData(true) | ||||||
|  |             splitClicked.setData(false) | ||||||
|  |             const splitAction = new SplitAction( | ||||||
|  |                 id, | ||||||
|  |                 splitPoints.data.map((ff) => ff.feature.geometry.coordinates), | ||||||
|  |                 { | ||||||
|  |                     theme: state?.layoutToUse?.id, | ||||||
|  |                 }, | ||||||
|  |                 5, | ||||||
|  |                 (coordinates) => { | ||||||
|  |                     state.allElements.ContainingFeatures.get(id).geometry["coordinates"] = | ||||||
|  |                         coordinates | ||||||
|  |                 } | ||||||
|  |             ) | ||||||
|  |             await state.changes.applyAction(splitAction) | ||||||
|  |             // We throw away the old map and splitpoints, and create a new map from scratch
 | ||||||
|  |             splitPoints.setData([]) | ||||||
|  |             leafletMap.setData(SplitRoadWizard.setupMapComponent(id, splitPoints, state)) | ||||||
|  |         }) | ||||||
|  | 
 | ||||||
|  |         saveButton.SetClass("btn btn-primary mr-3") | ||||||
|  |         const disabledSaveButton = new Button("Split", undefined) | ||||||
|  |         disabledSaveButton.SetClass("btn btn-disabled mr-3") | ||||||
|  |         // Only show the save button if there are split points defined
 | ||||||
|  |         const saveToggle = new Toggle( | ||||||
|  |             disabledSaveButton, | ||||||
|  |             saveButton, | ||||||
|  |             splitPoints.map((data) => data.length === 0) | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |         const cancelButton = Translations.t.general.cancel | ||||||
|  |             .Clone() // Not using Button() element to prevent full width button
 | ||||||
|  |             .SetClass("btn btn-secondary mr-3") | ||||||
|  |             .onClick(() => { | ||||||
|  |                 splitPoints.setData([]) | ||||||
|  |                 splitClicked.setData(false) | ||||||
|  |             }) | ||||||
|  | 
 | ||||||
|  |         cancelButton.SetClass("btn btn-secondary block") | ||||||
|  | 
 | ||||||
|  |         const splitTitle = new Title(t.splitTitle) | ||||||
|  | 
 | ||||||
|  |         const mapView = new Combine([ | ||||||
|  |             splitTitle, | ||||||
|  |             new VariableUiElement(leafletMap), | ||||||
|  |             new Combine([cancelButton, saveToggle]).SetClass("flex flex-row"), | ||||||
|  |         ]) | ||||||
|  |         mapView.SetClass("question") | ||||||
|  |         super([ | ||||||
|  |             Toggle.If(hasBeenSplit, () => | ||||||
|  |                 t.hasBeenSplit.Clone().SetClass("font-bold thanks block w-full") | ||||||
|  |             ), | ||||||
|  |             new Toggle(mapView, splitToggle, splitClicked), | ||||||
|  |         ]) | ||||||
|  |         this.dialogIsOpened = splitClicked | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private static setupMapComponent( | ||||||
|  |         id: string, | ||||||
|  |         splitPoints: UIEventSource<{ feature: any; freshness: Date }[]>, | ||||||
|  |         state: { | ||||||
|  |             filteredLayers: UIEventSource<FilteredLayer[]> | ||||||
|  |             backgroundLayer: UIEventSource<BaseLayer> | ||||||
|  |             featureSwitchIsTesting: UIEventSource<boolean> | ||||||
|  |             featureSwitchIsDebugging: UIEventSource<boolean> | ||||||
|  |             featureSwitchShowAllQuestions: UIEventSource<boolean> | ||||||
|  |             osmConnection: OsmConnection | ||||||
|  |             featureSwitchUserbadge: UIEventSource<boolean> | ||||||
|  |             changes: Changes | ||||||
|  |             layoutToUse: LayoutConfig | ||||||
|  |             allElements: ElementStorage | ||||||
|  |         } | ||||||
|  |     ): BaseUIElement { | ||||||
|         // Load the road with given id on the minimap
 |         // Load the road with given id on the minimap
 | ||||||
|         const roadElement = state.allElements.ContainingFeatures.get(id) |         const roadElement = state.allElements.ContainingFeatures.get(id) | ||||||
| 
 | 
 | ||||||
|  | @ -96,7 +198,6 @@ export default class SplitRoadWizard extends Toggle { | ||||||
|             layerToShow: SplitRoadWizard.splitLayerStyling, |             layerToShow: SplitRoadWizard.splitLayerStyling, | ||||||
|             state, |             state, | ||||||
|         }) |         }) | ||||||
| 
 |  | ||||||
|         /** |         /** | ||||||
|          * Handles a click on the overleaf map. |          * Handles a click on the overleaf map. | ||||||
|          * Finds the closest intersection with the road and adds a point there, ready to confirm the cut. |          * Finds the closest intersection with the road and adds a point there, ready to confirm the cut. | ||||||
|  | @ -137,67 +238,6 @@ export default class SplitRoadWizard extends Toggle { | ||||||
|                 onMapClick([mouseEvent.latlng.lng, mouseEvent.latlng.lat]) |                 onMapClick([mouseEvent.latlng.lng, mouseEvent.latlng.lat]) | ||||||
|             }) |             }) | ||||||
|         ) |         ) | ||||||
| 
 |         return miniMap | ||||||
|         // Toggle between splitmap
 |  | ||||||
|         const splitButton = new SubtleButton( |  | ||||||
|             Svg.scissors_ui().SetStyle("height: 1.5rem; width: auto"), |  | ||||||
|             t.inviteToSplit.Clone().SetClass("text-lg font-bold") |  | ||||||
|         ) |  | ||||||
|         splitButton.onClick(() => { |  | ||||||
|             splitClicked.setData(true) |  | ||||||
|         }) |  | ||||||
| 
 |  | ||||||
|         // Only show the splitButton if logged in, else show login prompt
 |  | ||||||
|         const loginBtn = t.loginToSplit |  | ||||||
|             .Clone() |  | ||||||
|             .onClick(() => state.osmConnection.AttemptLogin()) |  | ||||||
|             .SetClass("login-button-friendly") |  | ||||||
|         const splitToggle = new Toggle(splitButton, loginBtn, state.osmConnection.isLoggedIn) |  | ||||||
| 
 |  | ||||||
|         // Save button
 |  | ||||||
|         const saveButton = new Button(t.split.Clone(), () => { |  | ||||||
|             hasBeenSplit.setData(true) |  | ||||||
|             state.changes.applyAction( |  | ||||||
|                 new SplitAction( |  | ||||||
|                     id, |  | ||||||
|                     splitPoints.data.map((ff) => ff.feature.geometry.coordinates), |  | ||||||
|                     { |  | ||||||
|                         theme: state?.layoutToUse?.id, |  | ||||||
|                     } |  | ||||||
|                 ) |  | ||||||
|             ) |  | ||||||
|         }) |  | ||||||
| 
 |  | ||||||
|         saveButton.SetClass("btn btn-primary mr-3") |  | ||||||
|         const disabledSaveButton = new Button("Split", undefined) |  | ||||||
|         disabledSaveButton.SetClass("btn btn-disabled mr-3") |  | ||||||
|         // Only show the save button if there are split points defined
 |  | ||||||
|         const saveToggle = new Toggle( |  | ||||||
|             disabledSaveButton, |  | ||||||
|             saveButton, |  | ||||||
|             splitPoints.map((data) => data.length === 0) |  | ||||||
|         ) |  | ||||||
| 
 |  | ||||||
|         const cancelButton = Translations.t.general.cancel |  | ||||||
|             .Clone() // Not using Button() element to prevent full width button
 |  | ||||||
|             .SetClass("btn btn-secondary mr-3") |  | ||||||
|             .onClick(() => { |  | ||||||
|                 splitPoints.setData([]) |  | ||||||
|                 splitClicked.setData(false) |  | ||||||
|             }) |  | ||||||
| 
 |  | ||||||
|         cancelButton.SetClass("btn btn-secondary block") |  | ||||||
| 
 |  | ||||||
|         const splitTitle = new Title(t.splitTitle) |  | ||||||
| 
 |  | ||||||
|         const mapView = new Combine([ |  | ||||||
|             splitTitle, |  | ||||||
|             miniMap, |  | ||||||
|             new Combine([cancelButton, saveToggle]).SetClass("flex flex-row"), |  | ||||||
|         ]) |  | ||||||
|         mapView.SetClass("question") |  | ||||||
|         const confirm = new Toggle(mapView, splitToggle, splitClicked) |  | ||||||
|         super(t.hasBeenSplit.Clone(), confirm, hasBeenSplit) |  | ||||||
|         this.dialogIsOpened = splitClicked |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,7 +1,7 @@ | ||||||
| import { Utils } from "../../Utils" | import { Utils } from "../../Utils" | ||||||
| import { Feature } from "geojson" | import { Feature } from "geojson" | ||||||
| import { Point } from "@turf/turf" | import { Point } from "@turf/turf" | ||||||
| import { GeoLocationPointProperties } from "../../Logic/Actors/GeoLocationHandler" | import { GeoLocationPointProperties } from "../../Logic/State/GeoLocationState" | ||||||
| import UploadTraceToOsmUI from "../BigComponents/UploadTraceToOsmUI" | import UploadTraceToOsmUI from "../BigComponents/UploadTraceToOsmUI" | ||||||
| import { SpecialVisualization } from "../SpecialVisualization" | import { SpecialVisualization } from "../SpecialVisualization" | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -17,7 +17,10 @@ export default class ShowDataLayer { | ||||||
|      */ |      */ | ||||||
|     constructor(options: ShowDataLayerOptions & { layerToShow: LayerConfig }) { |     constructor(options: ShowDataLayerOptions & { layerToShow: LayerConfig }) { | ||||||
|         if (ShowDataLayer.actualContstructor === undefined) { |         if (ShowDataLayer.actualContstructor === undefined) { | ||||||
|             throw "Show data layer is called, but it isn't initialized yet. Call ` ShowDataLayer.actualContstructor = (options => new ShowDataLayerImplementation(options)) ` somewhere, e.g. in your init" |             console.error( | ||||||
|  |                 "Show data layer is called, but it isn't initialized yet. Call ` ShowDataLayer.actualContstructor = (options => new ShowDataLayerImplementation(options)) ` somewhere, e.g. in your init" | ||||||
|  |             ) | ||||||
|  |             return | ||||||
|         } |         } | ||||||
|         ShowDataLayer.actualContstructor(options) |         ShowDataLayer.actualContstructor(options) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -143,6 +143,99 @@ | ||||||
|       "override": { |       "override": { | ||||||
|         "condition": "amenity!=bank" |         "condition": "amenity!=bank" | ||||||
|       } |       } | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "id": "cash_out", | ||||||
|  |       "question": { | ||||||
|  |         "en": "Can you withdraw cash from this ATM?" | ||||||
|  |       }, | ||||||
|  |       "mappings": [ | ||||||
|  |         { | ||||||
|  |           "if": "cash_out=", | ||||||
|  |           "then": { | ||||||
|  |             "en": "You can withdraw cash from this ATM" | ||||||
|  |           }, | ||||||
|  |           "hideInAnswer": true | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |           "if": "cash_out=yes", | ||||||
|  |           "then": { | ||||||
|  |             "en": "You can withdraw cash from this ATM" | ||||||
|  |           } | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |           "if": "cash_out=no", | ||||||
|  |           "then": { | ||||||
|  |             "en": "You cannot withdraw cash from this ATM" | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       ] | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "id": "cash_in", | ||||||
|  |       "question": { | ||||||
|  |         "en": "Can you deposit cash into this ATM?" | ||||||
|  |       }, | ||||||
|  |       "mappings": [ | ||||||
|  |         { | ||||||
|  |           "if": "cash_in=", | ||||||
|  |           "then": { | ||||||
|  |             "en": "You probably cannot deposit cash into this ATM" | ||||||
|  |           }, | ||||||
|  |           "hideInAnswer": true | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |           "if": "cash_in=yes", | ||||||
|  |           "then": { | ||||||
|  |             "en": "You can deposit cash into this ATM" | ||||||
|  |           } | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |           "if": "cash_in=no", | ||||||
|  |           "then": { | ||||||
|  |             "en": "You cannot deposit cash into this ATM" | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       ] | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "id": "speech_output", | ||||||
|  |       "question": { | ||||||
|  |         "en": "Does this ATM have speech output for visually impaired users?" | ||||||
|  |       }, | ||||||
|  |       "mappings": [ | ||||||
|  |         { | ||||||
|  |           "if": "speech_output=yes", | ||||||
|  |           "then": { | ||||||
|  |             "en": "This ATM has speech output, usually available through a headphone jack" | ||||||
|  |           } | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |           "if": "speech_output=no", | ||||||
|  |           "then": { | ||||||
|  |             "en": "This ATM does not have speech output" | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       ] | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "id": "speech_output_language", | ||||||
|  |       "condition": "speech_output=yes", | ||||||
|  |       "render": { | ||||||
|  |         "special": { | ||||||
|  |           "type": "language_chooser", | ||||||
|  |           "key": "speech_output", | ||||||
|  |           "question": { | ||||||
|  |             "en": "In which languages does this ATM have speech output?" | ||||||
|  |           }, | ||||||
|  |           "render_list_item": { | ||||||
|  |             "en": "This ATM has speech output in {language():font-bold}" | ||||||
|  |           }, | ||||||
|  |           "render_single_language": { | ||||||
|  |             "en": "This ATM has speech output in {language():font-bold}" | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|     } |     } | ||||||
|   ], |   ], | ||||||
|   "mapRendering": [ |   "mapRendering": [ | ||||||
|  | @ -153,5 +246,19 @@ | ||||||
|         "centroid" |         "centroid" | ||||||
|       ] |       ] | ||||||
|     } |     } | ||||||
|  |   ], | ||||||
|  |   "filter": [ | ||||||
|  |     "open_now", | ||||||
|  |     { | ||||||
|  |       "id": "speech_output", | ||||||
|  |       "options": [ | ||||||
|  |         { | ||||||
|  |           "question": { | ||||||
|  |             "en": "With speech output" | ||||||
|  |           }, | ||||||
|  |           "osmTags": "speech_output=yes" | ||||||
|  |         } | ||||||
|  |       ] | ||||||
|  |     } | ||||||
|   ] |   ] | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -2,7 +2,11 @@ | ||||||
|   { |   { | ||||||
|     "path": "bench.svg", |     "path": "bench.svg", | ||||||
|     "license": "CC0", |     "license": "CC0", | ||||||
|     "authors": [], |     "authors": [ | ||||||
|     "sources": [] |       "Tobias Zwick" | ||||||
|  |     ], | ||||||
|  |     "sources": [ | ||||||
|  |       "https://github.com/streetcomplete/StreetComplete/" | ||||||
|  |     ] | ||||||
|   } |   } | ||||||
| ] | ] | ||||||
|  | @ -314,7 +314,10 @@ | ||||||
|             "cs": "Zde si můžete půjčit cyklistické přilby" |             "cs": "Zde si můžete půjčit cyklistické přilby" | ||||||
|           } |           } | ||||||
|         } |         } | ||||||
|       ] |       ], | ||||||
|  |       "condition": { | ||||||
|  |         "and": [] | ||||||
|  |       } | ||||||
|     }, |     }, | ||||||
|     { |     { | ||||||
|       "id": "rental_types", |       "id": "rental_types", | ||||||
|  | @ -443,7 +446,9 @@ | ||||||
|             "key": "capacity:bicycle_type", |             "key": "capacity:bicycle_type", | ||||||
|             "type": "pnat" |             "type": "pnat" | ||||||
|           }, |           }, | ||||||
|           "condition": "rental~.*bicycle_type.*" |           "condition": {"and":  | ||||||
|  |             ["rental~.*bicycle_type.*"] | ||||||
|  |           } | ||||||
|         } |         } | ||||||
|       ] |       ] | ||||||
|     } |     } | ||||||
|  | @ -554,4 +559,4 @@ | ||||||
|       } |       } | ||||||
|     ] |     ] | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -487,7 +487,14 @@ | ||||||
|         } |         } | ||||||
|       ] |       ] | ||||||
|     }, |     }, | ||||||
|     "bicycle_rental.*bicycle_rental", |     { | ||||||
|  |       "builtin":  "bicycle_rental.*bicycle_rental", | ||||||
|  |       "override": { | ||||||
|  |         "condition": { | ||||||
|  |           "and+": ["service:bicycle:rental=yes"] | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|     { |     { | ||||||
|       "id": "bike_repair_second-hand-bikes", |       "id": "bike_repair_second-hand-bikes", | ||||||
|       "question": { |       "question": { | ||||||
|  | @ -783,4 +790,4 @@ | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|   ] |   ] | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -23,7 +23,7 @@ | ||||||
|     "osmTags": "emergency=defibrillator" |     "osmTags": "emergency=defibrillator" | ||||||
|   }, |   }, | ||||||
|   "calculatedTags": [ |   "calculatedTags": [ | ||||||
|     "_days_since_last_survey=Math.floor(new Date() - new Date(feat.properties['survey:date'])/(1000*60*60*24))", |     "_days_since_last_survey=Math.floor((new Date() - new Date(feat.properties['survey:date']))/(1000*60*60*24))", | ||||||
|     "_recently_surveyed=Number(feat.properties._days_since_last_survey) <= 90" |     "_recently_surveyed=Number(feat.properties._days_since_last_survey) <= 90" | ||||||
|   ], |   ], | ||||||
|   "minzoom": 12, |   "minzoom": 12, | ||||||
|  |  | ||||||
|  | @ -15,6 +15,7 @@ | ||||||
|     "osmTags": "amenity=parking_space" |     "osmTags": "amenity=parking_space" | ||||||
|   }, |   }, | ||||||
|   "tagRenderings": [ |   "tagRenderings": [ | ||||||
|  |     "images", | ||||||
|     { |     { | ||||||
|       "id": "type", |       "id": "type", | ||||||
|       "question": { |       "question": { | ||||||
|  |  | ||||||
							
								
								
									
										14
									
								
								assets/layers/recycling/fluorescent_tubes.svg
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								assets/layers/recycling/fluorescent_tubes.svg
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,14 @@ | ||||||
|  | <?xml version="1.0" encoding="UTF-8"?> | ||||||
|  | <svg width="700pt" height="700pt" version="1.1" viewBox="0 0 700 700" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> | ||||||
|  |  <g> | ||||||
|  |   <path d="m552.4 181.67c-1.5312 0-3.0078-0.60156-4.1016-1.6953l-98.328-98.328c-1.0938-1.0938-1.6953-2.5156-1.6953-4.1016 0-1.5312 0.60156-3.0078 1.6953-4.1016l27.344-27.344c3.5547-3.5547 8.3125-5.5234 13.344-5.5234h0.054688c5.0312 0 9.7344 1.9688 13.289 5.4688l79.844 79.844c7.3828 7.3828 7.3828 19.359 0 26.688l-27.344 27.344c-1.0938 1.1484-2.5703 1.75-4.1016 1.75zm-91.055-104.12 91.055 91.055 23.68-23.68c3.1172-3.1172 3.1172-8.1484 0-11.266l-79.844-79.844c-1.4766-1.4766-3.4453-2.2969-5.5234-2.2969-2.1328 0-4.1016 0.82031-5.6328 2.3516z"/> | ||||||
|  |   <path d="m240.19 484.04-94.227-94.227 313.8-313.8 94.281 94.281zm-78.805-94.227 78.805 78.805 298.32-298.32-78.75-78.859z"/> | ||||||
|  |   <path d="m209.34 519.42c-5.0312 0-9.7891-1.9688-13.344-5.5234l-79.844-79.844c-3.5547-3.5547-5.5234-8.3125-5.5234-13.344s1.9688-9.7891 5.5234-13.344l27.344-27.344c1.0938-1.0938 2.5156-1.6953 4.1016-1.6953 1.5312 0 3.0078 0.60156 4.1016 1.6953l98.383 98.383c1.0938 1.0938 1.6953 2.5156 1.6953 4.1016 0 1.5312-0.60156 3.0078-1.6953 4.1016l-27.344 27.344c-3.6641 3.5-8.3672 5.4688-13.398 5.4688zm-61.797-128.08-23.68 23.734c-1.5312 1.5312-2.3516 3.5-2.3516 5.6328 0 2.1328 0.82031 4.1016 2.3516 5.6328l79.844 79.844c1.5312 1.5312 3.5 2.3516 5.6328 2.3516s4.1016-0.82031 5.6328-2.3516l23.68-23.68z"/> | ||||||
|  |   <path d="m522.1 79.68-19.523-19.523 21.711-21.711c5.4141-5.4141 14.219-5.4141 19.578 0 2.5703 2.5703 4.0469 6.0156 4.0469 9.6797 0 3.7188-1.4219 7.2188-4.0469 9.8438zm-4.1016-19.523 4.1016 4.1016 14-14c0.54688-0.54687 0.875-1.3125 0.875-2.0781 0-0.4375-0.10937-1.2578-0.82031-1.9688-1.1484-1.1484-3.0078-1.1484-4.1562-0.054688z"/> | ||||||
|  |   <path d="m569.84 127.37-19.523-19.523 21.711-21.711c5.3594-5.3047 14.109-5.3047 19.469 0 2.625 2.5703 4.1016 6.0156 4.1016 9.7344s-1.4219 7.2188-4.0469 9.8438zm-4.1016-19.523 4.1016 4.1016 13.945-13.945c0.54688-0.54688 0.875-1.3125 0.875-2.0781 0-0.4375-0.10937-1.2578-0.82031-1.9688-1.1484-1.1484-2.9531-1.1484-4.1562 0z"/> | ||||||
|  |   <path d="m165.92 525.6c-3.7188 0-7.1641-1.4219-9.7891-4.0469-2.5703-2.5703-4.0469-6.0156-4.0469-9.6797 0-3.7188 1.4219-7.2188 4.0469-9.8438l21.711-21.711 19.523 19.523-21.711 21.711c-2.5703 2.5703-6.0156 4.0469-9.6797 4.0469h-0.054687zm11.922-29.859-13.945 13.945c-0.54688 0.54688-0.875 1.3125-0.875 2.0781 0 0.4375 0.10937 1.2578 0.82031 1.9688 0.60156 0.60156 1.3125 0.875 2.0781 0.875 0.4375 0 1.2578-0.10938 1.9688-0.82031l14-14z"/> | ||||||
|  |   <path d="m118.23 477.91c-3.7188 0-7.1641-1.4219-9.7891-4.0469-2.5703-2.5703-4.0469-6.0156-4.0469-9.6797 0-3.7188 1.4219-7.2188 4.0469-9.8438l21.711-21.711 19.523 19.523-21.711 21.711c-2.5703 2.5703-6.0156 4.0469-9.6797 4.0469h-0.054687zm11.922-29.859-13.945 13.945c-0.54688 0.54688-0.875 1.3125-0.875 2.0781 0 0.4375 0.10937 1.2578 0.82031 1.9688 0.60156 0.60156 1.3125 0.875 2.0781 0.875 0.4375 0 1.2578-0.10938 1.9688-0.82031l14-14z"/> | ||||||
|  |   <path d="m297.07 284.02 7.7344 7.7344-16.781 16.781-7.7344-7.7344z"/> | ||||||
|  |   <path d="m370.96 210.19 7.7344 7.7344-57.656 57.656-7.7344-7.7344z"/> | ||||||
|  |  </g> | ||||||
|  | </svg> | ||||||
| After Width: | Height: | Size: 3.1 KiB | 
|  | @ -60,6 +60,17 @@ | ||||||
|       "https://github.com/streetcomplete/StreetComplete/blob/master/res/graphics/recycling%20icons/engine_oil.svg" |       "https://github.com/streetcomplete/StreetComplete/blob/master/res/graphics/recycling%20icons/engine_oil.svg" | ||||||
|     ] |     ] | ||||||
|   }, |   }, | ||||||
|  |   { | ||||||
|  |     "path": "fluorescent_tubes.svg", | ||||||
|  |     "license": "CC-BY", | ||||||
|  |     "authors": [ | ||||||
|  |       "Noun Project", | ||||||
|  |       "shashank singh" | ||||||
|  |     ], | ||||||
|  |     "sources": [ | ||||||
|  |       "https://thenounproject.com/icon/tube-fluorescent-light-3756518/" | ||||||
|  |     ] | ||||||
|  |   }, | ||||||
|   { |   { | ||||||
|     "path": "garden_waste.svg", |     "path": "garden_waste.svg", | ||||||
|     "license": "CC-BY-SA", |     "license": "CC-BY-SA", | ||||||
|  | @ -91,6 +102,17 @@ | ||||||
|       "https://github.com/streetcomplete/StreetComplete/blob/master/res/graphics/recycling%20icons/glass_bottles.svg" |       "https://github.com/streetcomplete/StreetComplete/blob/master/res/graphics/recycling%20icons/glass_bottles.svg" | ||||||
|     ] |     ] | ||||||
|   }, |   }, | ||||||
|  |   { | ||||||
|  |     "path": "light_bulbs.svg", | ||||||
|  |     "license": "CC0", | ||||||
|  |     "authors": [ | ||||||
|  |       "OpenClipArt", | ||||||
|  |       "fabiovaleggia" | ||||||
|  |     ], | ||||||
|  |     "sources": [ | ||||||
|  |       "https://openclipart.org/detail/175842/basic-light-bulb" | ||||||
|  |     ] | ||||||
|  |   }, | ||||||
|   { |   { | ||||||
|     "path": "newspaper.svg", |     "path": "newspaper.svg", | ||||||
|     "license": "CC-BY-SA", |     "license": "CC-BY-SA", | ||||||
|  |  | ||||||
							
								
								
									
										159
									
								
								assets/layers/recycling/light_bulbs.svg
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										159
									
								
								assets/layers/recycling/light_bulbs.svg
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,159 @@ | ||||||
|  | <?xml version="1.0" encoding="UTF-8"?> | ||||||
|  | <svg xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://web.resource.org/cc/" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:svg="http://www.w3.org/2000/svg" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:ns1="http://sozi.baierouge.fr" id="svg2" viewBox="0 0 801 801" version="1.0"> | ||||||
|  |   <defs id="defs4"> | ||||||
|  |     <linearGradient id="linearGradient3187"> | ||||||
|  |       <stop id="stop3189" stop-color="#fff" offset="0"/> | ||||||
|  |       <stop id="stop3191" stop-color="#fff" stop-opacity="0" offset="1"/> | ||||||
|  |     </linearGradient> | ||||||
|  |     <linearGradient id="linearGradient3175"> | ||||||
|  |       <stop id="stop3177" offset="0"/> | ||||||
|  |       <stop id="stop3179" stop-opacity="0" offset="1"/> | ||||||
|  |     </linearGradient> | ||||||
|  |     <filter id="filter4712" y="-.18408" width="1.4494" height="1.3682" x="-.22472"> | ||||||
|  |       <feGaussianBlur id="feGaussianBlur4714" stdDeviation="44.016276"/> | ||||||
|  |     </filter> | ||||||
|  |     <radialGradient id="radialGradient5307" gradientUnits="userSpaceOnUse" cy="891.75" cx="995.08" gradientTransform="matrix(1.7827 1.8967e-7 -4.1911e-8 .39392 -778.86 540.47)" r="52.236"> | ||||||
|  |       <stop id="stop4522" offset="0"/> | ||||||
|  |       <stop id="stop4524" stop-opacity="0" offset="1"/> | ||||||
|  |     </radialGradient> | ||||||
|  |     <radialGradient id="radialGradient5309" xlink:href="#linearGradient3175" gradientUnits="userSpaceOnUse" cy="717.11" cx="509.03" gradientTransform="matrix(.73643 8.7347e-7 -.0000011447 .96512 152.63 23.39)" r="64.5"/> | ||||||
|  |     <radialGradient id="radialGradient5311" xlink:href="#linearGradient3187" gradientUnits="userSpaceOnUse" cy="716.36" cx="512.8" gradientTransform="matrix(.48062 -9.5378e-8 1.5922e-7 .80233 316.04 140.73)" r="64.5"/> | ||||||
|  |     <radialGradient id="radialGradient5313" xlink:href="#linearGradient3175" gradientUnits="userSpaceOnUse" cy="717.11" cx="590.74" gradientTransform="matrix(.45736 -3.8247e-7 8.0707e-7 .96512 319.32 23.391)" r="64.5"/> | ||||||
|  |     <radialGradient id="radialGradient5315" xlink:href="#linearGradient3187" gradientUnits="userSpaceOnUse" cy="716.36" cx="598.74" gradientTransform="matrix(.30233 3.7735e-8 -1.0014e-7 .80233 428.99 140.73)" r="64.5"/> | ||||||
|  |     <radialGradient id="radialGradient5317" xlink:href="#linearGradient3175" gradientUnits="userSpaceOnUse" cy="717.11" cx="753.17" gradientTransform="matrix(.68217 -4.4781e-7 6.3355e-7 .96512 122.21 23.391)" r="64.5"/> | ||||||
|  |     <radialGradient id="radialGradient5319" xlink:href="#linearGradient3175" gradientUnits="userSpaceOnUse" cy="717.11" cx="509.03" gradientTransform="matrix(.72462 8.7347e-7 -.0000011263 .96512 -12.477 15.53)" r="64.5"/> | ||||||
|  |     <radialGradient id="radialGradient5321" xlink:href="#linearGradient3187" gradientUnits="userSpaceOnUse" cy="716.36" cx="512.8" gradientTransform="matrix(.47291 -9.5378e-8 1.5667e-7 .80233 148.31 132.87)" r="64.5"/> | ||||||
|  |     <radialGradient id="radialGradient5323" xlink:href="#linearGradient3175" gradientUnits="userSpaceOnUse" cy="717.11" cx="590.74" gradientTransform="matrix(.45108 .0069435 -.014382 .96501 161.23 11.508)" r="64.5"/> | ||||||
|  |     <radialGradient id="radialGradient5325" xlink:href="#linearGradient3187" gradientUnits="userSpaceOnUse" cy="716.36" cx="598.74" gradientTransform="matrix(.29748 3.7735e-8 -9.8537e-8 .80233 259.44 132.87)" r="64.5"/> | ||||||
|  |     <radialGradient id="radialGradient5327" xlink:href="#linearGradient3175" gradientUnits="userSpaceOnUse" cy="717.11" cx="753.17" gradientTransform="matrix(.67123 -4.4781e-7 6.2339e-7 .96512 -42.412 15.531)" r="64.5"/> | ||||||
|  |     <radialGradient id="radialGradient5329" xlink:href="#linearGradient3175" gradientUnits="userSpaceOnUse" cy="717.11" cx="509.03" gradientTransform="matrix(.73642 -.0042366 .0030467 .52973 -17.205 458.52)" r="64.5"/> | ||||||
|  |     <radialGradient id="radialGradient5331" xlink:href="#linearGradient3187" gradientUnits="userSpaceOnUse" cy="716.36" cx="512.8" gradientTransform="matrix(.48062 .000392 -.00034729 .42594 148.97 528.69)" r="64.5"/> | ||||||
|  |     <radialGradient id="radialGradient5333" xlink:href="#linearGradient3175" gradientUnits="userSpaceOnUse" cy="717.11" cx="590.74" gradientTransform="matrix(.45736 -.00082768 .00093239 .51491 151.73 463.54)" r="64.5"/> | ||||||
|  |     <radialGradient id="radialGradient5335" xlink:href="#linearGradient3187" gradientUnits="userSpaceOnUse" cy="716.36" cx="598.74" gradientTransform="matrix(.30232 -.0012309 .0017686 .43441 260.37 520.55)" r="64.5"/> | ||||||
|  |     <radialGradient id="radialGradient5337" xlink:href="#linearGradient3175" gradientUnits="userSpaceOnUse" cy="717.11" cx="753.17" gradientTransform="matrix(.68199 .015520 -.011766 .51703 -36.145 446.9)" r="64.5"/> | ||||||
|  |     <radialGradient id="radialGradient5389" xlink:href="#linearGradient3175" gradientUnits="userSpaceOnUse" cy="717.11" cx="509.03" gradientTransform="matrix(.73643 8.7347e-7 -.0000011447 .96512 -18.367 115.54)" r="64.5"/> | ||||||
|  |     <radialGradient id="radialGradient5391" xlink:href="#linearGradient3187" gradientUnits="userSpaceOnUse" cy="716.36" cx="512.8" gradientTransform="matrix(.48062 -9.5378e-8 1.5922e-7 .80233 145.62 232.86)" r="64.5"/> | ||||||
|  |     <radialGradient id="radialGradient5393" xlink:href="#linearGradient3175" gradientUnits="userSpaceOnUse" cy="717.11" cx="590.74" gradientTransform="matrix(.45736 -3.8247e-7 8.0707e-7 .96512 149.43 115.52)" r="64.5"/> | ||||||
|  |     <radialGradient id="radialGradient5395" xlink:href="#linearGradient3187" gradientUnits="userSpaceOnUse" cy="716.36" cx="598.74" gradientTransform="matrix(.30233 3.7735e-8 -1.0014e-7 .80233 258.58 232.89)" r="64.5"/> | ||||||
|  |     <radialGradient id="radialGradient5397" xlink:href="#linearGradient3175" gradientUnits="userSpaceOnUse" cy="717.11" cx="753.17" gradientTransform="matrix(.68217 -4.4781e-7 6.3355e-7 .96512 -47.791 115.52)" r="64.5"/> | ||||||
|  |     <radialGradient id="radialGradient2544" gradientUnits="userSpaceOnUse" cy="422.33" cx="1345.1" gradientTransform="matrix(.99128 -.0053344 .027760 5.1585 -.84309 -1759.8)" r="195.88"> | ||||||
|  |       <stop id="stop4646" stop-color="#66676a" offset="0"/> | ||||||
|  |       <stop id="stop4648" stop-color="#66676a" stop-opacity="0" offset="1"/> | ||||||
|  |     </radialGradient> | ||||||
|  |   </defs> | ||||||
|  |   <g id="layer1" transform="translate(-821.7 -56.241)"> | ||||||
|  |     <path id="path3446" d="m1235.6 199.12c-108.2 0-195.9 87.75-195.9 195.87 0 74.78 62.3 147.32 87.6 172.57 22.8 22.85 5.8 81.09 50.5 109.49l115.6 0.29c41.2-30.97 24.8-84.81 52.7-109.57 29.2-21.4 85.3-98 85.3-172.78 0-108.12-87.7-195.87-195.8-195.87z" stroke="#7f868f" stroke-linecap="round" fill="#fff"/> | ||||||
|  |     <path id="path3448" opacity=".29365" filter="url(#filter4712)" d="m1235.6 199.12c-108.2 0-195.9 87.75-195.9 195.87 0 74.78 65.5 149.92 89.3 172.79 24.4 23.39-0.3 77.64 48.8 109.56h115.6c41.2-30.97 24.2-86.49 52.7-109.57 28.6-23.07 85.3-98 85.3-172.78 0-108.12-87.7-195.87-195.8-195.87z" fill="url(#radialGradient2544)"/> | ||||||
|  |     <path id="path3450" fill="#fff" d="m1229.7 212.8c-1.1 0-2.2 0.01-3.3 0.03 88.9 11.45 157.6 87.42 157.6 179.34 0 69.04-51.9 139.76-78.8 159.52-25.8 22.85-10.6 72.56-48.7 101.15h-82.3c0.7 0.5 1.5 0.99 2.2 1.48h106.7c38.1-28.6 22.8-78.31 48.7-101.16 26.9-19.76 78.8-90.48 78.8-159.52 0-99.82-81-180.84-180.9-180.84z"/> | ||||||
|  |     <g id="g3452" stroke-linecap="round" transform="translate(828.16 .0000025)"> | ||||||
|  |       <g id="g3454" stroke="#000" transform="matrix(1.0301 0 0 .93888 -654.71 -74.398)"> | ||||||
|  |         <path id="path3456" d="m1078 891.75a51.736 20.077 0 1 1 -103.52 0 51.736 20.077 0 1 1 103.52 0z" fill="#000051" transform="translate(.77236 -.36379)"/> | ||||||
|  |         <path id="path3458" d="m1078 891.75a51.736 20.077 0 1 1 -103.52 0 51.736 20.077 0 1 1 103.52 0z" fill="url(#radialGradient5307)" transform="translate(.77236 -.36379)"/> | ||||||
|  |       </g> | ||||||
|  |       <g id="g3460" transform="translate(-174.44 -28.235)"> | ||||||
|  |         <path id="path3462" d="m525.49 705.74h110.02c4.98 0 8.99 1.76 8.99 3.94v11.61c0 2.19-4.01 3.95-8.99 3.95h-110.02c-4.98 0-8.99-1.76-8.99-3.95v-11.61c0-2.18 4.01-3.94 8.99-3.94z" stroke="#000" fill="url(#radialGradient5309)"/> | ||||||
|  |         <path id="path3464" d="m525.49 705.74h110.02c4.98 0 8.99 1.76 8.99 3.94v11.61c0 2.19-4.01 3.95-8.99 3.95h-110.02c-4.98 0-8.99-1.76-8.99-3.95v-11.61c0-2.18 4.01-3.94 8.99-3.94z" stroke="#000" fill="url(#radialGradient5311)"/> | ||||||
|  |         <path id="path3466" d="m525.49 705.74h110.02c4.98 0 8.99 1.76 8.99 3.94v11.61c0 2.19-4.01 3.95-8.99 3.95h-110.02c-4.98 0-8.99-1.76-8.99-3.95v-11.61c0-2.18 4.01-3.94 8.99-3.94z" stroke="#000" fill="url(#radialGradient5313)"/> | ||||||
|  |         <path id="path3468" d="m525.49 705.74h110.02c4.98 0 8.99 1.76 8.99 3.94v11.61c0 2.19-4.01 3.95-8.99 3.95h-110.02c-4.98 0-8.99-1.76-8.99-3.95v-11.61c0-2.18 4.01-3.94 8.99-3.94z" stroke="#000" fill="url(#radialGradient5315)"/> | ||||||
|  |         <path id="path3470" d="m525.49 705.74h110.02c4.98 0 8.99 1.76 8.99 3.94v11.61c0 2.19-4.01 3.95-8.99 3.95h-110.02c-4.98 0-8.99-1.76-8.99-3.95v-11.61c0-2.18 4.01-3.94 8.99-3.94z" stroke="#565352" fill="url(#radialGradient5317)"/> | ||||||
|  |       </g> | ||||||
|  |       <path id="path3472" d="m354.4 697.88h108.26c4.9 0 2.26 0.47 2.54-1.06l0.74 6.46c0 2.19-3.31 3.47-8.21 3.47l-103.33 10.63c-4.9 0-8.84-1.76-8.84-3.95v-11.61c0-2.18 3.94-3.94 8.84-3.94z" stroke="#000" stroke-width=".99195" fill="url(#radialGradient5319)"/> | ||||||
|  |       <path id="path3474" d="m354.4 697.88l107.78-0.42c4.9 0 2.91 0.67 2.71-0.8l0.99 6.64c0 2.18-3.4 3.29-8.3 3.29l-103.18 10.79c-4.9 0-8.84-1.76-8.84-3.95v-11.61c0-2.18 3.94-3.94 8.84-3.94z" stroke="#000" stroke-width=".99195" fill="url(#radialGradient5321)"/> | ||||||
|  |       <path id="path3476" d="m354.4 697.88h108.26c4.9 0 2.69-3.72 2.69-1.53l0.6 7.34c0 2.18-3.54 2.39-8.44 2.39l-103.11 11.3c-4.9 0-8.84-1.76-8.84-3.95v-11.61c0-2.18 3.94-3.94 8.84-3.94z" stroke="#000" stroke-width=".99195" fill="url(#radialGradient5323)"/> | ||||||
|  |       <path id="path3478" d="m354.4 697.88h108.26c4.9 0 3-3.72 3-1.53l-0.34 6.66c0 2.19-3.81 3.23-8.51 3.74l-102.41 10.63c-4.9 0-8.84-1.76-8.84-3.95v-11.61c0-2.18 3.94-3.94 8.84-3.94z" stroke="#000" stroke-width=".99195" fill="url(#radialGradient5325)"/> | ||||||
|  |       <path id="path3480" d="m354.4 697.88h108.26c3.82-1.57 3.3-3.87 3.3-1.69l0.16 5.98c0 2.19-3.95 3.95-8.85 3.95l-102.87 11.26c-4.9 0-8.84-1.76-8.84-3.95v-11.61c0-2.18 3.94-3.94 8.84-3.94z" stroke="#4e4b4b" stroke-width=".99195" fill="url(#radialGradient5327)"/> | ||||||
|  |       <g id="g3482" transform="matrix(.95599 0 0 1 -333.55 -40.073)"> | ||||||
|  |         <path id="path3484" d="m825.87 742.05c-0.21 0.01-0.43 0.03-0.65 0.04-0.3 0-0.59 0.01-0.88 0.03l-103.91 6.97c-2.79 0.2-4.79 0.88-6.12 1.78-1.6 0.96-2.53 2.3-2.47 3.97 0.05 1.46 1.11 2.58 2.66 3.37 1.44 0.81 3.4 1.29 5.56 1.31-2.27 0.21-3.98 0.74-5.22 1.44-0.04 0.02-0.09 0.04-0.12 0.06-0.1 0.05-0.2 0.11-0.29 0.16l-0.03 0.03-0.09 0.06c-0.16 0.1-0.32 0.21-0.47 0.32-0.02 0.01-0.04 0.04-0.06 0.06-1.26 0.92-1.99 2.09-1.94 3.56 0.04 1.29 0.87 2.33 2.13 3.09 0.01 0.01 0.01 0.03 0.03 0.04 0.15 0.09 0.33 0.19 0.5 0.28 1.18 0.67 2.72 1.07 4.43 1.22 0.07 0 0.13 0.02 0.19 0.03 0.19 0.02 0.37 0.02 0.56 0.03h0.47c0.42 0.02 0.86-0.02 1.28-0.03h0.07c0.12-0.01 0.25 0.01 0.37 0l103.91-6.97c1.2-0.08 2.32-0.32 3.31-0.69 3.21-0.98 5.09-2.7 4.81-5.09-0.27-2.43-2.54-4.05-5.75-4.5-0.02-0.01-0.04 0-0.06 0-0.05-0.01-0.1-0.03-0.16-0.03-0.03-0.01-0.06 0-0.09 0-0.29-0.04-0.57-0.08-0.87-0.1-0.23-0.01-0.49-0.02-0.72-0.03 0.89-0.09 1.73-0.27 2.5-0.53 3.41-0.97 5.44-2.75 5.15-5.22-0.28-2.48-2.64-4.09-5.97-4.5-0.62-0.09-1.32-0.16-2.06-0.16zm-0.65 21.04c-0.3 0-0.59 0.01-0.88 0.03l-103.91 6.97c-2.73 0.2-4.7 0.85-6.03 1.71-0.03 0.02-0.06 0.05-0.09 0.07l-0.09 0.06c-0.02 0.01-0.02 0.02-0.04 0.03-1.51 0.96-2.39 2.26-2.34 3.88 0.04 1.28 0.85 2.32 2.09 3.09 1.24 0.86 3.02 1.36 5 1.53 0.07 0.01 0.13 0.03 0.19 0.03 0.19 0.02 0.37 0.02 0.56 0.03-2.34 0.26-4.07 0.88-5.28 1.66l-0.09 0.06-0.09 0.06c-0.02 0.01-0.02 0.03-0.04 0.04-1.51 0.95-2.39 2.25-2.34 3.87 0.04 1.29 0.85 2.33 2.09 3.09 1.24 0.87 3.02 1.37 5 1.54 0.07 0 0.13 0.02 0.19 0.03 0.19 0.02 0.37 0.02 0.56 0.03h0.47c0.42 0.02 0.86-0.02 1.28-0.03h0.07c0.12-0.01 0.25 0.01 0.37 0l103.91-6.97c1.2-0.08 2.32-0.32 3.31-0.69 3.21-0.98 5.09-2.73 4.81-5.12-0.27-2.43-2.54-4.02-5.75-4.47-0.02-0.01-0.04 0-0.06 0-0.05-0.01-0.11-0.03-0.16-0.03-0.32-0.05-0.64-0.07-1-0.1-0.13-0.01-0.26-0.03-0.4-0.03 0.92-0.12 1.78-0.33 2.56-0.62 3.21-0.99 5.09-2.74 4.81-5.13-0.27-2.42-2.54-4.01-5.75-4.47-0.02 0-0.04 0.01-0.06 0-0.05-0.01-0.11-0.02-0.16-0.03-0.32-0.05-0.64-0.07-1-0.09-0.13-0.01-0.26-0.03-0.4-0.03-0.1-0.01-0.21 0-0.31 0-0.21-0.01-0.42-0.01-0.63 0-0.12-0.01-0.25-0.01-0.37 0zm0 21.15c-0.3 0-0.59 0.04-0.88 0.06l-8.69 0.6-95.22 6.34c-2.47 0.19-4.31 0.76-5.62 1.5-0.22 0.12-0.43 0.25-0.63 0.38-1.51 0.95-2.39 2.25-2.34 3.87 0.04 1.3 0.87 2.33 2.13 3.1 0.01 0 0.01 0.02 0.03 0.03 0.15 0.09 0.33 0.19 0.5 0.28 1.18 0.67 2.72 1.07 4.43 1.22 0.07 0 0.13 0.02 0.19 0.03 0.19 0.02 0.37 0.02 0.56 0.03h0.47c0.42 0.02 0.86-0.02 1.28-0.03h0.07c0.12-0.01 0.25 0.01 0.37 0l103.91-6.97c1.2-0.08 2.32-0.32 3.31-0.69 3.21-0.98 5.09-2.7 4.81-5.09-0.27-2.42-2.54-4.05-5.75-4.5-0.02-0.01-0.04 0-0.06 0-0.05-0.01-0.1-0.03-0.16-0.03-0.03-0.01-0.06 0-0.09 0-0.3-0.04-0.59-0.08-0.91-0.1-0.4-0.02-0.86-0.04-1.31-0.03h-0.4zm0.65 10.91c-0.17 0-0.35 0.02-0.53 0.03h-0.12c-0.17 0-0.34 0.02-0.5 0.03-0.13 0.01-0.25-0.01-0.38 0l-103.91 6.97c-2.72 0.2-4.67 0.86-6 1.72-0.03 0.02-0.08 0.04-0.12 0.06-1.6 0.96-2.53 2.3-2.47 3.97 0.05 1.46 1.11 2.59 2.66 3.37 1.23 0.7 2.83 1.13 4.62 1.25 0.31 0.03 0.62 0.06 0.94 0.07h0.94c0.14-0.01 0.29-0.02 0.43-0.03h0.07c0.12-0.01 0.25 0 0.37 0l103.91-6.97c1.06-0.07 2.07-0.3 2.97-0.6 3.41-0.96 5.44-2.75 5.15-5.22-0.31-2.71-3.1-4.43-6.93-4.62-0.35-0.02-0.73-0.03-1.1-0.03z" stroke="#000" stroke-width=".71131" fill="#fff"/> | ||||||
|  |         <g id="g3486" stroke-width=".73116" transform="matrix(.94643 0 0 1 255.03 -82.084)"> | ||||||
|  |           <g id="g3488" transform="translate(134.21)"> | ||||||
|  |             <path id="path3490" d="m357.51 831.16l109.79-6.96c4.98-0.32 9.6 1.57 9.97 4.61s-4.01 5.41-9.31 5.79l-109.79 6.97c-5.64 0.29-9.51-2.3-9.57-4.64-0.06-2.35 2.15-5.29 8.91-5.77z" stroke="#000" fill="url(#radialGradient5329)"/> | ||||||
|  |             <path id="path3492" d="m357.91 831.16l109.8-6.96c6.82-0.5 9.32 2.79 9.45 4.88 0.14 2.17-3.82 5.21-8.79 5.52l-109.79 6.97c-4.98 0.31-9.45-1.3-9.82-4.34s2.14-5.5 9.15-6.07z" stroke="#000" fill="url(#radialGradient5331)"/> | ||||||
|  |             <path id="path3494" d="m358.37 831.16l109.8-6.97c4.54-0.29 8.02 1.23 8.61 4.23 0.6 3.06-2.98 5.86-7.95 6.18l-109.8 6.96c-4.97 0.32-9.59-1.57-9.96-4.61-0.38-3.04 3.87-5.54 9.3-5.79z" stroke="#000" fill="url(#radialGradient5333)"/> | ||||||
|  |             <path id="path3496" d="m357.98 831.16l109.8-6.97c4.97-0.31 8.78 1.35 9.29 4.45 0.52 3.11-3.08 5.35-8.63 5.96l-109.8 6.96c-4.97 0.32-9.47-1.59-9.85-4.63-0.37-3.03 3.23-5.36 9.19-5.77z" stroke="#000" fill="url(#radialGradient5335)"/> | ||||||
|  |             <path id="path3498" d="m358.46 831.16l109.79-6.97c4.98-0.32 8.79 1.49 9.17 4.6s-3.1 5.13-8.5 5.8l-109.8 6.97c-4.97 0.32-10.55-1.31-10.67-4.65-0.13-3.42 4.03-5.41 10.01-5.75z" stroke="#565352" fill="url(#radialGradient5337)"/> | ||||||
|  |           </g> | ||||||
|  |           <g id="g3500" transform="translate(134.21 10.389)"> | ||||||
|  |             <path id="path3502" d="m357.51 831.16l109.79-6.96c4.98-0.32 9.6 1.57 9.97 4.61s-4.01 5.41-9.31 5.79l-109.79 6.97c-5.64 0.29-9.51-2.3-9.57-4.64-0.06-2.35 2.15-5.29 8.91-5.77z" stroke="#000" fill="url(#radialGradient5329)"/> | ||||||
|  |             <path id="path3504" d="m357.91 831.16l109.8-6.96c6.82-0.5 9.32 2.79 9.45 4.88 0.14 2.17-3.82 5.21-8.79 5.52l-109.79 6.97c-4.98 0.31-9.45-1.3-9.82-4.34s2.14-5.5 9.15-6.07z" stroke="#000" fill="url(#radialGradient5331)"/> | ||||||
|  |             <path id="path3506" d="m358.37 831.16l109.8-6.97c4.54-0.29 8.02 1.23 8.61 4.23 0.6 3.06-2.98 5.86-7.95 6.18l-109.8 6.96c-4.97 0.32-9.59-1.57-9.96-4.61-0.38-3.04 3.87-5.54 9.3-5.79z" stroke="#000" fill="url(#radialGradient5333)"/> | ||||||
|  |             <path id="path3508" d="m357.98 831.16l109.8-6.97c4.97-0.31 8.78 1.35 9.29 4.45 0.52 3.11-3.08 5.35-8.63 5.96l-109.8 6.96c-4.97 0.32-9.47-1.59-9.85-4.63-0.37-3.03 3.23-5.36 9.19-5.77z" stroke="#000" fill="url(#radialGradient5335)"/> | ||||||
|  |             <path id="path3510" d="m358.46 831.16l109.79-6.97c4.98-0.32 8.79 1.49 9.17 4.6s-3.1 5.13-8.5 5.8l-109.8 6.97c-4.97 0.32-10.55-1.31-10.67-4.65-0.13-3.42 4.03-5.41 10.01-5.75z" stroke="#565352" fill="url(#radialGradient5337)"/> | ||||||
|  |           </g> | ||||||
|  |           <g id="g3512" transform="translate(134.21 21.011)"> | ||||||
|  |             <path id="path3514" d="m357.51 831.16l109.79-6.96c4.98-0.32 9.6 1.57 9.97 4.61s-4.01 5.41-9.31 5.79l-109.79 6.97c-5.64 0.29-9.51-2.3-9.57-4.64-0.06-2.35 2.15-5.29 8.91-5.77z" stroke="#000" fill="url(#radialGradient5329)"/> | ||||||
|  |             <path id="path3516" d="m357.91 831.16l109.8-6.96c6.82-0.5 9.32 2.79 9.45 4.88 0.14 2.17-3.82 5.21-8.79 5.52l-109.79 6.97c-4.98 0.31-9.45-1.3-9.82-4.34s2.14-5.5 9.15-6.07z" stroke="#000" fill="url(#radialGradient5331)"/> | ||||||
|  |             <path id="path3518" d="m358.37 831.16l109.8-6.97c4.54-0.29 8.02 1.23 8.61 4.23 0.6 3.06-2.98 5.86-7.95 6.18l-109.8 6.96c-4.97 0.32-9.59-1.57-9.96-4.61-0.38-3.04 3.87-5.54 9.3-5.79z" stroke="#000" fill="url(#radialGradient5333)"/> | ||||||
|  |             <path id="path3520" d="m357.98 831.16l109.8-6.97c4.97-0.31 8.78 1.35 9.29 4.45 0.52 3.11-3.08 5.35-8.63 5.96l-109.8 6.96c-4.97 0.32-9.47-1.59-9.85-4.63-0.37-3.03 3.23-5.36 9.19-5.77z" stroke="#000" fill="url(#radialGradient5335)"/> | ||||||
|  |             <path id="path3522" d="m358.46 831.16l109.79-6.97c4.98-0.32 8.79 1.49 9.17 4.6s-3.1 5.13-8.5 5.8l-109.8 6.97c-4.97 0.32-10.55-1.31-10.67-4.65-0.13-3.42 4.03-5.41 10.01-5.75z" stroke="#565352" fill="url(#radialGradient5337)"/> | ||||||
|  |           </g> | ||||||
|  |           <g id="g3524" transform="translate(134.21 31.385)"> | ||||||
|  |             <path id="path3526" d="m357.51 831.16l109.79-6.96c4.98-0.32 9.6 1.57 9.97 4.61s-4.01 5.41-9.31 5.79l-109.79 6.97c-5.64 0.29-9.51-2.3-9.57-4.64-0.06-2.35 2.15-5.29 8.91-5.77z" stroke="#000" fill="url(#radialGradient5329)"/> | ||||||
|  |             <path id="path3528" d="m357.91 831.16l109.8-6.96c6.82-0.5 9.32 2.79 9.45 4.88 0.14 2.17-3.82 5.21-8.79 5.52l-109.79 6.97c-4.98 0.31-9.45-1.3-9.82-4.34s2.14-5.5 9.15-6.07z" stroke="#000" fill="url(#radialGradient5331)"/> | ||||||
|  |             <path id="path3530" d="m358.37 831.16l109.8-6.97c4.54-0.29 8.02 1.23 8.61 4.23 0.6 3.06-2.98 5.86-7.95 6.18l-109.8 6.96c-4.97 0.32-9.59-1.57-9.96-4.61-0.38-3.04 3.87-5.54 9.3-5.79z" stroke="#000" fill="url(#radialGradient5333)"/> | ||||||
|  |             <path id="path3532" d="m357.98 831.16l109.8-6.97c4.97-0.31 8.78 1.35 9.29 4.45 0.52 3.11-3.08 5.35-8.63 5.96l-109.8 6.96c-4.97 0.32-9.47-1.59-9.85-4.63-0.37-3.03 3.23-5.36 9.19-5.77z" stroke="#000" fill="url(#radialGradient5335)"/> | ||||||
|  |             <path id="path3534" d="m358.46 831.16l109.79-6.97c4.98-0.32 8.79 1.49 9.17 4.6s-3.1 5.13-8.5 5.8l-109.8 6.97c-4.97 0.32-10.55-1.31-10.67-4.65-0.13-3.42 4.03-5.41 10.01-5.75z" stroke="#565352" fill="url(#radialGradient5337)"/> | ||||||
|  |           </g> | ||||||
|  |           <g id="g3536" transform="translate(134.21 42.169)"> | ||||||
|  |             <path id="path3538" d="m357.51 831.16l109.79-6.96c4.98-0.32 9.6 1.57 9.97 4.61s-4.01 5.41-9.31 5.79l-109.79 6.97c-5.64 0.29-9.51-2.3-9.57-4.64-0.06-2.35 2.15-5.29 8.91-5.77z" stroke="#000" fill="url(#radialGradient5329)"/> | ||||||
|  |             <path id="path3540" d="m357.91 831.16l109.8-6.96c6.82-0.5 9.32 2.79 9.45 4.88 0.14 2.17-3.82 5.21-8.79 5.52l-109.79 6.97c-4.98 0.31-9.45-1.3-9.82-4.34s2.14-5.5 9.15-6.07z" stroke="#000" fill="url(#radialGradient5331)"/> | ||||||
|  |             <path id="path3542" d="m358.37 831.16l109.8-6.97c4.54-0.29 8.02 1.23 8.61 4.23 0.6 3.06-2.98 5.86-7.95 6.18l-109.8 6.96c-4.97 0.32-9.59-1.57-9.96-4.61-0.38-3.04 3.87-5.54 9.3-5.79z" stroke="#000" fill="url(#radialGradient5333)"/> | ||||||
|  |             <path id="path3544" d="m357.98 831.16l109.8-6.97c4.97-0.31 8.78 1.35 9.29 4.45 0.52 3.11-3.08 5.35-8.63 5.96l-109.8 6.96c-4.97 0.32-9.47-1.59-9.85-4.63-0.37-3.03 3.23-5.36 9.19-5.77z" stroke="#000" fill="url(#radialGradient5335)"/> | ||||||
|  |             <path id="path3546" d="m358.46 831.16l109.79-6.97c4.98-0.32 8.79 1.49 9.17 4.6s-3.1 5.13-8.5 5.8l-109.8 6.97c-4.97 0.32-10.55-1.31-10.67-4.65-0.13-3.42 4.03-5.41 10.01-5.75z" stroke="#565352" fill="url(#radialGradient5337)"/> | ||||||
|  |           </g> | ||||||
|  |           <g id="g3548" transform="translate(134.21 53.089)"> | ||||||
|  |             <path id="path3550" d="m357.51 831.16l109.79-6.96c4.98-0.32 9.6 1.57 9.97 4.61s-4.01 5.41-9.31 5.79l-109.79 6.97c-5.64 0.29-9.51-2.3-9.57-4.64-0.06-2.35 2.15-5.29 8.91-5.77z" stroke="#000" fill="url(#radialGradient5329)"/> | ||||||
|  |             <path id="path3552" d="m357.91 831.16l109.8-6.96c6.82-0.5 9.32 2.79 9.45 4.88 0.14 2.17-3.82 5.21-8.79 5.52l-109.79 6.97c-4.98 0.31-9.45-1.3-9.82-4.34s2.14-5.5 9.15-6.07z" stroke="#000" fill="url(#radialGradient5331)"/> | ||||||
|  |             <path id="path3554" d="m358.37 831.16l109.8-6.97c4.54-0.29 8.02 1.23 8.61 4.23 0.6 3.06-2.98 5.86-7.95 6.18l-109.8 6.96c-4.97 0.32-9.59-1.57-9.96-4.61-0.38-3.04 3.87-5.54 9.3-5.79z" stroke="#000" fill="url(#radialGradient5333)"/> | ||||||
|  |             <path id="path3556" d="m357.98 831.16l109.8-6.97c4.97-0.31 8.78 1.35 9.29 4.45 0.52 3.11-3.08 5.35-8.63 5.96l-109.8 6.96c-4.97 0.32-9.47-1.59-9.85-4.63-0.37-3.03 3.23-5.36 9.19-5.77z" stroke="#000" fill="url(#radialGradient5335)"/> | ||||||
|  |             <path id="path3558" d="m358.46 831.16l109.79-6.97c4.98-0.32 8.79 1.49 9.17 4.6s-3.1 5.13-8.5 5.8l-109.8 6.97c-4.97 0.32-10.55-1.31-10.67-4.65-0.13-3.42 4.03-5.41 10.01-5.75z" stroke="#565352" fill="url(#radialGradient5337)"/> | ||||||
|  |           </g> | ||||||
|  |         </g> | ||||||
|  |       </g> | ||||||
|  |       <g id="g3560" transform="matrix(.23216 0 0 .11037 265.8 686.62)"> | ||||||
|  |         <path id="path3562" d="m543.29 847.29h110.02c4.98 0 8.68 3.83 8.86 9.68s-3.42 9.21-8.86 9.82h-110.02c-4.98 0-10.44-3.69-10.35-9.95 0.09-6.39 4.37-9.62 10.35-9.55z" stroke="#565352" fill="#fff"/> | ||||||
|  |         <g id="g3564" transform="translate(187.8 49.415)"> | ||||||
|  |           <path id="path3566" d="m354.49 797.88h110.02c4.98 0 9.48 4.09 9.65 9.8 0.18 5.71-4.34 9.62-9.65 9.7h-110.02c-5.64-0.13-9.34-5.41-9.26-9.79 0.09-4.39 2.49-9.62 9.26-9.71z" stroke="#000" fill="url(#radialGradient5389)"/> | ||||||
|  |           <path id="path3568" d="m355.07 797.86h110.02c6.83-0.12 9.13 6.31 9.13 10.24 0 4.06-4.15 9.26-9.13 9.26h-110.02c-4.98 0-9.34-3.54-9.52-9.26-0.18-5.71 2.49-10.02 9.52-10.24z" stroke="#000" fill="url(#radialGradient5391)"/> | ||||||
|  |           <path id="path3570" d="m355.6 797.87h110.02c4.55 0 7.93 3.25 8.33 8.91 0.41 5.79-3.35 10.59-8.33 10.59h-110.02c-4.98 0-9.47-4.08-9.65-9.8-0.18-5.71 4.21-9.88 9.65-9.7z" stroke="#000" fill="url(#radialGradient5393)"/> | ||||||
|  |           <path id="path3572" d="m355.09 797.89h110.02c4.98 0 8.67 3.56 8.99 9.41 0.32 5.86-3.42 9.62-8.99 10.09h-110.02c-4.98 0-9.36-4.1-9.54-9.81-0.17-5.72 3.56-9.62 9.54-9.69z" stroke="#000" fill="url(#radialGradient5395)"/> | ||||||
|  |           <path id="path3574" d="m355.49 797.86h110.02c4.98 0 8.67 3.83 8.85 9.68s-3.41 9.21-8.85 9.82h-110.02c-4.98 0-10.45-3.69-10.36-9.95 0.1-6.39 4.38-9.62 10.36-9.55z" stroke="#565352" fill="url(#radialGradient5397)"/> | ||||||
|  |         </g> | ||||||
|  |       </g> | ||||||
|  |     </g> | ||||||
|  |   </g> | ||||||
|  |   <metadata> | ||||||
|  |     <rdf:RDF> | ||||||
|  |       <cc:Work> | ||||||
|  |         <dc:format>image/svg+xml</dc:format> | ||||||
|  |         <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/> | ||||||
|  |         <cc:license rdf:resource="http://creativecommons.org/licenses/publicdomain/"/> | ||||||
|  |         <dc:publisher> | ||||||
|  |           <cc:Agent rdf:about="http://openclipart.org/"> | ||||||
|  |             <dc:title>Openclipart</dc:title> | ||||||
|  |           </cc:Agent> | ||||||
|  |         </dc:publisher> | ||||||
|  |         <dc:title>basic light bulb</dc:title> | ||||||
|  |         <dc:date>2007-02-16T04:05:06</dc:date> | ||||||
|  |         <dc:description/> | ||||||
|  |         <dc:source>https://openclipart.org/detail/175842/Clipart-by-drunken_duck</dc:source> | ||||||
|  |         <dc:creator> | ||||||
|  |           <cc:Agent> | ||||||
|  |             <dc:title>drunken_duck</dc:title> | ||||||
|  |           </cc:Agent> | ||||||
|  |         </dc:creator> | ||||||
|  |         <dc:subject> | ||||||
|  |           <rdf:Bag> | ||||||
|  |             <rdf:li>light</rdf:li> | ||||||
|  |             <rdf:li>bulb</rdf:li> | ||||||
|  |             <rdf:li>electricity</rdf:li> | ||||||
|  |           </rdf:Bag> | ||||||
|  |         </dc:subject> | ||||||
|  |       </cc:Work> | ||||||
|  |       <cc:License rdf:about="http://creativecommons.org/licenses/publicdomain/"> | ||||||
|  |         <cc:permits rdf:resource="http://creativecommons.org/ns#Reproduction"/> | ||||||
|  |         <cc:permits rdf:resource="http://creativecommons.org/ns#Distribution"/> | ||||||
|  |         <cc:permits rdf:resource="http://creativecommons.org/ns#DerivativeWorks"/> | ||||||
|  |       </cc:License> | ||||||
|  |     </rdf:RDF> | ||||||
|  |   </metadata> | ||||||
|  | </svg> | ||||||
| After Width: | Height: | Size: 24 KiB | 
|  | @ -128,6 +128,15 @@ | ||||||
|             }, |             }, | ||||||
|             "then": "circle:white;./assets/layers/recycling/engine_oil.svg" |             "then": "circle:white;./assets/layers/recycling/engine_oil.svg" | ||||||
|           }, |           }, | ||||||
|  |           { | ||||||
|  |             "if": { | ||||||
|  |               "and": [ | ||||||
|  |                 "_waste_amount=1", | ||||||
|  |                 "recycling:fluorescent_tubes=yes" | ||||||
|  |               ] | ||||||
|  |             }, | ||||||
|  |             "then": "circle:white;./assets/layers/recycling/fluorescent_tubes.svg" | ||||||
|  |           }, | ||||||
|           { |           { | ||||||
|             "if": { |             "if": { | ||||||
|               "and": [ |               "and": [ | ||||||
|  | @ -160,6 +169,15 @@ | ||||||
|             }, |             }, | ||||||
|             "then": "circle:white;./assets/layers/recycling/garden_waste.svg" |             "then": "circle:white;./assets/layers/recycling/garden_waste.svg" | ||||||
|           }, |           }, | ||||||
|  |           { | ||||||
|  |             "if": { | ||||||
|  |               "and": [ | ||||||
|  |                 "_waste_amount=1", | ||||||
|  |                 "recycling:light_bulbs=yes" | ||||||
|  |               ] | ||||||
|  |             }, | ||||||
|  |             "then": "circle:white;./assets/layers/recycling/light_bulbs.svg" | ||||||
|  |           }, | ||||||
|           { |           { | ||||||
|             "if": { |             "if": { | ||||||
|               "and": [ |               "and": [ | ||||||
|  | @ -303,6 +321,15 @@ | ||||||
|           }, |           }, | ||||||
|           "then": "circle:white;./assets/layers/recycling/engine_oil.svg" |           "then": "circle:white;./assets/layers/recycling/engine_oil.svg" | ||||||
|         }, |         }, | ||||||
|  |         { | ||||||
|  |           "if": { | ||||||
|  |             "and": [ | ||||||
|  |               "_waste_amount>1", | ||||||
|  |               "recycling:fluorescent_tubes=yes" | ||||||
|  |             ] | ||||||
|  |           }, | ||||||
|  |           "then": "circle:white;./assets/layers/recycling/fluorescent_tubes.svg" | ||||||
|  |         }, | ||||||
|         { |         { | ||||||
|           "if": { |           "if": { | ||||||
|             "and": [ |             "and": [ | ||||||
|  | @ -335,6 +362,15 @@ | ||||||
|           }, |           }, | ||||||
|           "then": "circle:white;./assets/layers/recycling/garden_waste.svg" |           "then": "circle:white;./assets/layers/recycling/garden_waste.svg" | ||||||
|         }, |         }, | ||||||
|  |         { | ||||||
|  |           "if": { | ||||||
|  |             "and": [ | ||||||
|  |               "_waste_amount>1", | ||||||
|  |               "recycling:light_bulbs=yes" | ||||||
|  |             ] | ||||||
|  |           }, | ||||||
|  |           "then": "circle:white;./assets/layers/recycling/light_bulbs.svg" | ||||||
|  |         }, | ||||||
|         { |         { | ||||||
|           "if": { |           "if": { | ||||||
|             "and": [ |             "and": [ | ||||||
|  | @ -500,7 +536,9 @@ | ||||||
|             "recycling:clothes=", |             "recycling:clothes=", | ||||||
|             "recycling:cooking_oil=", |             "recycling:cooking_oil=", | ||||||
|             "recycling:engine_oil=", |             "recycling:engine_oil=", | ||||||
|  |             "recycling:fluorescent_tubes=", | ||||||
|             "recycling:green_waste=", |             "recycling:green_waste=", | ||||||
|  |             "recycling:light_bulbs=", | ||||||
|             "recycling:organic=", |             "recycling:organic=", | ||||||
|             "recycling:glass_bottles=", |             "recycling:glass_bottles=", | ||||||
|             "recycling:glass=", |             "recycling:glass=", | ||||||
|  | @ -698,6 +736,18 @@ | ||||||
|             "class": "medium" |             "class": "medium" | ||||||
|           } |           } | ||||||
|         }, |         }, | ||||||
|  |         { | ||||||
|  |           "if": "recycling:fluorescent_tubes=yes", | ||||||
|  |           "ifnot": "recycling:fluorescent_tubes=", | ||||||
|  |           "then": { | ||||||
|  |             "en": "Fluorescent tubes can be recycled here", | ||||||
|  |             "nl": "TL-buizen kunnen hier gerecycled worden" | ||||||
|  |           }, | ||||||
|  |           "icon": { | ||||||
|  |             "path": "./assets/layers/recycling/fluorescent_tubes.svg", | ||||||
|  |             "class": "medium" | ||||||
|  |           } | ||||||
|  |         }, | ||||||
|         { |         { | ||||||
|           "if": "recycling:green_waste=yes", |           "if": "recycling:green_waste=yes", | ||||||
|           "ifnot": "recycling:green_waste=", |           "ifnot": "recycling:green_waste=", | ||||||
|  | @ -761,6 +811,18 @@ | ||||||
|             "class": "medium" |             "class": "medium" | ||||||
|           } |           } | ||||||
|         }, |         }, | ||||||
|  |         { | ||||||
|  |           "if": "recycling:light_bulbs=yes", | ||||||
|  |           "ifnot": "recycling:light_bulbs=", | ||||||
|  |           "then": { | ||||||
|  |             "en": "Light bulbs can be recycled here", | ||||||
|  |             "nl": "Lampen kunnen hier gerecycled worden" | ||||||
|  |           }, | ||||||
|  |           "icon": { | ||||||
|  |             "path": "./assets/layers/recycling/light_bulbs.svg", | ||||||
|  |             "class": "medium" | ||||||
|  |           } | ||||||
|  |         }, | ||||||
|         { |         { | ||||||
|           "if": "recycling:newspaper=yes", |           "if": "recycling:newspaper=yes", | ||||||
|           "ifnot": "recycling:newspaper=", |           "ifnot": "recycling:newspaper=", | ||||||
|  | @ -1047,6 +1109,13 @@ | ||||||
|           }, |           }, | ||||||
|           "osmTags": "recycling:engine_oil=yes" |           "osmTags": "recycling:engine_oil=yes" | ||||||
|         }, |         }, | ||||||
|  |         { | ||||||
|  |           "question": { | ||||||
|  |             "en": "Recycling of fluorescent tubes", | ||||||
|  |             "nl": "Recycling van tl-buizen" | ||||||
|  |           }, | ||||||
|  |           "osmTags": "recycling:fluorescent_tubes=yes" | ||||||
|  |         }, | ||||||
|         { |         { | ||||||
|           "question": { |           "question": { | ||||||
|             "en": "Recycling of green waste", |             "en": "Recycling of green waste", | ||||||
|  | @ -1081,6 +1150,13 @@ | ||||||
|           }, |           }, | ||||||
|           "osmTags": "recycling:glass=yes" |           "osmTags": "recycling:glass=yes" | ||||||
|         }, |         }, | ||||||
|  |         { | ||||||
|  |           "question": { | ||||||
|  |             "en": "Recycling of light bulbs", | ||||||
|  |             "nl": "Recycling van lampen" | ||||||
|  |           }, | ||||||
|  |           "osmTags": "recycling:light_bulbs=yes" | ||||||
|  |         }, | ||||||
|         { |         { | ||||||
|           "question": { |           "question": { | ||||||
|             "en": "Recycling of newspapers", |             "en": "Recycling of newspapers", | ||||||
|  |  | ||||||
|  | @ -528,7 +528,8 @@ | ||||||
|         "de": "eine an einer Wand montierte Überwachungskamera" |         "de": "eine an einer Wand montierte Überwachungskamera" | ||||||
|       }, |       }, | ||||||
|       "preciseInput": { |       "preciseInput": { | ||||||
|         "snapToLayer": "walls_and_buildings" |         "snapToLayer": "walls_and_buildings", | ||||||
|  |         "preferredBackground": ["photo", "osmbasedmap","map"] | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|   ], |   ], | ||||||
|  | @ -597,4 +598,4 @@ | ||||||
|     "nl": "Deze laag toont bewakingscamera's en laat toe om de informatie te verrijken en om nieuwe camera\"s toe te voegen", |     "nl": "Deze laag toont bewakingscamera's en laat toe om de informatie te verrijken en om nieuwe camera\"s toe te voegen", | ||||||
|     "de": "Diese Ebene zeigt die Überwachungskameras an und ermöglicht es, Informationen zu aktualisieren und neue Kameras hinzuzufügen" |     "de": "Diese Ebene zeigt die Überwachungskameras an und ermöglicht es, Informationen zu aktualisieren und neue Kameras hinzuzufügen" | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -68,7 +68,7 @@ | ||||||
|             "then": { |             "then": { | ||||||
|               "en": "This ticket validator accepts OV-Chipkaart" |               "en": "This ticket validator accepts OV-Chipkaart" | ||||||
|             }, |             }, | ||||||
|             "hideInAnswer": "_country!=nl" |             "hideInAnswer": true | ||||||
|           }, |           }, | ||||||
|           { |           { | ||||||
|             "if": "payment:ov-chipkaart=yes", |             "if": "payment:ov-chipkaart=yes", | ||||||
|  | @ -76,7 +76,7 @@ | ||||||
|             "then": { |             "then": { | ||||||
|               "en": "This ticket validator accepts OV-Chipkaart" |               "en": "This ticket validator accepts OV-Chipkaart" | ||||||
|             }, |             }, | ||||||
|             "hideInAnswer": true |             "hideInAnswer": "_country!=nl" | ||||||
|           } |           } | ||||||
|         ] |         ] | ||||||
|       } |       } | ||||||
|  | @ -90,6 +90,9 @@ | ||||||
|       "title": { |       "title": { | ||||||
|         "en": "a ticket validator", |         "en": "a ticket validator", | ||||||
|         "de": "einen Fahrkartenentwerter" |         "de": "einen Fahrkartenentwerter" | ||||||
|  |       }, | ||||||
|  |       "description": { | ||||||
|  |         "en": "A ticket validator to validate a public transport ticket. This can be either a digital reader, reading a card or ticket, or a machine stamping or punching a ticket." | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|   ], |   ], | ||||||
|  | @ -103,4 +106,4 @@ | ||||||
|       ] |       ] | ||||||
|     } |     } | ||||||
|   ] |   ] | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -12,7 +12,7 @@ | ||||||
|     "license": "CC-BY", |     "license": "CC-BY", | ||||||
|     "authors": [ |     "authors": [ | ||||||
|       "asianson.design", |       "asianson.design", | ||||||
|       " Pieter Vander Vennet" |       "Pieter Vander Vennet" | ||||||
|     ], |     ], | ||||||
|     "sources": [ |     "sources": [ | ||||||
|       "https://thenounproject.com/term/urinal/1307984/" |       "https://thenounproject.com/term/urinal/1307984/" | ||||||
|  | @ -22,6 +22,8 @@ | ||||||
|     "path": "wheelchair.svg", |     "path": "wheelchair.svg", | ||||||
|     "license": "CC0", |     "license": "CC0", | ||||||
|     "authors": [], |     "authors": [], | ||||||
|     "sources": [] |     "sources": [ | ||||||
|  |       "https://wiki.openstreetmap.org/wiki/File:Wheelchair_symbol.svg" | ||||||
|  |     ] | ||||||
|   } |   } | ||||||
| ] | ] | ||||||
|  | @ -233,7 +233,7 @@ | ||||||
|       "id": "toilet-charge" |       "id": "toilet-charge" | ||||||
|     }, |     }, | ||||||
|     { |     { | ||||||
|       "builtin": "payment-options", |       "builtin": "payment-options-split", | ||||||
|       "override": { |       "override": { | ||||||
|         "condition": "fee=yes" |         "condition": "fee=yes" | ||||||
|       } |       } | ||||||
|  |  | ||||||
|  | @ -23,6 +23,8 @@ | ||||||
|     "path": "wheelchair.svg", |     "path": "wheelchair.svg", | ||||||
|     "license": "CC0", |     "license": "CC0", | ||||||
|     "authors": [], |     "authors": [], | ||||||
|     "sources": [] |     "sources": [ | ||||||
|  |       "https://wiki.openstreetmap.org/wiki/File:Wheelchair_symbol.svg" | ||||||
|  |     ] | ||||||
|   } |   } | ||||||
| ] | ] | ||||||
|  | @ -32,7 +32,7 @@ | ||||||
|     } |     } | ||||||
|   }, |   }, | ||||||
|   "description": { |   "description": { | ||||||
|     "en": "This is a public waste basket, thrash can, where you can throw away your thrash.", |     "en": "This is a public waste basket, trash can, where you can throw away your trash.", | ||||||
|     "nl": "Dit is een publieke vuilnisbak waar je je afval kan weggooien.", |     "nl": "Dit is een publieke vuilnisbak waar je je afval kan weggooien.", | ||||||
|     "de": "Dies ist ein öffentlicher Abfalleimer, in den Sie Ihren Müll entsorgen können.", |     "de": "Dies ist ein öffentlicher Abfalleimer, in den Sie Ihren Müll entsorgen können.", | ||||||
|     "hu": "Ez egy nyilvános szemétkosár vagy kuka, ahová kidobhatod a szemetedet.", |     "hu": "Ez egy nyilvános szemétkosár vagy kuka, ahová kidobhatod a szemetedet.", | ||||||
|  | @ -369,4 +369,4 @@ | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|   ] |   ] | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,8 +0,0 @@ | ||||||
| [ |  | ||||||
|   { |  | ||||||
|     "path": "watermill.svg", |  | ||||||
|     "license": "CC0", |  | ||||||
|     "authors": [], |  | ||||||
|     "sources": [] |  | ||||||
|   } |  | ||||||
| ] |  | ||||||
|  | @ -1,191 +0,0 @@ | ||||||
| { |  | ||||||
|   "id": "watermill", |  | ||||||
|   "name": { |  | ||||||
|     "nl": "Watermolens", |  | ||||||
|     "en": "Watermill", |  | ||||||
|     "de": "Wassermühlen", |  | ||||||
|     "ru": "Водяная мельница", |  | ||||||
|     "id": "Kincir Air", |  | ||||||
|     "fr": "Moulin à eau", |  | ||||||
|     "ca": "Molí d'aigua", |  | ||||||
|     "da": "Vandmølle" |  | ||||||
|   }, |  | ||||||
|   "minzoom": 12, |  | ||||||
|   "source": { |  | ||||||
|     "osmTags": { |  | ||||||
|       "and": [ |  | ||||||
|         "man_made=watermill" |  | ||||||
|       ] |  | ||||||
|     } |  | ||||||
|   }, |  | ||||||
|   "title": { |  | ||||||
|     "render": { |  | ||||||
|       "nl": "Watermolens" |  | ||||||
|     }, |  | ||||||
|     "mappings": [ |  | ||||||
|       { |  | ||||||
|         "if": { |  | ||||||
|           "and": [ |  | ||||||
|             "name:nl~*" |  | ||||||
|           ] |  | ||||||
|         }, |  | ||||||
|         "then": { |  | ||||||
|           "nl": "{name:nl}" |  | ||||||
|         } |  | ||||||
|       }, |  | ||||||
|       { |  | ||||||
|         "if": { |  | ||||||
|           "and": [ |  | ||||||
|             "name~*" |  | ||||||
|           ] |  | ||||||
|         }, |  | ||||||
|         "then": { |  | ||||||
|           "nl": "{name}" |  | ||||||
|         } |  | ||||||
|       } |  | ||||||
|     ] |  | ||||||
|   }, |  | ||||||
|   "description": { |  | ||||||
|     "nl": "Watermolens" |  | ||||||
|   }, |  | ||||||
|   "tagRenderings": [ |  | ||||||
|     "images", |  | ||||||
|     { |  | ||||||
|       "render": { |  | ||||||
|         "nl": "De toegankelijkheid van dit gebied is: {access:description}" |  | ||||||
|       }, |  | ||||||
|       "question": { |  | ||||||
|         "nl": "Is dit gebied toegankelijk?" |  | ||||||
|       }, |  | ||||||
|       "freeform": { |  | ||||||
|         "key": "access:description" |  | ||||||
|       }, |  | ||||||
|       "mappings": [ |  | ||||||
|         { |  | ||||||
|           "if": { |  | ||||||
|             "and": [ |  | ||||||
|               "access=yes", |  | ||||||
|               "fee=" |  | ||||||
|             ] |  | ||||||
|           }, |  | ||||||
|           "then": { |  | ||||||
|             "nl": "Vrij toegankelijk" |  | ||||||
|           } |  | ||||||
|         }, |  | ||||||
|         { |  | ||||||
|           "if": { |  | ||||||
|             "and": [ |  | ||||||
|               "access=no", |  | ||||||
|               "fee=" |  | ||||||
|             ] |  | ||||||
|           }, |  | ||||||
|           "then": { |  | ||||||
|             "nl": "Niet toegankelijk" |  | ||||||
|           } |  | ||||||
|         }, |  | ||||||
|         { |  | ||||||
|           "if": { |  | ||||||
|             "and": [ |  | ||||||
|               "access=private", |  | ||||||
|               "fee=" |  | ||||||
|             ] |  | ||||||
|           }, |  | ||||||
|           "then": { |  | ||||||
|             "nl": "Niet toegankelijk, want privégebied" |  | ||||||
|           } |  | ||||||
|         }, |  | ||||||
|         { |  | ||||||
|           "if": { |  | ||||||
|             "and": [ |  | ||||||
|               "access=permissive", |  | ||||||
|               "fee=" |  | ||||||
|             ] |  | ||||||
|           }, |  | ||||||
|           "then": { |  | ||||||
|             "nl": "Toegankelijk, ondanks dat het privegebied is" |  | ||||||
|           } |  | ||||||
|         }, |  | ||||||
|         { |  | ||||||
|           "if": { |  | ||||||
|             "and": [ |  | ||||||
|               "access=guided", |  | ||||||
|               "fee=" |  | ||||||
|             ] |  | ||||||
|           }, |  | ||||||
|           "then": { |  | ||||||
|             "nl": "Enkel toegankelijk met een gids of tijdens een activiteit" |  | ||||||
|           } |  | ||||||
|         }, |  | ||||||
|         { |  | ||||||
|           "if": { |  | ||||||
|             "and": [ |  | ||||||
|               "access=yes", |  | ||||||
|               "fee=yes" |  | ||||||
|             ] |  | ||||||
|           }, |  | ||||||
|           "then": { |  | ||||||
|             "nl": "Toegankelijk mits betaling" |  | ||||||
|           } |  | ||||||
|         } |  | ||||||
|       ], |  | ||||||
|       "id": "Access tag" |  | ||||||
|     }, |  | ||||||
|     { |  | ||||||
|       "render": { |  | ||||||
|         "nl": "Beheer door {operator}" |  | ||||||
|       }, |  | ||||||
|       "question": { |  | ||||||
|         "nl": "Wie beheert dit pad?" |  | ||||||
|       }, |  | ||||||
|       "freeform": { |  | ||||||
|         "key": "operator" |  | ||||||
|       }, |  | ||||||
|       "mappings": [ |  | ||||||
|         { |  | ||||||
|           "if": { |  | ||||||
|             "and": [ |  | ||||||
|               "operator=Natuurpunt" |  | ||||||
|             ] |  | ||||||
|           }, |  | ||||||
|           "then": { |  | ||||||
|             "nl": "Dit gebied wordt beheerd door Natuurpunt" |  | ||||||
|           }, |  | ||||||
|           "icon": { |  | ||||||
|             "path": "./assets/themes/buurtnatuur/Natuurpunt.jpg", |  | ||||||
|             "class": "small" |  | ||||||
|           } |  | ||||||
|         }, |  | ||||||
|         { |  | ||||||
|           "if": { |  | ||||||
|             "and": [ |  | ||||||
|               "operator~(n|N)atuurpunt.*" |  | ||||||
|             ] |  | ||||||
|           }, |  | ||||||
|           "then": { |  | ||||||
|             "nl": "Dit gebied wordt beheerd door {operator}" |  | ||||||
|           }, |  | ||||||
|           "hideInAnswer": true, |  | ||||||
|           "icon": { |  | ||||||
|             "path": "./assets/themes/buurtnatuur/Natuurpunt.jpg", |  | ||||||
|             "class": "small" |  | ||||||
|           } |  | ||||||
|         } |  | ||||||
|       ], |  | ||||||
|       "id": "Operator tag" |  | ||||||
|     } |  | ||||||
|   ], |  | ||||||
|   "mapRendering": [ |  | ||||||
|     { |  | ||||||
|       "icon": { |  | ||||||
|         "render": "./assets/layers/watermill/watermill.svg" |  | ||||||
|       }, |  | ||||||
|       "iconSize": { |  | ||||||
|         "render": "50,50,center" |  | ||||||
|       }, |  | ||||||
|       "location": [ |  | ||||||
|         "point", |  | ||||||
|         "centroid" |  | ||||||
|       ] |  | ||||||
|     } |  | ||||||
|   ] |  | ||||||
| } |  | ||||||
|  | @ -1,6 +0,0 @@ | ||||||
| <?xml version="1.0" encoding="UTF-8" standalone="yes"?> |  | ||||||
| <svg xmlns="http://www.w3.org/2000/svg" width="281px" height="374px" viewBox="0 0 281 374" version="1.1"> |  | ||||||
|   <g id="surface1"> |  | ||||||
|     <path style=" stroke:none;fill-rule:nonzero;fill:rgb(100%,100%,100%);fill-opacity:1;" d="M 173.078125 146.714844 L 281 39.171875 L 269.894531 28.089844 L 257.675781 15.707031 L 159.9375 113.457031 C 154.6875 108.464844 147.746094 105.632812 140.5 105.511719 L 136.058594 105.511719 L 34.617188 4.621094 L 23.511719 15.707031 L 11.105469 27.902344 L 111.066406 127.871094 C 110.085938 130.175781 109.398438 132.597656 109.03125 135.074219 L 107.179688 143.945312 L 0 250.566406 L 11.105469 261.652344 L 23.324219 274.035156 L 94.964844 202.519531 L 66.085938 342.21875 L 45.167969 342.21875 L 45.167969 369.378906 L 240.644531 369.378906 L 240.644531 342.21875 L 218.988281 342.21875 L 193.070312 231.347656 L 246.382812 284.5625 L 257.488281 273.480469 L 269.894531 261.28125 L 178.820312 170.925781 Z M 162.898438 340.183594 L 122.171875 340.183594 L 122.171875 287.152344 C 122.171875 276.03125 131.207031 267.007812 142.351562 267.007812 C 147.765625 266.910156 152.992188 268.992188 156.859375 272.777344 C 160.722656 276.566406 162.898438 281.746094 162.898438 287.152344 Z M 162.898438 340.183594 "/> |  | ||||||
|   </g> |  | ||||||
| </svg> |  | ||||||
| Before Width: | Height: | Size: 1.3 KiB | 
|  | @ -499,12 +499,6 @@ | ||||||
|       "https://www.iconpacks.net/free-icon-pack/gender-107.html" |       "https://www.iconpacks.net/free-icon-pack/gender-107.html" | ||||||
|     ] |     ] | ||||||
|   }, |   }, | ||||||
|   { |  | ||||||
|     "path": "gender_female.svg", |  | ||||||
|     "license": "CC0", |  | ||||||
|     "authors": [], |  | ||||||
|     "sources": [] |  | ||||||
|   }, |  | ||||||
|   { |   { | ||||||
|     "path": "gender_female.svg", |     "path": "gender_female.svg", | ||||||
|     "license": "CC0", |     "license": "CC0", | ||||||
|  | @ -525,12 +519,6 @@ | ||||||
|       "https://www.iconpacks.net/free-icon-pack/gender-107.html" |       "https://www.iconpacks.net/free-icon-pack/gender-107.html" | ||||||
|     ] |     ] | ||||||
|   }, |   }, | ||||||
|   { |  | ||||||
|     "path": "gender_male.svg", |  | ||||||
|     "license": "CC0", |  | ||||||
|     "authors": [], |  | ||||||
|     "sources": [] |  | ||||||
|   }, |  | ||||||
|   { |   { | ||||||
|     "path": "gender_male.svg", |     "path": "gender_male.svg", | ||||||
|     "license": "CC0", |     "license": "CC0", | ||||||
|  | @ -551,12 +539,6 @@ | ||||||
|       "https://www.iconpacks.net/free-icon-pack/gender-107.html" |       "https://www.iconpacks.net/free-icon-pack/gender-107.html" | ||||||
|     ] |     ] | ||||||
|   }, |   }, | ||||||
|   { |  | ||||||
|     "path": "gender_trans.svg", |  | ||||||
|     "license": "CC0", |  | ||||||
|     "authors": [], |  | ||||||
|     "sources": [] |  | ||||||
|   }, |  | ||||||
|   { |   { | ||||||
|     "path": "gender_trans.svg", |     "path": "gender_trans.svg", | ||||||
|     "license": "CC0", |     "license": "CC0", | ||||||
|  | @ -899,7 +881,7 @@ | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     "path": "none.svg", |     "path": "none.svg", | ||||||
|     "license": "CC0", |     "license": "trivial", | ||||||
|     "authors": [], |     "authors": [], | ||||||
|     "sources": [] |     "sources": [] | ||||||
|   }, |   }, | ||||||
|  | @ -1277,9 +1259,11 @@ | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     "path": "teardrop_with_hole_green.svg", |     "path": "teardrop_with_hole_green.svg", | ||||||
|     "license": "CC0", |     "license": "Creative Commons 4.0 BY-NC", | ||||||
|     "authors": [], |     "authors": [], | ||||||
|     "sources": [] |     "sources": [ | ||||||
|  |       "https://pngimg.com/image/46283" | ||||||
|  |     ] | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     "path": "translate.svg", |     "path": "translate.svg", | ||||||
|  |  | ||||||
|  | @ -919,6 +919,7 @@ | ||||||
|           "icon": "./assets/tagRenderings/coins.svg", |           "icon": "./assets/tagRenderings/coins.svg", | ||||||
|           "then": { |           "then": { | ||||||
|             "en": "Coins are accepted here", |             "en": "Coins are accepted here", | ||||||
|  |             "nl": "Muntgeld wordt hier aanvaard", | ||||||
|             "de": "Münzen werden hier akzeptiert" |             "de": "Münzen werden hier akzeptiert" | ||||||
|           } |           } | ||||||
|         }, |         }, | ||||||
|  | @ -928,6 +929,7 @@ | ||||||
|           "icon": "./assets/tagRenderings/notes.svg", |           "icon": "./assets/tagRenderings/notes.svg", | ||||||
|           "then": { |           "then": { | ||||||
|             "en": "Bank notes are accepted here", |             "en": "Bank notes are accepted here", | ||||||
|  |             "nl": "Bankbiljetten worden hier aanvaard", | ||||||
|             "de": "Geldscheine werden hier akzeptiert" |             "de": "Geldscheine werden hier akzeptiert" | ||||||
|           } |           } | ||||||
|         }, |         }, | ||||||
|  | @ -937,6 +939,7 @@ | ||||||
|           "icon": "./assets/tagRenderings/payment_card.svg", |           "icon": "./assets/tagRenderings/payment_card.svg", | ||||||
|           "then": { |           "then": { | ||||||
|             "en": "Debit cards are accepted here", |             "en": "Debit cards are accepted here", | ||||||
|  |             "nl": "Betalen met debetkaarten kan hier", | ||||||
|             "de": "Debitkarten werden hier akzeptiert" |             "de": "Debitkarten werden hier akzeptiert" | ||||||
|           } |           } | ||||||
|         }, |         }, | ||||||
|  | @ -946,6 +949,7 @@ | ||||||
|           "icon": "./assets/tagRenderings/payment_card.svg", |           "icon": "./assets/tagRenderings/payment_card.svg", | ||||||
|           "then": { |           "then": { | ||||||
|             "en": "Credit cards are accepted here", |             "en": "Credit cards are accepted here", | ||||||
|  |             "nl": "Betalen met creditkaarten kan hier", | ||||||
|             "de": "Kreditkarten werden hier akzeptiert" |             "de": "Kreditkarten werden hier akzeptiert" | ||||||
|           } |           } | ||||||
|         } |         } | ||||||
|  | @ -1664,4 +1668,4 @@ | ||||||
|       "es": "El nombre de red es <b>{internet_access:ssid}</b>" |       "es": "El nombre de red es <b>{internet_access:ssid}</b>" | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -13,8 +13,13 @@ | ||||||
|   { |   { | ||||||
|     "path": "car.svg", |     "path": "car.svg", | ||||||
|     "license": "CC0", |     "license": "CC0", | ||||||
|     "authors": [], |     "authors": [ | ||||||
|     "sources": [] |       "Simon Child", | ||||||
|  |       "The Noun Project" | ||||||
|  |     ], | ||||||
|  |     "sources": [ | ||||||
|  |       "https://thenounproject.com/icon/electric-car-55511/" | ||||||
|  |     ] | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     "path": "logo.svg", |     "path": "logo.svg", | ||||||
|  |  | ||||||
|  | @ -39,6 +39,7 @@ | ||||||
|   "socialImage": "./assets/themes/cyclofix/logo.svg", |   "socialImage": "./assets/themes/cyclofix/logo.svg", | ||||||
|   "layers": [ |   "layers": [ | ||||||
|     "bike_cafe", |     "bike_cafe", | ||||||
|  |     "bike_shop", | ||||||
|     { |     { | ||||||
|       "builtin": [ |       "builtin": [ | ||||||
|         "bicycle_rental" |         "bicycle_rental" | ||||||
|  | @ -87,7 +88,6 @@ | ||||||
|         ] |         ] | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "bike_shop", |  | ||||||
|     { |     { | ||||||
|       "builtin": "bicycle_library", |       "builtin": "bicycle_library", | ||||||
|       "override": { |       "override": { | ||||||
|  | @ -130,4 +130,4 @@ | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|   ] |   ] | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -3,37 +3,49 @@ | ||||||
|     "path": "bench.svg", |     "path": "bench.svg", | ||||||
|     "license": "CC0", |     "license": "CC0", | ||||||
|     "authors": [], |     "authors": [], | ||||||
|     "sources": [] |     "sources": [ | ||||||
|  |       "https://osoc.be/editions/2021/nature-moves" | ||||||
|  |     ] | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     "path": "birdhide.svg", |     "path": "birdhide.svg", | ||||||
|     "license": "CC0", |     "license": "CC0", | ||||||
|     "authors": [], |     "authors": [], | ||||||
|     "sources": [] |     "sources": [ | ||||||
|  |       "https://osoc.be/editions/2021/nature-moves" | ||||||
|  |     ] | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     "path": "drips.svg", |     "path": "drips.svg", | ||||||
|     "license": "CC0", |     "license": "CC0", | ||||||
|     "authors": [], |     "authors": [], | ||||||
|     "sources": [] |     "sources": [ | ||||||
|  |       "https://osoc.be/editions/2021/nature-moves" | ||||||
|  |     ] | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     "path": "information.svg", |     "path": "information.svg", | ||||||
|     "license": "CC0", |     "license": "CC0", | ||||||
|     "authors": [], |     "authors": [], | ||||||
|     "sources": [] |     "sources": [ | ||||||
|  |       "https://osoc.be/editions/2021/nature-moves" | ||||||
|  |     ] | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     "path": "information_board.svg", |     "path": "information_board.svg", | ||||||
|     "license": "CC0", |     "license": "CC0", | ||||||
|     "authors": [], |     "authors": [], | ||||||
|     "sources": [] |     "sources": [ | ||||||
|  |       "https://osoc.be/editions/2021/nature-moves" | ||||||
|  |     ] | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     "path": "nature_reserve.svg", |     "path": "nature_reserve.svg", | ||||||
|     "license": "CC0", |     "license": "CC0", | ||||||
|     "authors": [], |     "authors": [], | ||||||
|     "sources": [] |     "sources": [ | ||||||
|  |       "https://osoc.be/editions/2021/nature-moves" | ||||||
|  |     ] | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     "path": "natuurpunt.png", |     "path": "natuurpunt.png", | ||||||
|  | @ -49,72 +61,88 @@ | ||||||
|     "path": "parking.svg", |     "path": "parking.svg", | ||||||
|     "license": "CC0", |     "license": "CC0", | ||||||
|     "authors": [], |     "authors": [], | ||||||
|     "sources": [] |     "sources": [ | ||||||
|  |       "https://osoc.be/editions/2021/nature-moves" | ||||||
|  |     ] | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     "path": "parkingbike.svg", |     "path": "parkingbike.svg", | ||||||
|     "license": "CC0", |     "license": "CC0", | ||||||
|     "authors": [], |     "authors": [], | ||||||
|     "sources": [] |     "sources": [ | ||||||
|  |       "https://osoc.be/editions/2021/nature-moves" | ||||||
|  |     ] | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     "path": "parkingmotor.svg", |     "path": "parkingmotor.svg", | ||||||
|     "license": "CC0", |     "license": "CC0", | ||||||
|     "authors": [], |     "authors": [], | ||||||
|     "sources": [] |     "sources": [ | ||||||
|  |       "https://osoc.be/editions/2021/nature-moves" | ||||||
|  |     ] | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     "path": "parkingwheels.svg", |     "path": "parkingwheels.svg", | ||||||
|     "license": "CC0", |     "license": "CC0", | ||||||
|     "authors": [], |     "authors": [], | ||||||
|     "sources": [] |     "sources": [ | ||||||
|  |       "https://osoc.be/editions/2021/nature-moves" | ||||||
|  |     ] | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     "path": "picnic_table.svg", |     "path": "picnic_table.svg", | ||||||
|     "license": "CC0", |     "license": "CC0", | ||||||
|     "authors": [], |     "authors": [], | ||||||
|     "sources": [] |     "sources": [ | ||||||
|  |       "https://osoc.be/editions/2021/nature-moves" | ||||||
|  |     ] | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     "path": "pushchair.svg", |     "path": "pushchair.svg", | ||||||
|     "license": "CC0", |     "license": "CC0", | ||||||
|     "authors": [], |     "authors": [], | ||||||
|     "sources": [] |     "sources": [ | ||||||
|  |       "https://osoc.be/editions/2021/nature-moves" | ||||||
|  |     ] | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     "path": "toilets.svg", |     "path": "toilets.svg", | ||||||
|     "license": "CC0", |     "license": "CC0", | ||||||
|     "authors": [], |     "authors": [], | ||||||
|     "sources": [] |     "sources": [ | ||||||
|  |       "https://osoc.be/editions/2021/nature-moves" | ||||||
|  |     ] | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     "path": "trail.svg", |     "path": "trail.svg", | ||||||
|     "license": "CC0", |     "license": "CC0", | ||||||
|     "authors": [], |     "authors": [], | ||||||
|     "sources": [] |     "sources": [ | ||||||
|  |       "https://osoc.be/editions/2021/nature-moves" | ||||||
|  |     ] | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     "path": "urinal.svg", |     "path": "urinal.svg", | ||||||
|     "license": "CC0", |     "license": "CC0", | ||||||
|     "authors": [], |     "authors": [], | ||||||
|     "sources": [] |     "sources": [ | ||||||
|  |       "https://osoc.be/editions/2021/nature-moves" | ||||||
|  |     ] | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     "path": "walk_wheelchair.svg", |     "path": "walk_wheelchair.svg", | ||||||
|     "license": "CC0", |     "license": "CC0", | ||||||
|     "authors": [], |     "authors": [], | ||||||
|     "sources": [] |     "sources": [ | ||||||
|   }, |       "https://osoc.be/editions/2021/nature-moves" | ||||||
|   { |     ] | ||||||
|     "path": "watermill.svg", |  | ||||||
|     "license": "CC0", |  | ||||||
|     "authors": [], |  | ||||||
|     "sources": [] |  | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     "path": "wheelchair.svg", |     "path": "wheelchair.svg", | ||||||
|     "license": "CC0", |     "license": "CC0", | ||||||
|     "authors": [], |     "authors": [], | ||||||
|     "sources": [] |     "sources": [ | ||||||
|  |       "https://osoc.be/editions/2021/nature-moves" | ||||||
|  |     ] | ||||||
|   } |   } | ||||||
| ] | ] | ||||||
|  | @ -323,24 +323,6 @@ | ||||||
|         ] |         ] | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     { |  | ||||||
|       "builtin": "watermill", |  | ||||||
|       "override": { |  | ||||||
|         "minzoom": "14", |  | ||||||
|         "source": { |  | ||||||
|           "geoJson": "https://raw.githubusercontent.com/pietervdvn/MapComplete-data/main/natuurpunt_cache/natuurpunt_{layer}_{z}_{x}_{y}.geojson", |  | ||||||
|           "geoJsonZoomLevel": 12, |  | ||||||
|           "isOsmCache": true |  | ||||||
|         }, |  | ||||||
|         "mapRendering": [ |  | ||||||
|           { |  | ||||||
|             "icon": { |  | ||||||
|               "render": "circle:#FE6F32;./assets/themes/natuurpunt/watermill.svg" |  | ||||||
|             } |  | ||||||
|           } |  | ||||||
|         ] |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     { |     { | ||||||
|       "builtin": "gps_track", |       "builtin": "gps_track", | ||||||
|       "override": { |       "override": { | ||||||
|  | @ -353,4 +335,4 @@ | ||||||
|   "enableIframePopout": false, |   "enableIframePopout": false, | ||||||
|   "enableBackgroundLayerSelection": false, |   "enableBackgroundLayerSelection": false, | ||||||
|   "enableNoteImports": false |   "enableNoteImports": false | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,45 +0,0 @@ | ||||||
| <?xml version="1.0" encoding="UTF-8" standalone="no"?> |  | ||||||
| <svg |  | ||||||
|    viewBox="-10 8 15.18 19.74" |  | ||||||
|    version="1.1" |  | ||||||
|    id="svg1054" |  | ||||||
|    sodipodi:docname="watermill.svg" |  | ||||||
|    width="15.18" |  | ||||||
|    height="19.74" |  | ||||||
|    inkscape:version="1.1.1 (1:1.1+202109281949+c3084ef5ed)" |  | ||||||
|    xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" |  | ||||||
|    xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" |  | ||||||
|    xmlns="http://www.w3.org/2000/svg" |  | ||||||
|    xmlns:svg="http://www.w3.org/2000/svg"> |  | ||||||
|   <sodipodi:namedview |  | ||||||
|      id="namedview1056" |  | ||||||
|      pagecolor="#505050" |  | ||||||
|      bordercolor="#eeeeee" |  | ||||||
|      borderopacity="1" |  | ||||||
|      inkscape:pageshadow="0" |  | ||||||
|      inkscape:pageopacity="0" |  | ||||||
|      inkscape:pagecheckerboard="0" |  | ||||||
|      showgrid="false" |  | ||||||
|      inkscape:zoom="6.0407534" |  | ||||||
|      inkscape:cx="-31.949657" |  | ||||||
|      inkscape:cy="10.594705" |  | ||||||
|      inkscape:current-layer="svg1054" /> |  | ||||||
|   <defs |  | ||||||
|      id="defs1048"> |  | ||||||
|     <style |  | ||||||
|        id="style1046">.cls-1{fill:#fff;}</style> |  | ||||||
|   </defs> |  | ||||||
|   <g |  | ||||||
|      id="Layer_2" |  | ||||||
|      data-name="Layer 2" |  | ||||||
|      transform="matrix(0.58392824,0,0,0.58392824,-6.8420153,12.106628)"> |  | ||||||
|     <g |  | ||||||
|        id="Layer_1-2" |  | ||||||
|        data-name="Layer 1"> |  | ||||||
|       <path |  | ||||||
|          class="cls-1" |  | ||||||
|          d="M 9.35,7.69 15.18,1.87 14.58,1.27 13.92,0.6 8.64,5.89 A 1.56,1.56 0 0 0 7.59,5.46 H 7.35 L 1.87,0 1.27,0.6 0.6,1.26 6,6.67 A 1.6,1.6 0 0 0 5.89,7.06 v 0 L 5.79,7.54 0,13.31 l 0.6,0.6 0.66,0.67 3.87,-3.87 -1.56,7.56 H 2.44 v 1.47 H 13 v -1.47 h -1.17 l -1.4,-6 2.88,2.88 0.6,-0.6 0.67,-0.66 L 9.66,9 Z M 8.8,18.16 H 6.6 V 15.29 A 1.09,1.09 0 0 1 7.69,14.2 v 0 a 1.09,1.09 0 0 1 1.11,1.09 z" |  | ||||||
|          id="path1050" /> |  | ||||||
|     </g> |  | ||||||
|   </g> |  | ||||||
| </svg> |  | ||||||
| Before Width: | Height: | Size: 1.6 KiB | 
|  | @ -189,6 +189,25 @@ | ||||||
|         ] |         ] | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|  |     { | ||||||
|  |       "builtin": "parking_spaces", | ||||||
|  |       "override": { | ||||||
|  |         "source": { | ||||||
|  |           "osmTags": "parking_space=disabled" | ||||||
|  |         }, | ||||||
|  |         "mapRendering": [ | ||||||
|  |           { | ||||||
|  |             "icon": { | ||||||
|  |               "mappings": null | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |         ] | ||||||
|  |       }, | ||||||
|  |       "hideTagRenderingsWithLabels": [ | ||||||
|  |         "type", | ||||||
|  |         "capacity" | ||||||
|  |       ] | ||||||
|  |     }, | ||||||
|     { |     { | ||||||
|       "builtin": "shops", |       "builtin": "shops", | ||||||
|       "override": { |       "override": { | ||||||
|  |  | ||||||
|  | @ -107,9 +107,11 @@ | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     "path": "logo.svg", |     "path": "logo.svg", | ||||||
|     "license": "CC0", |     "license": "Creative Commons 4.0 BY-NC", | ||||||
|     "authors": [], |     "authors": [], | ||||||
|     "sources": [] |     "sources": [ | ||||||
|  |       "https://pngimg.com/image/46283" | ||||||
|  |     ] | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     "path": "observation_platform.svg", |     "path": "observation_platform.svg", | ||||||
|  |  | ||||||
|  | @ -2,7 +2,9 @@ | ||||||
|   { |   { | ||||||
|     "path": "icon.svg", |     "path": "icon.svg", | ||||||
|     "license": "CC0; trivial", |     "license": "CC0; trivial", | ||||||
|     "authors": [], |     "authors": [ | ||||||
|  |       "Pieter Vander Vennet" | ||||||
|  |     ], | ||||||
|     "sources": [] |     "sources": [] | ||||||
|   } |   } | ||||||
| ] | ] | ||||||
|  | @ -913,6 +913,7 @@ | ||||||
|         "inviteToSplit": "Split this road in smaller segments. This allows to give different properties to parts of the road.", |         "inviteToSplit": "Split this road in smaller segments. This allows to give different properties to parts of the road.", | ||||||
|         "loginToSplit": "You must be logged in to split a road", |         "loginToSplit": "You must be logged in to split a road", | ||||||
|         "split": "Split", |         "split": "Split", | ||||||
|  |         "splitAgain": "Split this road again", | ||||||
|         "splitTitle": "Choose on the map where the properties of this road change" |         "splitTitle": "Choose on the map where the properties of this road change" | ||||||
|     }, |     }, | ||||||
|     "translations": { |     "translations": { | ||||||
|  |  | ||||||
|  | @ -881,31 +881,31 @@ | ||||||
|                     "2": { |                     "2": { | ||||||
|                         "then": "Ací es poden reciclar llaunes" |                         "then": "Ací es poden reciclar llaunes" | ||||||
|                     }, |                     }, | ||||||
|                     "7": { |                     "8": { | ||||||
|                         "then": "Ací es poden reciclar residus orgànics" |                         "then": "Ací es poden reciclar residus orgànics" | ||||||
|                     }, |                     }, | ||||||
|                     "8": { |                     "9": { | ||||||
|                         "then": "Ací es poden reciclar ampolles de vidre" |                         "then": "Ací es poden reciclar ampolles de vidre" | ||||||
|                     }, |                     }, | ||||||
|                     "9": { |                     "10": { | ||||||
|                         "then": "Ací es pot reciclar vidre" |                         "then": "Ací es pot reciclar vidre" | ||||||
|                     }, |                     }, | ||||||
|                     "10": { |                     "12": { | ||||||
|                         "then": "Ací es poden reciclar diaris" |                         "then": "Ací es poden reciclar diaris" | ||||||
|                     }, |                     }, | ||||||
|                     "11": { |                     "13": { | ||||||
|                         "then": "Ací es pot reciclar paper" |                         "then": "Ací es pot reciclar paper" | ||||||
|                     }, |                     }, | ||||||
|                     "12": { |                     "14": { | ||||||
|                         "then": "Ací es poden reciclar ampolles de plàstic" |                         "then": "Ací es poden reciclar ampolles de plàstic" | ||||||
|                     }, |                     }, | ||||||
|                     "13": { |                     "15": { | ||||||
|                         "then": "Ací es poden reciclar envasos de plàstic" |                         "then": "Ací es poden reciclar envasos de plàstic" | ||||||
|                     }, |                     }, | ||||||
|                     "14": { |                     "16": { | ||||||
|                         "then": "Ací es pot reciclar plàstic" |                         "then": "Ací es pot reciclar plàstic" | ||||||
|                     }, |                     }, | ||||||
|                     "20": { |                     "22": { | ||||||
|                         "then": "Ací es pot reciclar el rebuig" |                         "then": "Ací es pot reciclar el rebuig" | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|  | @ -1052,9 +1052,6 @@ | ||||||
|             "render": "Paperera" |             "render": "Paperera" | ||||||
|         } |         } | ||||||
|     }, |     }, | ||||||
|     "watermill": { |  | ||||||
|         "name": "Molí d'aigua" |  | ||||||
|     }, |  | ||||||
|     "windturbine": { |     "windturbine": { | ||||||
|         "title": { |         "title": { | ||||||
|             "mappings": { |             "mappings": { | ||||||
|  |  | ||||||
|  | @ -6322,37 +6322,37 @@ | ||||||
|                     "6": { |                     "6": { | ||||||
|                         "question": "Recycling von Motoröl" |                         "question": "Recycling von Motoröl" | ||||||
|                     }, |                     }, | ||||||
|                     "7": { |                     "8": { | ||||||
|                         "question": "Recycling von Grünabfällen" |                         "question": "Recycling von Grünabfällen" | ||||||
|                     }, |                     }, | ||||||
|                     "8": { |                     "9": { | ||||||
|                         "question": "Recycling von Glasflaschen" |                         "question": "Recycling von Glasflaschen" | ||||||
|                     }, |                     }, | ||||||
|                     "9": { |                     "10": { | ||||||
|                         "question": "Recycling von Glas" |                         "question": "Recycling von Glas" | ||||||
|                     }, |                     }, | ||||||
|                     "10": { |                     "12": { | ||||||
|                         "question": "Recycling von Zeitungen" |                         "question": "Recycling von Zeitungen" | ||||||
|                     }, |                     }, | ||||||
|                     "11": { |                     "13": { | ||||||
|                         "question": "Recycling von Papier" |                         "question": "Recycling von Papier" | ||||||
|                     }, |                     }, | ||||||
|                     "12": { |                     "14": { | ||||||
|                         "question": "Recycling von Plastikflaschen" |                         "question": "Recycling von Plastikflaschen" | ||||||
|                     }, |                     }, | ||||||
|                     "13": { |                     "15": { | ||||||
|                         "question": "Recycling von Kunststoffverpackungen" |                         "question": "Recycling von Kunststoffverpackungen" | ||||||
|                     }, |                     }, | ||||||
|                     "14": { |                     "16": { | ||||||
|                         "question": "Recycling von Kunststoffen" |                         "question": "Recycling von Kunststoffen" | ||||||
|                     }, |                     }, | ||||||
|                     "15": { |                     "17": { | ||||||
|                         "question": "Recycling von Metallschrott" |                         "question": "Recycling von Metallschrott" | ||||||
|                     }, |                     }, | ||||||
|                     "16": { |                     "18": { | ||||||
|                         "question": "Recycling von Elektrokleingeräten" |                         "question": "Recycling von Elektrokleingeräten" | ||||||
|                     }, |                     }, | ||||||
|                     "17": { |                     "19": { | ||||||
|                         "question": "Recycling von Restabfällen" |                         "question": "Recycling von Restabfällen" | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|  | @ -6411,49 +6411,49 @@ | ||||||
|                     "5": { |                     "5": { | ||||||
|                         "then": "Motoröl kann hier recycelt werden" |                         "then": "Motoröl kann hier recycelt werden" | ||||||
|                     }, |                     }, | ||||||
|                     "6": { |                     "7": { | ||||||
|                         "then": "Grünabfälle können hier recycelt werden" |                         "then": "Grünabfälle können hier recycelt werden" | ||||||
|                     }, |                     }, | ||||||
|                     "7": { |                     "8": { | ||||||
|                         "then": "Bio-Abfall kann hier recycelt werden" |                         "then": "Bio-Abfall kann hier recycelt werden" | ||||||
|                     }, |                     }, | ||||||
|                     "8": { |                     "9": { | ||||||
|                         "then": "Glasflaschen können hier recycelt werden" |                         "then": "Glasflaschen können hier recycelt werden" | ||||||
|                     }, |                     }, | ||||||
|                     "9": { |                     "10": { | ||||||
|                         "then": "Glas kann hier recycelt werden" |                         "then": "Glas kann hier recycelt werden" | ||||||
|                     }, |                     }, | ||||||
|                     "10": { |                     "12": { | ||||||
|                         "then": "Zeitungen können hier recycelt werden" |                         "then": "Zeitungen können hier recycelt werden" | ||||||
|                     }, |                     }, | ||||||
|                     "11": { |                     "13": { | ||||||
|                         "then": "Papier kann hier recycelt werden" |                         "then": "Papier kann hier recycelt werden" | ||||||
|                     }, |                     }, | ||||||
|                     "12": { |                     "14": { | ||||||
|                         "then": "Plastikflaschen können hier recycelt werden" |                         "then": "Plastikflaschen können hier recycelt werden" | ||||||
|                     }, |                     }, | ||||||
|                     "13": { |                     "15": { | ||||||
|                         "then": "Kunststoffverpackungen können hier recycelt werden" |                         "then": "Kunststoffverpackungen können hier recycelt werden" | ||||||
|                     }, |                     }, | ||||||
|                     "14": { |                     "16": { | ||||||
|                         "then": "Kunststoff kann hier recycelt werden" |                         "then": "Kunststoff kann hier recycelt werden" | ||||||
|                     }, |                     }, | ||||||
|                     "15": { |                     "17": { | ||||||
|                         "then": "Metallschrott kann hier recycelt werden" |                         "then": "Metallschrott kann hier recycelt werden" | ||||||
|                     }, |                     }, | ||||||
|                     "16": { |                     "18": { | ||||||
|                         "then": "Schuhe können hier recycelt werden" |                         "then": "Schuhe können hier recycelt werden" | ||||||
|                     }, |                     }, | ||||||
|                     "17": { |  | ||||||
|                         "then": "Elektrokleingeräte können hier recycelt werden" |  | ||||||
|                     }, |  | ||||||
|                     "18": { |  | ||||||
|                         "then": "Elektrokleingeräte können hier recycelt werden" |  | ||||||
|                     }, |  | ||||||
|                     "19": { |                     "19": { | ||||||
|                         "then": "Nadeln können hier recycelt werden" |                         "then": "Elektrokleingeräte können hier recycelt werden" | ||||||
|                     }, |                     }, | ||||||
|                     "20": { |                     "20": { | ||||||
|  |                         "then": "Elektrokleingeräte können hier recycelt werden" | ||||||
|  |                     }, | ||||||
|  |                     "21": { | ||||||
|  |                         "then": "Nadeln können hier recycelt werden" | ||||||
|  |                     }, | ||||||
|  |                     "22": { | ||||||
|                         "then": "Restmüll kann hier recycelt werden" |                         "then": "Restmüll kann hier recycelt werden" | ||||||
|                     } |                     } | ||||||
|                 }, |                 }, | ||||||
|  | @ -8308,9 +8308,6 @@ | ||||||
|             "render": "Mülltonne" |             "render": "Mülltonne" | ||||||
|         } |         } | ||||||
|     }, |     }, | ||||||
|     "watermill": { |  | ||||||
|         "name": "Wassermühlen" |  | ||||||
|     }, |  | ||||||
|     "windturbine": { |     "windturbine": { | ||||||
|         "description": "Moderne Windmühlen zur Stromerzeugung", |         "description": "Moderne Windmühlen zur Stromerzeugung", | ||||||
|         "name": "Windräder", |         "name": "Windräder", | ||||||
|  |  | ||||||
|  | @ -169,6 +169,15 @@ | ||||||
|     }, |     }, | ||||||
|     "atm": { |     "atm": { | ||||||
|         "description": "ATMS to withdraw money", |         "description": "ATMS to withdraw money", | ||||||
|  |         "filter": { | ||||||
|  |             "1": { | ||||||
|  |                 "options": { | ||||||
|  |                     "0": { | ||||||
|  |                         "question": "With speech output" | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         }, | ||||||
|         "name": "ATMs", |         "name": "ATMs", | ||||||
|         "presets": { |         "presets": { | ||||||
|             "0": { |             "0": { | ||||||
|  | @ -186,6 +195,34 @@ | ||||||
|                 "question": "What brand is this ATM?", |                 "question": "What brand is this ATM?", | ||||||
|                 "render": "The brand of this ATM is {brand}" |                 "render": "The brand of this ATM is {brand}" | ||||||
|             }, |             }, | ||||||
|  |             "cash_in": { | ||||||
|  |                 "mappings": { | ||||||
|  |                     "0": { | ||||||
|  |                         "then": "You probably cannot deposit cash into this ATM" | ||||||
|  |                     }, | ||||||
|  |                     "1": { | ||||||
|  |                         "then": "You can deposit cash into this ATM" | ||||||
|  |                     }, | ||||||
|  |                     "2": { | ||||||
|  |                         "then": "You cannot deposit cash into this ATM" | ||||||
|  |                     } | ||||||
|  |                 }, | ||||||
|  |                 "question": "Can you deposit cash into this ATM?" | ||||||
|  |             }, | ||||||
|  |             "cash_out": { | ||||||
|  |                 "mappings": { | ||||||
|  |                     "0": { | ||||||
|  |                         "then": "You can withdraw cash from this ATM" | ||||||
|  |                     }, | ||||||
|  |                     "1": { | ||||||
|  |                         "then": "You can withdraw cash from this ATM" | ||||||
|  |                     }, | ||||||
|  |                     "2": { | ||||||
|  |                         "then": "You cannot withdraw cash from this ATM" | ||||||
|  |                     } | ||||||
|  |                 }, | ||||||
|  |                 "question": "Can you withdraw cash from this ATM?" | ||||||
|  |             }, | ||||||
|             "name": { |             "name": { | ||||||
|                 "render": "The name of this ATM is {name}" |                 "render": "The name of this ATM is {name}" | ||||||
|             }, |             }, | ||||||
|  | @ -195,6 +232,26 @@ | ||||||
|                 }, |                 }, | ||||||
|                 "question": "What company operates this ATM?", |                 "question": "What company operates this ATM?", | ||||||
|                 "render": "The ATM is operated by {operator}" |                 "render": "The ATM is operated by {operator}" | ||||||
|  |             }, | ||||||
|  |             "speech_output": { | ||||||
|  |                 "mappings": { | ||||||
|  |                     "0": { | ||||||
|  |                         "then": "This ATM has speech output, usually available through a headphone jack" | ||||||
|  |                     }, | ||||||
|  |                     "1": { | ||||||
|  |                         "then": "This ATM does not have speech output" | ||||||
|  |                     } | ||||||
|  |                 }, | ||||||
|  |                 "question": "Does this ATM have speech output for visually impaired users?" | ||||||
|  |             }, | ||||||
|  |             "speech_output_language": { | ||||||
|  |                 "render": { | ||||||
|  |                     "special": { | ||||||
|  |                         "question": "In which languages does this ATM have speech output?", | ||||||
|  |                         "render_list_item": "This ATM has speech output in {language():font-bold}", | ||||||
|  |                         "render_single_language": "This ATM has speech output in {language():font-bold}" | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|             } |             } | ||||||
|         }, |         }, | ||||||
|         "title": { |         "title": { | ||||||
|  | @ -6323,36 +6380,42 @@ | ||||||
|                         "question": "Recycling of engine oil" |                         "question": "Recycling of engine oil" | ||||||
|                     }, |                     }, | ||||||
|                     "7": { |                     "7": { | ||||||
|                         "question": "Recycling of green waste" |                         "question": "Recycling of fluorescent tubes" | ||||||
|                     }, |                     }, | ||||||
|                     "8": { |                     "8": { | ||||||
|                         "question": "Recycling of glass bottles" |                         "question": "Recycling of green waste" | ||||||
|                     }, |                     }, | ||||||
|                     "9": { |                     "9": { | ||||||
|                         "question": "Recycling of glass" |                         "question": "Recycling of glass bottles" | ||||||
|                     }, |                     }, | ||||||
|                     "10": { |                     "10": { | ||||||
|                         "question": "Recycling of newspapers" |                         "question": "Recycling of glass" | ||||||
|                     }, |                     }, | ||||||
|                     "11": { |                     "11": { | ||||||
|                         "question": "Recycling of paper" |                         "question": "Recycling of light bulbs" | ||||||
|                     }, |                     }, | ||||||
|                     "12": { |                     "12": { | ||||||
|                         "question": "Recycling of plastic bottles" |                         "question": "Recycling of newspapers" | ||||||
|                     }, |                     }, | ||||||
|                     "13": { |                     "13": { | ||||||
|                         "question": "Recycling of plastic packaging" |                         "question": "Recycling of paper" | ||||||
|                     }, |                     }, | ||||||
|                     "14": { |                     "14": { | ||||||
|                         "question": "Recycling of plastic" |                         "question": "Recycling of plastic bottles" | ||||||
|                     }, |                     }, | ||||||
|                     "15": { |                     "15": { | ||||||
|                         "question": "Recycling of scrap metal" |                         "question": "Recycling of plastic packaging" | ||||||
|                     }, |                     }, | ||||||
|                     "16": { |                     "16": { | ||||||
|                         "question": "Recycling of small electrical appliances" |                         "question": "Recycling of plastic" | ||||||
|                     }, |                     }, | ||||||
|                     "17": { |                     "17": { | ||||||
|  |                         "question": "Recycling of scrap metal" | ||||||
|  |                     }, | ||||||
|  |                     "18": { | ||||||
|  |                         "question": "Recycling of small electrical appliances" | ||||||
|  |                     }, | ||||||
|  |                     "19": { | ||||||
|                         "question": "Recycling of residual waste" |                         "question": "Recycling of residual waste" | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|  | @ -6412,48 +6475,54 @@ | ||||||
|                         "then": "Engine oil can be recycled here" |                         "then": "Engine oil can be recycled here" | ||||||
|                     }, |                     }, | ||||||
|                     "6": { |                     "6": { | ||||||
|                         "then": "Green waste can be recycled here" |                         "then": "Fluorescent tubes can be recycled here" | ||||||
|                     }, |                     }, | ||||||
|                     "7": { |                     "7": { | ||||||
|                         "then": "Organic waste can be recycled here" |                         "then": "Green waste can be recycled here" | ||||||
|                     }, |                     }, | ||||||
|                     "8": { |                     "8": { | ||||||
|                         "then": "Glass bottles can be recycled here" |                         "then": "Organic waste can be recycled here" | ||||||
|                     }, |                     }, | ||||||
|                     "9": { |                     "9": { | ||||||
|                         "then": "Glass can be recycled here" |                         "then": "Glass bottles can be recycled here" | ||||||
|                     }, |                     }, | ||||||
|                     "10": { |                     "10": { | ||||||
|                         "then": "Newspapers can be recycled here" |                         "then": "Glass can be recycled here" | ||||||
|                     }, |                     }, | ||||||
|                     "11": { |                     "11": { | ||||||
|                         "then": "Paper can be recycled here" |                         "then": "Light bulbs can be recycled here" | ||||||
|                     }, |                     }, | ||||||
|                     "12": { |                     "12": { | ||||||
|                         "then": "Plastic bottles can be recycled here" |                         "then": "Newspapers can be recycled here" | ||||||
|                     }, |                     }, | ||||||
|                     "13": { |                     "13": { | ||||||
|                         "then": "Plastic packaging can be recycled here" |                         "then": "Paper can be recycled here" | ||||||
|                     }, |                     }, | ||||||
|                     "14": { |                     "14": { | ||||||
|                         "then": "Plastic can be recycled here" |                         "then": "Plastic bottles can be recycled here" | ||||||
|                     }, |                     }, | ||||||
|                     "15": { |                     "15": { | ||||||
|                         "then": "Scrap metal can be recycled here" |                         "then": "Plastic packaging can be recycled here" | ||||||
|                     }, |                     }, | ||||||
|                     "16": { |                     "16": { | ||||||
|                         "then": "Shoes can be recycled here" |                         "then": "Plastic can be recycled here" | ||||||
|                     }, |                     }, | ||||||
|                     "17": { |                     "17": { | ||||||
|                         "then": "Small electrical appliances can be recycled here" |                         "then": "Scrap metal can be recycled here" | ||||||
|                     }, |                     }, | ||||||
|                     "18": { |                     "18": { | ||||||
|                         "then": "Small electrical appliances can be recycled here" |                         "then": "Shoes can be recycled here" | ||||||
|                     }, |                     }, | ||||||
|                     "19": { |                     "19": { | ||||||
|                         "then": "Needles can be recycled here" |                         "then": "Small electrical appliances can be recycled here" | ||||||
|                     }, |                     }, | ||||||
|                     "20": { |                     "20": { | ||||||
|  |                         "then": "Small electrical appliances can be recycled here" | ||||||
|  |                     }, | ||||||
|  |                     "21": { | ||||||
|  |                         "then": "Needles can be recycled here" | ||||||
|  |                     }, | ||||||
|  |                     "22": { | ||||||
|                         "then": "Residual waste can be recycled here" |                         "then": "Residual waste can be recycled here" | ||||||
|                     } |                     } | ||||||
|                 }, |                 }, | ||||||
|  | @ -7407,6 +7476,7 @@ | ||||||
|         "name": "Ticket Validators", |         "name": "Ticket Validators", | ||||||
|         "presets": { |         "presets": { | ||||||
|             "0": { |             "0": { | ||||||
|  |                 "description": "A ticket validator to validate a public transport ticket. This can be either a digital reader, reading a card or ticket, or a machine stamping or punching a ticket.", | ||||||
|                 "title": "a ticket validator" |                 "title": "a ticket validator" | ||||||
|             } |             } | ||||||
|         }, |         }, | ||||||
|  | @ -8308,9 +8378,6 @@ | ||||||
|             "render": "Waste Disposal" |             "render": "Waste Disposal" | ||||||
|         } |         } | ||||||
|     }, |     }, | ||||||
|     "watermill": { |  | ||||||
|         "name": "Watermill" |  | ||||||
|     }, |  | ||||||
|     "windturbine": { |     "windturbine": { | ||||||
|         "description": "Modern windmills generating electricity", |         "description": "Modern windmills generating electricity", | ||||||
|         "name": "wind turbine", |         "name": "wind turbine", | ||||||
|  |  | ||||||
|  | @ -3189,28 +3189,28 @@ | ||||||
|                     "6": { |                     "6": { | ||||||
|                         "question": "Reciclaje de aceite de motor" |                         "question": "Reciclaje de aceite de motor" | ||||||
|                     }, |                     }, | ||||||
|                     "8": { |                     "9": { | ||||||
|                         "question": "Reciclaje de botellas de cristal" |                         "question": "Reciclaje de botellas de cristal" | ||||||
|                     }, |                     }, | ||||||
|                     "9": { |                     "10": { | ||||||
|                         "question": "Reciclaje de cristal" |                         "question": "Reciclaje de cristal" | ||||||
|                     }, |                     }, | ||||||
|                     "10": { |                     "12": { | ||||||
|                         "question": "Reciclaje de periódicos" |                         "question": "Reciclaje de periódicos" | ||||||
|                     }, |                     }, | ||||||
|                     "11": { |                     "13": { | ||||||
|                         "question": "Reciclaje de papel" |                         "question": "Reciclaje de papel" | ||||||
|                     }, |                     }, | ||||||
|                     "12": { |                     "14": { | ||||||
|                         "question": "Reciclaje de botellas de papel" |                         "question": "Reciclaje de botellas de papel" | ||||||
|                     }, |                     }, | ||||||
|                     "13": { |                     "15": { | ||||||
|                         "question": "Reciclaje de embalajes plásticos" |                         "question": "Reciclaje de embalajes plásticos" | ||||||
|                     }, |                     }, | ||||||
|                     "14": { |                     "16": { | ||||||
|                         "question": "Reciclaje de plástico" |                         "question": "Reciclaje de plástico" | ||||||
|                     }, |                     }, | ||||||
|                     "15": { |                     "17": { | ||||||
|                         "question": "Reciclaje de chatarra" |                         "question": "Reciclaje de chatarra" | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|  | @ -3266,34 +3266,34 @@ | ||||||
|                     "5": { |                     "5": { | ||||||
|                         "then": "Aquí se puede reciclar aceite de motor" |                         "then": "Aquí se puede reciclar aceite de motor" | ||||||
|                     }, |                     }, | ||||||
|                     "7": { |                     "8": { | ||||||
|                         "then": "Aquí se pueden reciclar residuos orgánicos" |                         "then": "Aquí se pueden reciclar residuos orgánicos" | ||||||
|                     }, |                     }, | ||||||
|                     "8": { |                     "9": { | ||||||
|                         "then": "Aquí se pueden reciclar botellas de cristal" |                         "then": "Aquí se pueden reciclar botellas de cristal" | ||||||
|                     }, |                     }, | ||||||
|                     "9": { |                     "10": { | ||||||
|                         "then": "Aquí se puede reciclar cristal" |                         "then": "Aquí se puede reciclar cristal" | ||||||
|                     }, |                     }, | ||||||
|                     "10": { |                     "12": { | ||||||
|                         "then": "Aquí se pueden reciclar periódicos" |                         "then": "Aquí se pueden reciclar periódicos" | ||||||
|                     }, |                     }, | ||||||
|                     "11": { |                     "13": { | ||||||
|                         "then": "Aquí se puede reciclar papel" |                         "then": "Aquí se puede reciclar papel" | ||||||
|                     }, |                     }, | ||||||
|                     "12": { |                     "14": { | ||||||
|                         "then": "Aquí se pueden reciclar botellas de plástico" |                         "then": "Aquí se pueden reciclar botellas de plástico" | ||||||
|                     }, |                     }, | ||||||
|                     "13": { |                     "15": { | ||||||
|                         "then": "Aquí se pueden reciclar embalajes plásticos" |                         "then": "Aquí se pueden reciclar embalajes plásticos" | ||||||
|                     }, |                     }, | ||||||
|                     "14": { |                     "16": { | ||||||
|                         "then": "Aquí se puede reciclar plástico" |                         "then": "Aquí se puede reciclar plástico" | ||||||
|                     }, |                     }, | ||||||
|                     "15": { |                     "17": { | ||||||
|                         "then": "Aquí se puede reciclar chatarra" |                         "then": "Aquí se puede reciclar chatarra" | ||||||
|                     }, |                     }, | ||||||
|                     "16": { |                     "18": { | ||||||
|                         "then": "Aquí se pueden reciclar zapatos" |                         "then": "Aquí se pueden reciclar zapatos" | ||||||
|                     } |                     } | ||||||
|                 }, |                 }, | ||||||
|  |  | ||||||
|  | @ -1766,37 +1766,37 @@ | ||||||
|                     "6": { |                     "6": { | ||||||
|                         "question": "Riciclo di olio da motore" |                         "question": "Riciclo di olio da motore" | ||||||
|                     }, |                     }, | ||||||
|                     "7": { |                     "8": { | ||||||
|                         "question": "Riciclo di umido" |                         "question": "Riciclo di umido" | ||||||
|                     }, |                     }, | ||||||
|                     "8": { |                     "9": { | ||||||
|                         "question": "Riciclo di bottiglie di vetro" |                         "question": "Riciclo di bottiglie di vetro" | ||||||
|                     }, |                     }, | ||||||
|                     "9": { |                     "10": { | ||||||
|                         "question": "Riciclo di vetro" |                         "question": "Riciclo di vetro" | ||||||
|                     }, |                     }, | ||||||
|                     "10": { |                     "12": { | ||||||
|                         "question": "Riciclo di giornali" |                         "question": "Riciclo di giornali" | ||||||
|                     }, |                     }, | ||||||
|                     "11": { |                     "13": { | ||||||
|                         "question": "Riciclo di carta" |                         "question": "Riciclo di carta" | ||||||
|                     }, |                     }, | ||||||
|                     "12": { |                     "14": { | ||||||
|                         "question": "Riciclo di bottiglie di plastica" |                         "question": "Riciclo di bottiglie di plastica" | ||||||
|                     }, |                     }, | ||||||
|                     "13": { |                     "15": { | ||||||
|                         "question": "Riciclo di confezioni di plastica" |                         "question": "Riciclo di confezioni di plastica" | ||||||
|                     }, |                     }, | ||||||
|                     "14": { |                     "16": { | ||||||
|                         "question": "Riciclo di plastica" |                         "question": "Riciclo di plastica" | ||||||
|                     }, |                     }, | ||||||
|                     "15": { |                     "17": { | ||||||
|                         "question": "Riciclo di rottami metallici" |                         "question": "Riciclo di rottami metallici" | ||||||
|                     }, |                     }, | ||||||
|                     "16": { |                     "18": { | ||||||
|                         "question": "Riciclo di piccoli elettrodomestici" |                         "question": "Riciclo di piccoli elettrodomestici" | ||||||
|                     }, |                     }, | ||||||
|                     "17": { |                     "19": { | ||||||
|                         "question": "Riciclo di secco" |                         "question": "Riciclo di secco" | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|  | @ -1855,49 +1855,49 @@ | ||||||
|                     "5": { |                     "5": { | ||||||
|                         "then": "Olio di motore" |                         "then": "Olio di motore" | ||||||
|                     }, |                     }, | ||||||
|                     "6": { |                     "7": { | ||||||
|                         "then": "Verde" |                         "then": "Verde" | ||||||
|                     }, |                     }, | ||||||
|                     "7": { |                     "8": { | ||||||
|                         "then": "Umido" |                         "then": "Umido" | ||||||
|                     }, |                     }, | ||||||
|                     "8": { |                     "9": { | ||||||
|                         "then": "Bottiglie di vetro" |                         "then": "Bottiglie di vetro" | ||||||
|                     }, |                     }, | ||||||
|                     "9": { |                     "10": { | ||||||
|                         "then": "Vetro" |                         "then": "Vetro" | ||||||
|                     }, |                     }, | ||||||
|                     "10": { |                     "12": { | ||||||
|                         "then": "Giornali" |                         "then": "Giornali" | ||||||
|                     }, |                     }, | ||||||
|                     "11": { |                     "13": { | ||||||
|                         "then": "Carta" |                         "then": "Carta" | ||||||
|                     }, |                     }, | ||||||
|                     "12": { |                     "14": { | ||||||
|                         "then": "Bottiglie di platica" |                         "then": "Bottiglie di platica" | ||||||
|                     }, |                     }, | ||||||
|                     "13": { |                     "15": { | ||||||
|                         "then": "Confezioni di plastica" |                         "then": "Confezioni di plastica" | ||||||
|                     }, |                     }, | ||||||
|                     "14": { |                     "16": { | ||||||
|                         "then": "Plastica" |                         "then": "Plastica" | ||||||
|                     }, |                     }, | ||||||
|                     "15": { |                     "17": { | ||||||
|                         "then": "Rottami metallici" |                         "then": "Rottami metallici" | ||||||
|                     }, |                     }, | ||||||
|                     "16": { |                     "18": { | ||||||
|                         "then": "Scarpe" |                         "then": "Scarpe" | ||||||
|                     }, |                     }, | ||||||
|                     "17": { |  | ||||||
|                         "then": "Piccoli elettrodomestici" |  | ||||||
|                     }, |  | ||||||
|                     "18": { |  | ||||||
|                         "then": "Piccoli elettrodomestici" |  | ||||||
|                     }, |  | ||||||
|                     "19": { |                     "19": { | ||||||
|                         "then": "Aghi e oggetti appuntiti" |                         "then": "Piccoli elettrodomestici" | ||||||
|                     }, |                     }, | ||||||
|                     "20": { |                     "20": { | ||||||
|  |                         "then": "Piccoli elettrodomestici" | ||||||
|  |                     }, | ||||||
|  |                     "21": { | ||||||
|  |                         "then": "Aghi e oggetti appuntiti" | ||||||
|  |                     }, | ||||||
|  |                     "22": { | ||||||
|                         "then": "Secco" |                         "then": "Secco" | ||||||
|                     } |                     } | ||||||
|                 }, |                 }, | ||||||
|  |  | ||||||
|  | @ -5988,36 +5988,42 @@ | ||||||
|                         "question": "Recycling van motorolie" |                         "question": "Recycling van motorolie" | ||||||
|                     }, |                     }, | ||||||
|                     "7": { |                     "7": { | ||||||
|                         "question": "Recycling van groen afval" |                         "question": "Recycling van tl-buizen" | ||||||
|                     }, |                     }, | ||||||
|                     "8": { |                     "8": { | ||||||
|                         "question": "Recycling van glazen flessen" |                         "question": "Recycling van groen afval" | ||||||
|                     }, |                     }, | ||||||
|                     "9": { |                     "9": { | ||||||
|                         "question": "Recycling van glas" |                         "question": "Recycling van glazen flessen" | ||||||
|                     }, |                     }, | ||||||
|                     "10": { |                     "10": { | ||||||
|                         "question": "Recycling van kranten" |                         "question": "Recycling van glas" | ||||||
|                     }, |                     }, | ||||||
|                     "11": { |                     "11": { | ||||||
|                         "question": "Recycling van papier" |                         "question": "Recycling van lampen" | ||||||
|                     }, |                     }, | ||||||
|                     "12": { |                     "12": { | ||||||
|                         "question": "Recycling van plastic flessen" |                         "question": "Recycling van kranten" | ||||||
|                     }, |                     }, | ||||||
|                     "13": { |                     "13": { | ||||||
|                         "question": "Recycling van plastic verpakking" |                         "question": "Recycling van papier" | ||||||
|                     }, |                     }, | ||||||
|                     "14": { |                     "14": { | ||||||
|                         "question": "Recycling van plastic" |                         "question": "Recycling van plastic flessen" | ||||||
|                     }, |                     }, | ||||||
|                     "15": { |                     "15": { | ||||||
|                         "question": "Recycling van oud metaal" |                         "question": "Recycling van plastic verpakking" | ||||||
|                     }, |                     }, | ||||||
|                     "16": { |                     "16": { | ||||||
|                         "question": "Recycling van kleine elektrische apparaten" |                         "question": "Recycling van plastic" | ||||||
|                     }, |                     }, | ||||||
|                     "17": { |                     "17": { | ||||||
|  |                         "question": "Recycling van oud metaal" | ||||||
|  |                     }, | ||||||
|  |                     "18": { | ||||||
|  |                         "question": "Recycling van kleine elektrische apparaten" | ||||||
|  |                     }, | ||||||
|  |                     "19": { | ||||||
|                         "question": "Recycling van restafval" |                         "question": "Recycling van restafval" | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|  | @ -6077,48 +6083,54 @@ | ||||||
|                         "then": "Motorolie kan hier gerecycled worden" |                         "then": "Motorolie kan hier gerecycled worden" | ||||||
|                     }, |                     }, | ||||||
|                     "6": { |                     "6": { | ||||||
|                         "then": "Groen afval kan hier gerecycled worden" |                         "then": "TL-buizen kunnen hier gerecycled worden" | ||||||
|                     }, |                     }, | ||||||
|                     "7": { |                     "7": { | ||||||
|                         "then": "Organisch afval kan hier gerecycled worden" |                         "then": "Groen afval kan hier gerecycled worden" | ||||||
|                     }, |                     }, | ||||||
|                     "8": { |                     "8": { | ||||||
|                         "then": "Glazen flessen kunnen hier gerecycled worden" |                         "then": "Organisch afval kan hier gerecycled worden" | ||||||
|                     }, |                     }, | ||||||
|                     "9": { |                     "9": { | ||||||
|                         "then": "Glas kan hier gerecycled worden" |                         "then": "Glazen flessen kunnen hier gerecycled worden" | ||||||
|                     }, |                     }, | ||||||
|                     "10": { |                     "10": { | ||||||
|                         "then": "Kranten kunnen hier gerecycled worden" |                         "then": "Glas kan hier gerecycled worden" | ||||||
|                     }, |                     }, | ||||||
|                     "11": { |                     "11": { | ||||||
|                         "then": "Papier kan hier gerecycled worden" |                         "then": "Lampen kunnen hier gerecycled worden" | ||||||
|                     }, |                     }, | ||||||
|                     "12": { |                     "12": { | ||||||
|                         "then": "Plastic flessen kunnen hier gerecycled worden" |                         "then": "Kranten kunnen hier gerecycled worden" | ||||||
|                     }, |                     }, | ||||||
|                     "13": { |                     "13": { | ||||||
|                         "then": "Plastic verpakking kan hier gerecycled worden" |                         "then": "Papier kan hier gerecycled worden" | ||||||
|                     }, |                     }, | ||||||
|                     "14": { |                     "14": { | ||||||
|                         "then": "Plastic kan hier gerecycled worden" |                         "then": "Plastic flessen kunnen hier gerecycled worden" | ||||||
|                     }, |                     }, | ||||||
|                     "15": { |                     "15": { | ||||||
|                         "then": "Oud metaal kan hier gerecycled worden" |                         "then": "Plastic verpakking kan hier gerecycled worden" | ||||||
|                     }, |                     }, | ||||||
|                     "16": { |                     "16": { | ||||||
|                         "then": "Schoenen kunnen hier gerecycled worden" |                         "then": "Plastic kan hier gerecycled worden" | ||||||
|                     }, |                     }, | ||||||
|                     "17": { |                     "17": { | ||||||
|                         "then": "Kleine elektrische apparaten kunnen hier gerecycled worden" |                         "then": "Oud metaal kan hier gerecycled worden" | ||||||
|                     }, |                     }, | ||||||
|                     "18": { |                     "18": { | ||||||
|                         "then": "Kleine elektrische apparaten kunnen hier gerecycled worden" |                         "then": "Schoenen kunnen hier gerecycled worden" | ||||||
|                     }, |                     }, | ||||||
|                     "19": { |                     "19": { | ||||||
|                         "then": "Injectienaalden kunnen hier gerecycled worden" |                         "then": "Kleine elektrische apparaten kunnen hier gerecycled worden" | ||||||
|                     }, |                     }, | ||||||
|                     "20": { |                     "20": { | ||||||
|  |                         "then": "Kleine elektrische apparaten kunnen hier gerecycled worden" | ||||||
|  |                     }, | ||||||
|  |                     "21": { | ||||||
|  |                         "then": "Injectienaalden kunnen hier gerecycled worden" | ||||||
|  |                     }, | ||||||
|  |                     "22": { | ||||||
|                         "then": "Restafval kan hier gerecycled worden" |                         "then": "Restafval kan hier gerecycled worden" | ||||||
|                     } |                     } | ||||||
|                 }, |                 }, | ||||||
|  | @ -7872,59 +7884,6 @@ | ||||||
|             "render": "Afvalbak" |             "render": "Afvalbak" | ||||||
|         } |         } | ||||||
|     }, |     }, | ||||||
|     "watermill": { |  | ||||||
|         "description": "Watermolens", |  | ||||||
|         "name": "Watermolens", |  | ||||||
|         "tagRenderings": { |  | ||||||
|             "Access tag": { |  | ||||||
|                 "mappings": { |  | ||||||
|                     "0": { |  | ||||||
|                         "then": "Vrij toegankelijk" |  | ||||||
|                     }, |  | ||||||
|                     "1": { |  | ||||||
|                         "then": "Niet toegankelijk" |  | ||||||
|                     }, |  | ||||||
|                     "2": { |  | ||||||
|                         "then": "Niet toegankelijk, want privégebied" |  | ||||||
|                     }, |  | ||||||
|                     "3": { |  | ||||||
|                         "then": "Toegankelijk, ondanks dat het privegebied is" |  | ||||||
|                     }, |  | ||||||
|                     "4": { |  | ||||||
|                         "then": "Enkel toegankelijk met een gids of tijdens een activiteit" |  | ||||||
|                     }, |  | ||||||
|                     "5": { |  | ||||||
|                         "then": "Toegankelijk mits betaling" |  | ||||||
|                     } |  | ||||||
|                 }, |  | ||||||
|                 "question": "Is dit gebied toegankelijk?", |  | ||||||
|                 "render": "De toegankelijkheid van dit gebied is: {access:description}" |  | ||||||
|             }, |  | ||||||
|             "Operator tag": { |  | ||||||
|                 "mappings": { |  | ||||||
|                     "0": { |  | ||||||
|                         "then": "Dit gebied wordt beheerd door Natuurpunt" |  | ||||||
|                     }, |  | ||||||
|                     "1": { |  | ||||||
|                         "then": "Dit gebied wordt beheerd door {operator}" |  | ||||||
|                     } |  | ||||||
|                 }, |  | ||||||
|                 "question": "Wie beheert dit pad?", |  | ||||||
|                 "render": "Beheer door {operator}" |  | ||||||
|             } |  | ||||||
|         }, |  | ||||||
|         "title": { |  | ||||||
|             "mappings": { |  | ||||||
|                 "0": { |  | ||||||
|                     "then": "{name:nl}" |  | ||||||
|                 }, |  | ||||||
|                 "1": { |  | ||||||
|                     "then": "{name}" |  | ||||||
|                 } |  | ||||||
|             }, |  | ||||||
|             "render": "Watermolens" |  | ||||||
|         } |  | ||||||
|     }, |  | ||||||
|     "windturbine": { |     "windturbine": { | ||||||
|         "description": "Windturbines (moderne windmolens die elektriciteit genereren)", |         "description": "Windturbines (moderne windmolens die elektriciteit genereren)", | ||||||
|         "name": "windturbine", |         "name": "windturbine", | ||||||
|  |  | ||||||
|  | @ -179,6 +179,24 @@ | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         }, |         }, | ||||||
|  |         "payment-options-split": { | ||||||
|  |             "override": { | ||||||
|  |                 "mappings+": { | ||||||
|  |                     "0": { | ||||||
|  |                         "then": "Muntgeld wordt hier aanvaard" | ||||||
|  |                     }, | ||||||
|  |                     "1": { | ||||||
|  |                         "then": "Bankbiljetten worden hier aanvaard" | ||||||
|  |                     }, | ||||||
|  |                     "2": { | ||||||
|  |                         "then": "Betalen met debetkaarten kan hier" | ||||||
|  |                     }, | ||||||
|  |                     "3": { | ||||||
|  |                         "then": "Betalen met creditkaarten kan hier" | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         }, | ||||||
|         "phone": { |         "phone": { | ||||||
|             "question": "Wat is het telefoonnummer van {title()}?" |             "question": "Wat is het telefoonnummer van {title()}?" | ||||||
|         }, |         }, | ||||||
|  |  | ||||||
|  | @ -443,14 +443,14 @@ | ||||||
|     }, |     }, | ||||||
|     "onwheels": { |     "onwheels": { | ||||||
|         "layers": { |         "layers": { | ||||||
|             "18": { |             "19": { | ||||||
|                 "override": { |                 "override": { | ||||||
|                     "=title": { |                     "=title": { | ||||||
|                         "render": "Estadístiques" |                         "render": "Estadístiques" | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|             }, |             }, | ||||||
|             "19": { |             "20": { | ||||||
|                 "override": { |                 "override": { | ||||||
|                     "+tagRenderings": { |                     "+tagRenderings": { | ||||||
|                         "0": { |                         "0": { | ||||||
|  |  | ||||||
|  | @ -680,14 +680,14 @@ | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|             }, |             }, | ||||||
|             "18": { |             "19": { | ||||||
|                 "override": { |                 "override": { | ||||||
|                     "=title": { |                     "=title": { | ||||||
|                         "render": "Statistikker" |                         "render": "Statistikker" | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|             }, |             }, | ||||||
|             "19": { |             "20": { | ||||||
|                 "override": { |                 "override": { | ||||||
|                     "+tagRenderings": { |                     "+tagRenderings": { | ||||||
|                         "0": { |                         "0": { | ||||||
|  |  | ||||||
|  | @ -909,14 +909,14 @@ | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|             }, |             }, | ||||||
|             "18": { |             "19": { | ||||||
|                 "override": { |                 "override": { | ||||||
|                     "=title": { |                     "=title": { | ||||||
|                         "render": "Statistik" |                         "render": "Statistik" | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|             }, |             }, | ||||||
|             "19": { |             "20": { | ||||||
|                 "override": { |                 "override": { | ||||||
|                     "+tagRenderings": { |                     "+tagRenderings": { | ||||||
|                         "0": { |                         "0": { | ||||||
|  |  | ||||||
|  | @ -909,14 +909,14 @@ | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|             }, |             }, | ||||||
|             "18": { |             "19": { | ||||||
|                 "override": { |                 "override": { | ||||||
|                     "=title": { |                     "=title": { | ||||||
|                         "render": "Statistics" |                         "render": "Statistics" | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|             }, |             }, | ||||||
|             "19": { |             "20": { | ||||||
|                 "override": { |                 "override": { | ||||||
|                     "+tagRenderings": { |                     "+tagRenderings": { | ||||||
|                         "0": { |                         "0": { | ||||||
|  |  | ||||||
|  | @ -888,14 +888,14 @@ | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|             }, |             }, | ||||||
|             "18": { |             "19": { | ||||||
|                 "override": { |                 "override": { | ||||||
|                     "=title": { |                     "=title": { | ||||||
|                         "render": "Statistiques" |                         "render": "Statistiques" | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|             }, |             }, | ||||||
|             "19": { |             "20": { | ||||||
|                 "override": { |                 "override": { | ||||||
|                     "+tagRenderings": { |                     "+tagRenderings": { | ||||||
|                         "0": { |                         "0": { | ||||||
|  |  | ||||||
|  | @ -441,14 +441,14 @@ | ||||||
|     }, |     }, | ||||||
|     "onwheels": { |     "onwheels": { | ||||||
|         "layers": { |         "layers": { | ||||||
|             "18": { |             "19": { | ||||||
|                 "override": { |                 "override": { | ||||||
|                     "=title": { |                     "=title": { | ||||||
|                         "render": "Statistikk" |                         "render": "Statistikk" | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|             }, |             }, | ||||||
|             "19": { |             "20": { | ||||||
|                 "override": { |                 "override": { | ||||||
|                     "+tagRenderings": { |                     "+tagRenderings": { | ||||||
|                         "0": { |                         "0": { | ||||||
|  |  | ||||||
|  | @ -1036,14 +1036,14 @@ | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|             }, |             }, | ||||||
|             "18": { |             "19": { | ||||||
|                 "override": { |                 "override": { | ||||||
|                     "=title": { |                     "=title": { | ||||||
|                         "render": "Statistieken" |                         "render": "Statistieken" | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|             }, |             }, | ||||||
|             "19": { |             "20": { | ||||||
|                 "override": { |                 "override": { | ||||||
|                     "+tagRenderings": { |                     "+tagRenderings": { | ||||||
|                         "0": { |                         "0": { | ||||||
|  |  | ||||||
|  | @ -168,14 +168,14 @@ | ||||||
|     }, |     }, | ||||||
|     "onwheels": { |     "onwheels": { | ||||||
|         "layers": { |         "layers": { | ||||||
|             "18": { |             "19": { | ||||||
|                 "override": { |                 "override": { | ||||||
|                     "=title": { |                     "=title": { | ||||||
|                         "render": "انکڑے" |                         "render": "انکڑے" | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|             }, |             }, | ||||||
|             "19": { |             "20": { | ||||||
|                 "override": { |                 "override": { | ||||||
|                     "+tagRenderings": { |                     "+tagRenderings": { | ||||||
|                         "0": { |                         "0": { | ||||||
|  |  | ||||||
							
								
								
									
										4119
									
								
								package-lock.json
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										4119
									
								
								package-lock.json
									
										
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							|  | @ -1,6 +1,6 @@ | ||||||
| { | { | ||||||
|   "name": "mapcomplete", |   "name": "mapcomplete", | ||||||
|   "version": "0.0.5", |   "version": "0.25.1", | ||||||
|   "repository": "https://github.com/pietervdvn/MapComplete", |   "repository": "https://github.com/pietervdvn/MapComplete", | ||||||
|   "description": "A small website to edit OSM easily", |   "description": "A small website to edit OSM easily", | ||||||
|   "bugs": "https://github.com/pietervdvn/MapComplete/issues", |   "bugs": "https://github.com/pietervdvn/MapComplete/issues", | ||||||
|  |  | ||||||
|  | @ -16,7 +16,13 @@ import SharedTagRenderings from "../Customizations/SharedTagRenderings" | ||||||
| import { writeFile } from "fs" | import { writeFile } from "fs" | ||||||
| import Translations from "../UI/i18n/Translations" | import Translations from "../UI/i18n/Translations" | ||||||
| import * as themeOverview from "../assets/generated/theme_overview.json" | import * as themeOverview from "../assets/generated/theme_overview.json" | ||||||
| 
 | import DefaultGUI from "../UI/DefaultGUI" | ||||||
|  | import FeaturePipelineState from "../Logic/State/FeaturePipelineState" | ||||||
|  | import LayoutConfig from "../Models/ThemeConfig/LayoutConfig" | ||||||
|  | import * as bookcases from "../assets/generated/themes/bookcases.json" | ||||||
|  | import { DefaultGuiState } from "../UI/DefaultGuiState" | ||||||
|  | import * as fakedom from "fake-dom" | ||||||
|  | import Hotkeys from "../UI/Base/Hotkeys" | ||||||
| function WriteFile( | function WriteFile( | ||||||
|     filename, |     filename, | ||||||
|     html: BaseUIElement, |     html: BaseUIElement, | ||||||
|  | @ -217,5 +223,13 @@ WriteFile("./Docs/URL_Parameters.md", QueryParameterDocumentation.GenerateQueryP | ||||||
|     "Logic/Web/QueryParameters.ts", |     "Logic/Web/QueryParameters.ts", | ||||||
|     "UI/QueryParameterDocumentation.ts", |     "UI/QueryParameterDocumentation.ts", | ||||||
| ]) | ]) | ||||||
|  | if (fakedom === undefined || window === undefined) { | ||||||
|  |     throw "FakeDom not initialized" | ||||||
|  | } | ||||||
|  | new DefaultGUI( | ||||||
|  |     new FeaturePipelineState(new LayoutConfig(<any>bookcases)), | ||||||
|  |     new DefaultGuiState() | ||||||
|  | ).setup() | ||||||
| 
 | 
 | ||||||
|  | WriteFile("./Docs/Hotkeys.md", Hotkeys.generateDocumentation(), []) | ||||||
| console.log("Generated docs") | console.log("Generated docs") | ||||||
|  |  | ||||||
|  | @ -279,7 +279,23 @@ function main(args: string[]) { | ||||||
|     const invalidLicenses = licenseInfos |     const invalidLicenses = licenseInfos | ||||||
|         .filter((l) => (l.license ?? "") === "") |         .filter((l) => (l.license ?? "") === "") | ||||||
|         .map((l) => `License for artwork ${l.path} is empty string or undefined`) |         .map((l) => `License for artwork ${l.path} is empty string or undefined`) | ||||||
|  | 
 | ||||||
|  |     let invalid = 0 | ||||||
|     for (const licenseInfo of licenseInfos) { |     for (const licenseInfo of licenseInfos) { | ||||||
|  |         const isTrivial = | ||||||
|  |             licenseInfo.license | ||||||
|  |                 .split(";") | ||||||
|  |                 .map((l) => l.trim().toLowerCase()) | ||||||
|  |                 .indexOf("trivial") >= 0 | ||||||
|  |         if (licenseInfo.sources.length + licenseInfo.authors.length == 0 && !isTrivial) { | ||||||
|  |             invalid++ | ||||||
|  |             invalidLicenses.push( | ||||||
|  |                 "Invalid license: No sources nor authors given in the license for " + | ||||||
|  |                     JSON.stringify(licenseInfo) | ||||||
|  |             ) | ||||||
|  |             continue | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|         for (const source of licenseInfo.sources) { |         for (const source of licenseInfo.sources) { | ||||||
|             if (source == "") { |             if (source == "") { | ||||||
|                 invalidLicenses.push( |                 invalidLicenses.push( | ||||||
|  | @ -294,7 +310,7 @@ function main(args: string[]) { | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     if (missingLicenses.length > 0) { |     if (missingLicenses.length > 0 || invalidLicenses.length) { | ||||||
|         const msg = `There are ${missingLicenses.length} licenses missing and ${invalidLicenses.length} invalid licenses.` |         const msg = `There are ${missingLicenses.length} licenses missing and ${invalidLicenses.length} invalid licenses.` | ||||||
|         console.log(missingLicenses.concat(invalidLicenses).join("\n")) |         console.log(missingLicenses.concat(invalidLicenses).join("\n")) | ||||||
|         console.error(msg) |         console.error(msg) | ||||||
|  |  | ||||||
|  | @ -7594,7 +7594,7 @@ describe("GenerateCache", () => { | ||||||
|         } |         } | ||||||
|         mkdirSync(dir + "np-cache") |         mkdirSync(dir + "np-cache") | ||||||
|         initDownloads( |         initDownloads( | ||||||
|             "(nwr%5B%22amenity%22%3D%22toilets%22%5D%3Bnwr%5B%22amenity%22%3D%22parking%22%5D%3Bnwr%5B%22amenity%22%3D%22bench%22%5D%3Bnwr%5B%22id%22%3D%22location_track%22%5D%3Bnwr%5B%22id%22%3D%22gps%22%5D%3Bnwr%5B%22information%22%3D%22board%22%5D%3Bnwr%5B%22leisure%22%3D%22picnic_table%22%5D%3Bnwr%5B%22man_made%22%3D%22watermill%22%5D%3Bnwr%5B%22selected%22%3D%22yes%22%5D%3Bnwr%5B%22user%3Ahome%22%3D%22yes%22%5D%3Bnwr%5B%22user%3Alocation%22%3D%22yes%22%5D%3Bnwr%5B%22leisure%22%3D%22nature_reserve%22%5D%5B%22operator%22~%22%5E(.*%5BnN%5Datuurpunt.*)%24%22%5D%3Bnwr%5B%22boundary%22%3D%22protected_area%22%5D%5B%22protect_class%22!%3D%2298%22%5D%5B%22operator%22~%22%5E(.*%5BnN%5Datuurpunt.*)%24%22%5D%3Bnwr%5B%22information%22%3D%22visitor_centre%22%5D%5B%22operator%22~%22%5E(.*%5BnN%5Datuurpunt.*)%24%22%5D%3Bnwr%5B%22information%22%3D%22office%22%5D%5B%22operator%22~%22%5E(.*%5BnN%5Datuurpunt.*)%24%22%5D%3Bnwr%5B%22route%22~%22%5E(.*foot.*)%24%22%5D%5B%22operator%22~%22%5E(.*%5BnN%5Datuurpunt.*)%24%22%5D%3Bnwr%5B%22route%22~%22%5E(.*hiking.*)%24%22%5D%5B%22operator%22~%22%5E(.*%5BnN%5Datuurpunt.*)%24%22%5D%3Bnwr%5B%22route%22~%22%5E(.*bycicle.*)%24%22%5D%5B%22operator%22~%22%5E(.*%5BnN%5Datuurpunt.*)%24%22%5D%3Bnwr%5B%22route%22~%22%5E(.*horse.*)%24%22%5D%5B%22operator%22~%22%5E(.*%5BnN%5Datuurpunt.*)%24%22%5D%3Bnwr%5B%22leisure%22%3D%22bird_hide%22%5D%5B%22operator%22~%22%5E(.*%5BnN%5Datuurpunt.*)%24%22%5D%3Bnwr%5B%22amenity%22%3D%22drinking_water%22%5D%5B%22man_made%22!%3D%22reservoir_covered%22%5D%5B%22access%22!%3D%22permissive%22%5D%5B%22access%22!%3D%22private%22%5D%3Bnwr%5B%22drinking_water%22%3D%22yes%22%5D%5B%22man_made%22!%3D%22reservoir_covered%22%5D%5B%22access%22!%3D%22permissive%22%5D%5B%22access%22!%3D%22private%22%5D%3B)%3Bout%20body%3Bout%20meta%3B%3E%3Bout%20skel%20qt%3B" |             "(nwr%5B%22amenity%22%3D%22toilets%22%5D%3Bnwr%5B%22amenity%22%3D%22parking%22%5D%3Bnwr%5B%22amenity%22%3D%22bench%22%5D%3Bnwr%5B%22id%22%3D%22location_track%22%5D%3Bnwr%5B%22id%22%3D%22gps%22%5D%3Bnwr%5B%22information%22%3D%22board%22%5D%3Bnwr%5B%22leisure%22%3D%22picnic_table%22%5D%3Bnwr%5B%22selected%22%3D%22yes%22%5D%3Bnwr%5B%22user%3Ahome%22%3D%22yes%22%5D%3Bnwr%5B%22user%3Alocation%22%3D%22yes%22%5D%3Bnwr%5B%22leisure%22%3D%22nature_reserve%22%5D%5B%22operator%22~%22%5E(.*%5BnN%5Datuurpunt.*)%24%22%5D%3Bnwr%5B%22boundary%22%3D%22protected_area%22%5D%5B%22protect_class%22!%3D%2298%22%5D%5B%22operator%22~%22%5E(.*%5BnN%5Datuurpunt.*)%24%22%5D%3Bnwr%5B%22information%22%3D%22visitor_centre%22%5D%5B%22operator%22~%22%5E(.*%5BnN%5Datuurpunt.*)%24%22%5D%3Bnwr%5B%22information%22%3D%22office%22%5D%5B%22operator%22~%22%5E(.*%5BnN%5Datuurpunt.*)%24%22%5D%3Bnwr%5B%22route%22~%22%5E(.*foot.*)%24%22%5D%5B%22operator%22~%22%5E(.*%5BnN%5Datuurpunt.*)%24%22%5D%3Bnwr%5B%22route%22~%22%5E(.*hiking.*)%24%22%5D%5B%22operator%22~%22%5E(.*%5BnN%5Datuurpunt.*)%24%22%5D%3Bnwr%5B%22route%22~%22%5E(.*bycicle.*)%24%22%5D%5B%22operator%22~%22%5E(.*%5BnN%5Datuurpunt.*)%24%22%5D%3Bnwr%5B%22route%22~%22%5E(.*horse.*)%24%22%5D%5B%22operator%22~%22%5E(.*%5BnN%5Datuurpunt.*)%24%22%5D%3Bnwr%5B%22leisure%22%3D%22bird_hide%22%5D%5B%22operator%22~%22%5E(.*%5BnN%5Datuurpunt.*)%24%22%5D%3Bnwr%5B%22amenity%22%3D%22drinking_water%22%5D%5B%22man_made%22!%3D%22reservoir_covered%22%5D%5B%22access%22!%3D%22permissive%22%5D%5B%22access%22!%3D%22private%22%5D%3Bnwr%5B%22drinking_water%22%3D%22yes%22%5D%5B%22man_made%22!%3D%22reservoir_covered%22%5D%5B%22access%22!%3D%22permissive%22%5D%5B%22access%22!%3D%22private%22%5D%3B)%3Bout%20body%3Bout%20meta%3B%3E%3Bout%20skel%20qt%3B" | ||||||
|         ) |         ) | ||||||
|         await main([ |         await main([ | ||||||
|             "natuurpunt", |             "natuurpunt", | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue