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 { BBox } from "../BBox" | ||||
| 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" | ||||
|     "user:location": "yes" | ||||
|     date: string | ||||
|     latitude: number | ||||
|     longitude: number | ||||
|     speed: number | ||||
|     accuracy: number | ||||
|     heading: number | ||||
|     altitude: number | ||||
| /** | ||||
|  * The geolocation-handler takes a map-location and a geolocation state. | ||||
|  * It'll move the map as appropriate given the state of the geolocation-API | ||||
|  * It will also copy the geolocation into the appropriate FeatureSource to display on the map | ||||
|  */ | ||||
| export default class GeoLocationHandler { | ||||
|     public readonly geolocationState: GeoLocationState | ||||
|     private readonly _state: State | ||||
|     public readonly mapHasMoved: UIEventSource<boolean> = new UIEventSource<boolean>(false) | ||||
| 
 | ||||
|     constructor( | ||||
|         geolocationState: GeoLocationState, | ||||
|         state: State // { locationControl: UIEventSource<Loc>, selectedElement: UIEventSource<any>, leafletMap?: UIEventSource<any> })
 | ||||
|     ) { | ||||
|         this.geolocationState = geolocationState | ||||
|         this._state = state | ||||
|         const mapLocation = state.locationControl | ||||
|         // Did an interaction move the map?
 | ||||
|         let self = this | ||||
|         let initTime = new Date() | ||||
|         mapLocation.addCallbackD((_) => { | ||||
|             if (new Date().getTime() - initTime.getTime() < 250) { | ||||
|                 return | ||||
|             } | ||||
|             self.mapHasMoved.setData(true) | ||||
|             return true // Unsubscribe
 | ||||
|         }) | ||||
| 
 | ||||
|         const latLonGivenViaUrl = | ||||
|             QueryParameters.wasInitialized("lat") || QueryParameters.wasInitialized("lon") | ||||
|         if (latLonGivenViaUrl) { | ||||
|             // The URL counts as a 'user interaction'
 | ||||
|             this.mapHasMoved.setData(true) | ||||
|         } | ||||
| 
 | ||||
| export default class GeoLocationHandler extends VariableUiElement { | ||||
|     private readonly currentLocation?: SimpleFeatureSource | ||||
| 
 | ||||
|     /** | ||||
|      * Wether or not the geolocation is active, aka the user requested the current location | ||||
|      */ | ||||
|     private readonly _isActive: UIEventSource<boolean> | ||||
| 
 | ||||
|     /** | ||||
|      * Wether or not the geolocation is locked, aka the user requested the current location and wants the crosshair to follow the user | ||||
|      */ | ||||
|     private readonly _isLocked: UIEventSource<boolean> | ||||
| 
 | ||||
|     /** | ||||
|      * 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" | ||||
|         this.geolocationState.currentGPSLocation.addCallbackAndRunD((newLocation) => { | ||||
|             const timeSinceLastRequest = | ||||
|                 (new Date().getTime() - geolocationState.requestMoment.data?.getTime() ?? 0) / 1000 | ||||
|             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 | ||||
|                 ) | ||||
|         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 | ||||
|                 if (timeSinceLastRequest < Constants.zoomToLocationTimeout) { | ||||
|                     self.MoveMapToCurrentLocation() | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             if (this.geolocationState.isLocked.data) { | ||||
|                 // Jup, the map is locked to the bound location: move automatically
 | ||||
|                 self.MoveMapToCurrentLocation() | ||||
|                 return | ||||
|             } | ||||
|             const timeDiff = (new Date().getTime() - lastClick.getTime()) / 1000 | ||||
|             return timeDiff <= 3 | ||||
|         }) | ||||
| 
 | ||||
|         const latLonGiven = | ||||
|             QueryParameters.wasInitialized("lat") && QueryParameters.wasInitialized("lon") | ||||
|         const willFocus = lastClick.map((lastUserRequest) => { | ||||
|             const timeDiffInited = (new Date().getTime() - initedAt.getTime()) / 1000 | ||||
|             if (!latLonGiven && !autozoomDone && timeDiffInited < Constants.zoomToLocationTimeout) { | ||||
|                 return true | ||||
|             } | ||||
|             if (lastUserRequest === undefined) { | ||||
|                 return false | ||||
|             } | ||||
|             const timeDiff = (new Date().getTime() - lastUserRequest.getTime()) / 1000 | ||||
|             return timeDiff <= Constants.zoomToLocationTimeout | ||||
|         }) | ||||
| 
 | ||||
|         lastClick.addCallbackAndRunD((_) => { | ||||
|             window.setTimeout(() => { | ||||
|                 if (lastClickWithinThreeSecs.data || willFocus.data) { | ||||
|                     lastClick.ping() | ||||
|                 } | ||||
|             }, 500) | ||||
|         }) | ||||
| 
 | ||||
|         super( | ||||
|             hasLocation.map( | ||||
|                 (hasLocationData) => { | ||||
|                     if (permission.data === "denied") { | ||||
|                         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 { | ||||
|                     lastClick.setData(new Date()) | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             self.init(true, true) | ||||
|         }) | ||||
| 
 | ||||
|         const doAutoZoomToLocation = | ||||
|             !latLonGiven && | ||||
|             state.featureSwitchGeolocation.data && | ||||
|             state.selectedElement.data !== undefined | ||||
|         this.init(false, doAutoZoomToLocation) | ||||
| 
 | ||||
|         isLocked.addCallbackAndRunD((isLocked) => { | ||||
|         geolocationState.isLocked.map( | ||||
|             (isLocked) => { | ||||
|                 if (isLocked) { | ||||
|                 leafletMap.data?.dragging?.disable() | ||||
|                     state.leafletMap?.data?.dragging?.disable() | ||||
|                 } else { | ||||
|                 leafletMap.data?.dragging?.enable() | ||||
|                     state.leafletMap?.data?.dragging?.enable() | ||||
|                 } | ||||
|         }) | ||||
|             }, | ||||
|             [state.leafletMap] | ||||
|         ) | ||||
| 
 | ||||
|         this.currentLocation = state.currentUserLocation | ||||
|         this._currentGPSLocation.addCallback((location) => { | ||||
|             self._previousLocationGrant.setData("granted") | ||||
|         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 | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         mapLocation.setData({ | ||||
|             zoom: mapLocation.data.zoom, | ||||
|             lon: newLocation.longitude, | ||||
|             lat: newLocation.latitude, | ||||
|         }) | ||||
|         this.mapHasMoved.setData(true) | ||||
|     } | ||||
| 
 | ||||
|     private CopyGeolocationIntoMapstate() { | ||||
|         const state = this._state | ||||
|         this.geolocationState.currentGPSLocation.addCallbackAndRun((location) => { | ||||
|             if (location === undefined) { | ||||
|                 state.currentUserLocation?.features?.setData([]) | ||||
|                 return | ||||
|             } | ||||
|             const feature = { | ||||
|                 type: "Feature", | ||||
|                 properties: <GeoLocationPointProperties>{ | ||||
|                     id: "gps", | ||||
|                     "user:location": "yes", | ||||
|                     date: new Date().toISOString(), | ||||
|                     latitude: location.latitude, | ||||
|                     longitude: location.longitude, | ||||
|                     speed: location.speed, | ||||
|                     accuracy: location.accuracy, | ||||
|                     heading: location.heading, | ||||
|                     altitude: location.altitude, | ||||
|                     ...location, | ||||
|                 }, | ||||
|                 geometry: { | ||||
|                     type: "Point", | ||||
|  | @ -232,164 +137,7 @@ export default class GeoLocationHandler extends VariableUiElement { | |||
|                 }, | ||||
|             } | ||||
| 
 | ||||
|             self.currentLocation?.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() | ||||
|             } | ||||
|             state.currentUserLocation?.features?.setData([{ feature, freshness: new Date() }]) | ||||
|         }) | ||||
|     } | ||||
| 
 | ||||
|     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 ScrollableFullScreen from "../../UI/Base/ScrollableFullScreen" | ||||
| import FilteredLayer from "../../Models/FilteredLayer" | ||||
| import Constants from "../../Models/Constants" | ||||
| import ScrollableFullScreen from "../../UI/Base/ScrollableFullScreen" | ||||
| import BaseUIElement from "../../UI/BaseUIElement" | ||||
| 
 | ||||
| /** | ||||
|  | @ -10,70 +8,16 @@ import BaseUIElement from "../../UI/BaseUIElement" | |||
|  * Shows the given uiToShow-element in the messagebox | ||||
|  */ | ||||
| export default class StrayClickHandler { | ||||
|     private _lastMarker | ||||
| 
 | ||||
|     constructor( | ||||
|     public static construct = ( | ||||
|         state: { | ||||
|             LastClickLocation: UIEventSource<{ lat: number; lon: number }> | ||||
|             selectedElement: UIEventSource<string> | ||||
|             filteredLayers: UIEventSource<FilteredLayer[]> | ||||
|             leafletMap: UIEventSource<L.Map> | ||||
|             leafletMap: UIEventSource<any> | ||||
|         }, | ||||
|         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 | ||||
|             } | ||||
|         }) | ||||
|     ) => { | ||||
|         return undefined | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -16,6 +16,7 @@ export default class SplitAction extends OsmChangeAction { | |||
|     private readonly _splitPointsCoordinates: [number, number][] // lon, lat
 | ||||
|     private _meta: { theme: string; changeType: "split" } | ||||
|     private _toleranceInMeters: number | ||||
|     private _withNewCoordinates: (coordinates: [number, number][]) => void | ||||
| 
 | ||||
|     /** | ||||
|      * Create a changedescription for splitting a point. | ||||
|  | @ -24,17 +25,20 @@ export default class SplitAction extends OsmChangeAction { | |||
|      * @param splitPointCoordinates: lon, lat | ||||
|      * @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 withNewCoordinates: an optional callback which will leak the new coordinates of the original way | ||||
|      */ | ||||
|     constructor( | ||||
|         wayId: string, | ||||
|         splitPointCoordinates: [number, number][], | ||||
|         meta: { theme: string }, | ||||
|         toleranceInMeters = 5 | ||||
|         toleranceInMeters = 5, | ||||
|         withNewCoordinates?: (coordinates: [number, number][]) => void | ||||
|     ) { | ||||
|         super(wayId, true) | ||||
|         this.wayId = wayId | ||||
|         this._splitPointsCoordinates = splitPointCoordinates | ||||
|         this._toleranceInMeters = toleranceInMeters | ||||
|         this._withNewCoordinates = withNewCoordinates | ||||
|         this._meta = { ...meta, changeType: "split" } | ||||
|     } | ||||
| 
 | ||||
|  | @ -59,7 +63,7 @@ export default class SplitAction extends OsmChangeAction { | |||
|         const originalElement = <OsmWay>await OsmObject.DownloadObjectAsync(this.wayId) | ||||
|         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) | ||||
|         // Now we have a list with e.g.
 | ||||
|         // [ { 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[] = [] | ||||
|         // Let's create the new points as needed
 | ||||
|         // Let's create the new nodes as needed
 | ||||
|         for (const element of splitInfo) { | ||||
|             if (element.originalIndex >= 0) { | ||||
|                 continue | ||||
|  | @ -114,17 +118,21 @@ export default class SplitAction extends OsmChangeAction { | |||
|         for (const wayPart of wayParts) { | ||||
|             let isOriginal = wayPart === longest | ||||
|             if (isOriginal) { | ||||
|                 // We change the actual element!
 | ||||
|                 // We change the existing way
 | ||||
|                 const nodeIds = wayPart.map((p) => p.originalIndex) | ||||
|                 const newCoordinates = wayPart.map((p) => p.lngLat) | ||||
|                 changeDescription.push({ | ||||
|                     type: "way", | ||||
|                     id: originalElement.id, | ||||
|                     changes: { | ||||
|                         coordinates: wayPart.map((p) => p.lngLat), | ||||
|                         coordinates: newCoordinates, | ||||
|                         nodes: nodeIds, | ||||
|                     }, | ||||
|                     meta: this._meta, | ||||
|                 }) | ||||
|                 if (this._withNewCoordinates) { | ||||
|                     this._withNewCoordinates(newCoordinates) | ||||
|                 } | ||||
|                 allWayIdsInOrder.push(originalElement.id) | ||||
|                 allWaysNodesInOrder.push(nodeIds) | ||||
|             } else { | ||||
|  | @ -141,6 +149,10 @@ export default class SplitAction extends OsmChangeAction { | |||
|                     kv.push({ k: k, v: originalElement.tags[k] }) | ||||
|                 } | ||||
|                 const nodeIds = wayPart.map((p) => p.originalIndex) | ||||
|                 if (nodeIds.length <= 1) { | ||||
|                     console.error("Got a segment with only one node - skipping") | ||||
|                     continue | ||||
|                 } | ||||
|                 changeDescription.push({ | ||||
|                     type: "way", | ||||
|                     id: id, | ||||
|  |  | |||
|  | @ -8,7 +8,7 @@ import { LocalStorageSource } from "../Web/LocalStorageSource" | |||
| import SimpleMetaTagger from "../SimpleMetaTagger" | ||||
| import FeatureSource from "../FeatureSource/FeatureSource" | ||||
| import { ElementStorage } from "../ElementStorage" | ||||
| import { GeoLocationPointProperties } from "../Actors/GeoLocationHandler" | ||||
| import { GeoLocationPointProperties } from "../State/GeoLocationState" | ||||
| import { GeoOperations } from "../GeoOperations" | ||||
| import { ChangesetHandler, ChangesetTag } from "./ChangesetHandler" | ||||
| 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" | ||||
| 
 | ||||
| export default class Constants { | ||||
|     public static vNumber = "0.25.0" | ||||
|     public static vNumber = "0.25.2" | ||||
| 
 | ||||
|     public static ImgurApiKey = "7070e7167f0a25a" | ||||
|     public static readonly mapillary_client_token_v4 = | ||||
|  |  | |||
|  | @ -260,7 +260,7 @@ export interface LayerConfigJson { | |||
|                   /** | ||||
|                    * The type of background picture | ||||
|                    */ | ||||
|                   preferredBackground: | ||||
|                   preferredBackground?: | ||||
|                       | "osmbasedmap" | ||||
|                       | "photo" | ||||
|                       | "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 ShowDataLayer from "../ShowDataLayer/ShowDataLayer" | ||||
| 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 { | ||||
|     private static _nextId = 0 | ||||
|     public readonly leafletMap: UIEventSource<Map> | ||||
|  | @ -53,6 +129,18 @@ export default class MinimapImplementation extends BaseUIElement implements Mini | |||
|         AvailableBaseLayers.implement(new AvailableBaseLayersImplementation()) | ||||
|         Minimap.createMiniMap = (options) => new MinimapImplementation(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) { | ||||
|  |  | |||
|  | @ -5,6 +5,7 @@ import { UIEventSource } from "../../Logic/UIEventSource" | |||
| import Hash from "../../Logic/Web/Hash" | ||||
| import BaseUIElement from "../BaseUIElement" | ||||
| import Title from "./Title" | ||||
| import Hotkeys from "./Hotkeys" | ||||
| 
 | ||||
| /** | ||||
|  * | ||||
|  | @ -82,12 +83,11 @@ export default class ScrollableFullScreen { | |||
|     } | ||||
| 
 | ||||
|     private static initEmpty(): FixedUiElement { | ||||
|         document.addEventListener("keyup", function (event) { | ||||
|             if (event.code === "Escape") { | ||||
|                 ScrollableFullScreen.collapse() | ||||
|                 event.preventDefault() | ||||
|             } | ||||
|         }) | ||||
|         Hotkeys.RegisterHotkey( | ||||
|             { nomod: "Escape", onUp: true }, | ||||
|             "Close the sidebar", | ||||
|             ScrollableFullScreen.collapse | ||||
|         ) | ||||
| 
 | ||||
|         return new FixedUiElement("") | ||||
|     } | ||||
|  | @ -117,7 +117,7 @@ export default class ScrollableFullScreen { | |||
|         this._fullscreencomponent.AttachTo("fullscreen") | ||||
|         const fs = document.getElementById("fullscreen") | ||||
|         ScrollableFullScreen._currentlyOpen = this | ||||
|         fs.classList.remove("hidden") | ||||
|         fs?.classList?.remove("hidden") | ||||
|     } | ||||
| 
 | ||||
|     private BuildComponent(title: BaseUIElement, content: BaseUIElement): BaseUIElement { | ||||
|  |  | |||
|  | @ -24,6 +24,10 @@ export default abstract class BaseUIElement { | |||
|     AttachTo(divId: string) { | ||||
|         let element = document.getElementById(divId) | ||||
|         if (element === null) { | ||||
|             if (Utils.runningFromConsole) { | ||||
|                 this.ConstructElement() | ||||
|                 return | ||||
|             } | ||||
|             throw "SEVERE: could not attach UIElement to " + divId | ||||
|         } | ||||
| 
 | ||||
|  |  | |||
|  | @ -7,6 +7,7 @@ import BaseLayer from "../../Models/BaseLayer" | |||
| import AvailableBaseLayers from "../../Logic/Actors/AvailableBaseLayers" | ||||
| import BaseUIElement from "../BaseUIElement" | ||||
| import { GeoOperations } from "../../Logic/GeoOperations" | ||||
| import Hotkeys from "../Base/Hotkeys" | ||||
| 
 | ||||
| class SingleLayerSelectionButton extends Toggle { | ||||
|     public readonly activate: () => void | ||||
|  | @ -48,13 +49,13 @@ class SingleLayerSelectionButton extends Toggle { | |||
|         let toggle: BaseUIElement = new Toggle( | ||||
|             selected, | ||||
|             unselected, | ||||
|             options.currentBackground.map((bg) => bg.category === options.preferredType) | ||||
|             options.currentBackground.map((bg) => bg?.category === options.preferredType) | ||||
|         ) | ||||
| 
 | ||||
|         super( | ||||
|             toggle, | ||||
|             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?: { | ||||
|             preferredCategory?: string | ||||
|             allowedCategories?: ("osmbasedmap" | "photo" | "map")[] | ||||
|             enableHotkeys?: boolean | ||||
|         } | ||||
|     ) { | ||||
|         const allowedCategories = options?.allowedCategories ?? ["osmbasedmap", "photo", "map"] | ||||
|  | @ -183,7 +185,7 @@ export default class BackgroundMapSwitch extends Combine { | |||
|         let activatePrevious: () => void = undefined | ||||
|         for (const category of allowedCategories) { | ||||
|             let preferredLayer = undefined | ||||
|             if (previousLayer.category === category) { | ||||
|             if (previousLayer?.category === category) { | ||||
|                 preferredLayer = previousLayer | ||||
|             } | ||||
| 
 | ||||
|  | @ -198,6 +200,16 @@ export default class BackgroundMapSwitch extends Combine { | |||
|             if (category === options?.preferredCategory) { | ||||
|                 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) | ||||
|         } | ||||
| 
 | ||||
|  |  | |||
|  | @ -26,7 +26,7 @@ export class DownloadPanel extends Toggle { | |||
|         currentBounds: UIEventSource<BBox> | ||||
|     }) { | ||||
|         const t = Translations.t.general.download | ||||
|         const name = State.state.layoutToUse.id | ||||
|         const name = state.layoutToUse.id | ||||
| 
 | ||||
|         const includeMetaToggle = new CheckBoxes([t.includeMetaData]) | ||||
|         const metaisIncluded = includeMetaToggle.GetValue().map((selected) => selected.length > 0) | ||||
|  |  | |||
|  | @ -20,6 +20,7 @@ import FilteredLayer from "../../Models/FilteredLayer" | |||
| import CopyrightPanel from "./CopyrightPanel" | ||||
| import FeaturePipeline from "../../Logic/FeatureSource/FeaturePipeline" | ||||
| import PrivacyPolicy from "./PrivacyPolicy" | ||||
| import Hotkeys from "../Base/Hotkeys" | ||||
| 
 | ||||
| export default class FullWelcomePaneWithTabs extends ScrollableFullScreen { | ||||
|     public static MoreThemesTabIndex = 1 | ||||
|  | @ -126,6 +127,7 @@ export default class FullWelcomePaneWithTabs extends ScrollableFullScreen { | |||
|                     osmcha_link: Utils.OsmChaLinkFor(7), | ||||
|                 }), | ||||
|                 "<br/>Version " + Constants.vNumber, | ||||
|                 Hotkeys.generateDocumentationDynamic(), | ||||
|             ]).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 CopyrightPanel from "./CopyrightPanel" | ||||
| import FeaturePipelineState from "../../Logic/State/FeaturePipelineState" | ||||
| import { FixedUiElement } from "../Base/FixedUiElement" | ||||
| import Hotkeys from "../Base/Hotkeys" | ||||
| 
 | ||||
| export default class LeftControls extends Combine { | ||||
|     constructor( | ||||
|  | @ -73,7 +73,7 @@ export default class LeftControls extends Combine { | |||
|             guiState.downloadControlIsOpened.setData(true) | ||||
|         ) | ||||
| 
 | ||||
|         const downloadButtonn = new Toggle( | ||||
|         const downloadButton = new Toggle( | ||||
|             toggledDownload, | ||||
|             undefined, | ||||
|             state.featureSwitchEnableExport.map( | ||||
|  | @ -94,11 +94,20 @@ export default class LeftControls extends Combine { | |||
|         const toggledFilter = new MapControlButton(Svg.layers_svg()).onClick(() => | ||||
|             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 mapSwitch = new Toggle( | ||||
|             new BackgroundMapSwitch(state, state.backgroundLayer), | ||||
|             new BackgroundMapSwitch(state, state.backgroundLayer, { enableHotkeys: true }), | ||||
|             undefined, | ||||
|             state.featureSwitchBackgroundSelection | ||||
|         ) | ||||
|  | @ -120,7 +129,7 @@ export default class LeftControls extends Combine { | |||
|             state.featureSwitchWelcomeMessage | ||||
|         ) | ||||
| 
 | ||||
|         super([currentViewAction, filterButton, downloadButtonn, copyright, mapSwitch]) | ||||
|         super([currentViewAction, filterButton, downloadButton, copyright, mapSwitch]) | ||||
| 
 | ||||
|         this.SetClass("flex flex-col") | ||||
|     } | ||||
|  |  | |||
|  | @ -92,13 +92,13 @@ export default class MoreScreen extends Combine { | |||
| 
 | ||||
|         if (onMainScreen) { | ||||
|             search.focus() | ||||
|         } | ||||
|             document.addEventListener("keydown", function (event) { | ||||
|                 if (event.ctrlKey && event.code === "KeyF") { | ||||
|                     search.focus() | ||||
|                     event.preventDefault() | ||||
|                 } | ||||
|             }) | ||||
|         } | ||||
| 
 | ||||
|         const searchBar = new Combine([ | ||||
|             Svg.search_svg().SetClass("w-8"), | ||||
|  |  | |||
|  | @ -5,18 +5,16 @@ import GeoLocationHandler from "../../Logic/Actors/GeoLocationHandler" | |||
| import Svg from "../../Svg" | ||||
| import MapState from "../../Logic/State/MapState" | ||||
| 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 { GeolocationControl } from "./GeolocationControl" | ||||
| 
 | ||||
| export default class RightControls extends Combine { | ||||
|     constructor(state: MapState & { featurePipeline: FeaturePipeline }) { | ||||
|         const geolocatioHandler = new GeoLocationHandler(state) | ||||
| 
 | ||||
|     constructor( | ||||
|         state: MapState & { featurePipeline: FeaturePipeline }, | ||||
|         geolocationHandler: GeoLocationHandler | ||||
|     ) { | ||||
|         const geolocationButton = new Toggle( | ||||
|             new MapControlButton(geolocatioHandler, { | ||||
|             new MapControlButton(new GeolocationControl(geolocationHandler, state), { | ||||
|                 dontStyle: true, | ||||
|             }).SetClass("p-1"), | ||||
|             undefined, | ||||
|  |  | |||
|  | @ -1,6 +1,5 @@ | |||
| import { UIEventSource } from "../../Logic/UIEventSource" | ||||
| import { Translation } from "../i18n/Translation" | ||||
| import { VariableUiElement } from "../Base/VariableUIElement" | ||||
| import Svg from "../../Svg" | ||||
| import { TextField } from "../Input/TextField" | ||||
| import { Geocoding } from "../../Logic/Osm/Geocoding" | ||||
|  | @ -10,6 +9,7 @@ import Combine from "../Base/Combine" | |||
| import Locale from "../i18n/Locale" | ||||
| 
 | ||||
| export default class SearchAndGo extends Combine { | ||||
|     private readonly _searchField: TextField | ||||
|     constructor(state: { leafletMap: UIEventSource<any>; selectedElement?: UIEventSource<any> }) { | ||||
|         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) | ||||
|         this._searchField = searchField | ||||
|         goButton.onClick(runSearch) | ||||
|     } | ||||
| 
 | ||||
|     focus() { | ||||
|         this._searchField.focus() | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -80,7 +80,7 @@ export default class ShareScreen extends Combine { | |||
|             includeCurrentBackground.GetValue().map( | ||||
|                 (includeBG) => { | ||||
|                     if (includeBG) { | ||||
|                         return "background=" + currentLayer.data.id | ||||
|                         return "background=" + currentLayer.data?.id | ||||
|                     } else { | ||||
|                         return null | ||||
|                     } | ||||
|  |  | |||
|  | @ -29,6 +29,10 @@ import Img from "./Base/Img" | |||
| import UserInformationPanel from "./BigComponents/UserInformation" | ||||
| import { LoginToggle } from "./Popup/LoginButton" | ||||
| 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 | ||||
|  | @ -38,10 +42,14 @@ import { FixedUiElement } from "./Base/FixedUiElement" | |||
| export default class DefaultGUI { | ||||
|     private readonly guiState: DefaultGuiState | ||||
|     private readonly state: FeaturePipelineState | ||||
|     private readonly geolocationHandler: GeoLocationHandler | undefined | ||||
| 
 | ||||
|     constructor(state: FeaturePipelineState, guiState: DefaultGuiState) { | ||||
|         this.state = state | ||||
|         this.guiState = guiState | ||||
|         if (this.state.featureSwitchGeolocation.data) { | ||||
|             this.geolocationHandler = new GeoLocationHandler(new GeoLocationState(), state) | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public setup() { | ||||
|  | @ -55,6 +63,14 @@ export default class DefaultGUI { | |||
|             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") | ||||
|             .then((data) => console.log("Service worker", data)) | ||||
|             .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!
 | ||||
|             } | ||||
| 
 | ||||
|             new StrayClickHandler( | ||||
|             StrayClickHandler.construct( | ||||
|                 state, | ||||
|                 addNewPoint, | ||||
|                 hasPresets ? new AddNewMarker(state.filteredLayers) : noteMarker | ||||
|  | @ -145,6 +161,9 @@ export default class DefaultGUI { | |||
|     } | ||||
| 
 | ||||
|     private SetupMap() { | ||||
|         if (Utils.runningFromConsole) { | ||||
|             return | ||||
|         } | ||||
|         const state = this.state | ||||
|         const guiState = this.guiState | ||||
| 
 | ||||
|  | @ -232,18 +251,27 @@ export default class DefaultGUI { | |||
|             .AttachTo("on-small-screen") | ||||
| 
 | ||||
|         new Combine([ | ||||
|             Toggle.If(state.featureSwitchSearch, () => | ||||
|                 new SearchAndGo(state).SetClass( | ||||
|             Toggle.If(state.featureSwitchSearch, () => { | ||||
|                 const search = new SearchAndGo(state).SetClass( | ||||
|                     "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") | ||||
| 
 | ||||
|         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") | ||||
|         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
 | ||||
|         for (const state of guiState.allFullScreenStates) { | ||||
|  |  | |||
|  | @ -22,8 +22,10 @@ import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig" | |||
| import { ElementStorage } from "../../Logic/ElementStorage" | ||||
| import BaseLayer from "../../Models/BaseLayer" | ||||
| 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
 | ||||
|     private static splitLayerStyling = new LayerConfig( | ||||
|         split_point, | ||||
|  | @ -63,6 +65,106 @@ export default class SplitRoadWizard extends Toggle { | |||
| 
 | ||||
|         // Toggle variable between show split button and map
 | ||||
|         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
 | ||||
|         const roadElement = state.allElements.ContainingFeatures.get(id) | ||||
| 
 | ||||
|  | @ -96,7 +198,6 @@ export default class SplitRoadWizard extends Toggle { | |||
|             layerToShow: SplitRoadWizard.splitLayerStyling, | ||||
|             state, | ||||
|         }) | ||||
| 
 | ||||
|         /** | ||||
|          * Handles a click on the overleaf map. | ||||
|          * 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]) | ||||
|             }) | ||||
|         ) | ||||
| 
 | ||||
|         // 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 | ||||
|         return miniMap | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -1,7 +1,7 @@ | |||
| import { Utils } from "../../Utils" | ||||
| import { Feature } from "geojson" | ||||
| import { Point } from "@turf/turf" | ||||
| import { GeoLocationPointProperties } from "../../Logic/Actors/GeoLocationHandler" | ||||
| import { GeoLocationPointProperties } from "../../Logic/State/GeoLocationState" | ||||
| import UploadTraceToOsmUI from "../BigComponents/UploadTraceToOsmUI" | ||||
| import { SpecialVisualization } from "../SpecialVisualization" | ||||
| 
 | ||||
|  |  | |||
|  | @ -17,7 +17,10 @@ export default class ShowDataLayer { | |||
|      */ | ||||
|     constructor(options: ShowDataLayerOptions & { layerToShow: LayerConfig }) { | ||||
|         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) | ||||
|     } | ||||
|  |  | |||
|  | @ -143,6 +143,99 @@ | |||
|       "override": { | ||||
|         "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": [ | ||||
|  | @ -153,5 +246,19 @@ | |||
|         "centroid" | ||||
|       ] | ||||
|     } | ||||
|   ], | ||||
|   "filter": [ | ||||
|     "open_now", | ||||
|     { | ||||
|       "id": "speech_output", | ||||
|       "options": [ | ||||
|         { | ||||
|           "question": { | ||||
|             "en": "With speech output" | ||||
|           }, | ||||
|           "osmTags": "speech_output=yes" | ||||
|         } | ||||
|       ] | ||||
|     } | ||||
|   ] | ||||
| } | ||||
|  | @ -2,7 +2,11 @@ | |||
|   { | ||||
|     "path": "bench.svg", | ||||
|     "license": "CC0", | ||||
|     "authors": [], | ||||
|     "sources": [] | ||||
|     "authors": [ | ||||
|       "Tobias Zwick" | ||||
|     ], | ||||
|     "sources": [ | ||||
|       "https://github.com/streetcomplete/StreetComplete/" | ||||
|     ] | ||||
|   } | ||||
| ] | ||||
|  | @ -314,7 +314,10 @@ | |||
|             "cs": "Zde si můžete půjčit cyklistické přilby" | ||||
|           } | ||||
|         } | ||||
|       ] | ||||
|       ], | ||||
|       "condition": { | ||||
|         "and": [] | ||||
|       } | ||||
|     }, | ||||
|     { | ||||
|       "id": "rental_types", | ||||
|  | @ -443,7 +446,9 @@ | |||
|             "key": "capacity:bicycle_type", | ||||
|             "type": "pnat" | ||||
|           }, | ||||
|           "condition": "rental~.*bicycle_type.*" | ||||
|           "condition": {"and":  | ||||
|             ["rental~.*bicycle_type.*"] | ||||
|           } | ||||
|         } | ||||
|       ] | ||||
|     } | ||||
|  |  | |||
|  | @ -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", | ||||
|       "question": { | ||||
|  |  | |||
|  | @ -23,7 +23,7 @@ | |||
|     "osmTags": "emergency=defibrillator" | ||||
|   }, | ||||
|   "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" | ||||
|   ], | ||||
|   "minzoom": 12, | ||||
|  |  | |||
|  | @ -15,6 +15,7 @@ | |||
|     "osmTags": "amenity=parking_space" | ||||
|   }, | ||||
|   "tagRenderings": [ | ||||
|     "images", | ||||
|     { | ||||
|       "id": "type", | ||||
|       "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" | ||||
|     ] | ||||
|   }, | ||||
|   { | ||||
|     "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", | ||||
|     "license": "CC-BY-SA", | ||||
|  | @ -91,6 +102,17 @@ | |||
|       "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", | ||||
|     "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" | ||||
|           }, | ||||
|           { | ||||
|             "if": { | ||||
|               "and": [ | ||||
|                 "_waste_amount=1", | ||||
|                 "recycling:fluorescent_tubes=yes" | ||||
|               ] | ||||
|             }, | ||||
|             "then": "circle:white;./assets/layers/recycling/fluorescent_tubes.svg" | ||||
|           }, | ||||
|           { | ||||
|             "if": { | ||||
|               "and": [ | ||||
|  | @ -160,6 +169,15 @@ | |||
|             }, | ||||
|             "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": { | ||||
|               "and": [ | ||||
|  | @ -303,6 +321,15 @@ | |||
|           }, | ||||
|           "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": { | ||||
|             "and": [ | ||||
|  | @ -335,6 +362,15 @@ | |||
|           }, | ||||
|           "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": { | ||||
|             "and": [ | ||||
|  | @ -500,7 +536,9 @@ | |||
|             "recycling:clothes=", | ||||
|             "recycling:cooking_oil=", | ||||
|             "recycling:engine_oil=", | ||||
|             "recycling:fluorescent_tubes=", | ||||
|             "recycling:green_waste=", | ||||
|             "recycling:light_bulbs=", | ||||
|             "recycling:organic=", | ||||
|             "recycling:glass_bottles=", | ||||
|             "recycling:glass=", | ||||
|  | @ -698,6 +736,18 @@ | |||
|             "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", | ||||
|           "ifnot": "recycling:green_waste=", | ||||
|  | @ -761,6 +811,18 @@ | |||
|             "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", | ||||
|           "ifnot": "recycling:newspaper=", | ||||
|  | @ -1047,6 +1109,13 @@ | |||
|           }, | ||||
|           "osmTags": "recycling:engine_oil=yes" | ||||
|         }, | ||||
|         { | ||||
|           "question": { | ||||
|             "en": "Recycling of fluorescent tubes", | ||||
|             "nl": "Recycling van tl-buizen" | ||||
|           }, | ||||
|           "osmTags": "recycling:fluorescent_tubes=yes" | ||||
|         }, | ||||
|         { | ||||
|           "question": { | ||||
|             "en": "Recycling of green waste", | ||||
|  | @ -1081,6 +1150,13 @@ | |||
|           }, | ||||
|           "osmTags": "recycling:glass=yes" | ||||
|         }, | ||||
|         { | ||||
|           "question": { | ||||
|             "en": "Recycling of light bulbs", | ||||
|             "nl": "Recycling van lampen" | ||||
|           }, | ||||
|           "osmTags": "recycling:light_bulbs=yes" | ||||
|         }, | ||||
|         { | ||||
|           "question": { | ||||
|             "en": "Recycling of newspapers", | ||||
|  |  | |||
|  | @ -528,7 +528,8 @@ | |||
|         "de": "eine an einer Wand montierte Überwachungskamera" | ||||
|       }, | ||||
|       "preciseInput": { | ||||
|         "snapToLayer": "walls_and_buildings" | ||||
|         "snapToLayer": "walls_and_buildings", | ||||
|         "preferredBackground": ["photo", "osmbasedmap","map"] | ||||
|       } | ||||
|     } | ||||
|   ], | ||||
|  |  | |||
|  | @ -68,7 +68,7 @@ | |||
|             "then": { | ||||
|               "en": "This ticket validator accepts OV-Chipkaart" | ||||
|             }, | ||||
|             "hideInAnswer": "_country!=nl" | ||||
|             "hideInAnswer": true | ||||
|           }, | ||||
|           { | ||||
|             "if": "payment:ov-chipkaart=yes", | ||||
|  | @ -76,7 +76,7 @@ | |||
|             "then": { | ||||
|               "en": "This ticket validator accepts OV-Chipkaart" | ||||
|             }, | ||||
|             "hideInAnswer": true | ||||
|             "hideInAnswer": "_country!=nl" | ||||
|           } | ||||
|         ] | ||||
|       } | ||||
|  | @ -90,6 +90,9 @@ | |||
|       "title": { | ||||
|         "en": "a ticket validator", | ||||
|         "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." | ||||
|       } | ||||
|     } | ||||
|   ], | ||||
|  |  | |||
|  | @ -22,6 +22,8 @@ | |||
|     "path": "wheelchair.svg", | ||||
|     "license": "CC0", | ||||
|     "authors": [], | ||||
|     "sources": [] | ||||
|     "sources": [ | ||||
|       "https://wiki.openstreetmap.org/wiki/File:Wheelchair_symbol.svg" | ||||
|     ] | ||||
|   } | ||||
| ] | ||||
|  | @ -233,7 +233,7 @@ | |||
|       "id": "toilet-charge" | ||||
|     }, | ||||
|     { | ||||
|       "builtin": "payment-options", | ||||
|       "builtin": "payment-options-split", | ||||
|       "override": { | ||||
|         "condition": "fee=yes" | ||||
|       } | ||||
|  |  | |||
|  | @ -23,6 +23,8 @@ | |||
|     "path": "wheelchair.svg", | ||||
|     "license": "CC0", | ||||
|     "authors": [], | ||||
|     "sources": [] | ||||
|     "sources": [ | ||||
|       "https://wiki.openstreetmap.org/wiki/File:Wheelchair_symbol.svg" | ||||
|     ] | ||||
|   } | ||||
| ] | ||||
|  | @ -32,7 +32,7 @@ | |||
|     } | ||||
|   }, | ||||
|   "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.", | ||||
|     "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.", | ||||
|  |  | |||
|  | @ -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" | ||||
|     ] | ||||
|   }, | ||||
|   { | ||||
|     "path": "gender_female.svg", | ||||
|     "license": "CC0", | ||||
|     "authors": [], | ||||
|     "sources": [] | ||||
|   }, | ||||
|   { | ||||
|     "path": "gender_female.svg", | ||||
|     "license": "CC0", | ||||
|  | @ -525,12 +519,6 @@ | |||
|       "https://www.iconpacks.net/free-icon-pack/gender-107.html" | ||||
|     ] | ||||
|   }, | ||||
|   { | ||||
|     "path": "gender_male.svg", | ||||
|     "license": "CC0", | ||||
|     "authors": [], | ||||
|     "sources": [] | ||||
|   }, | ||||
|   { | ||||
|     "path": "gender_male.svg", | ||||
|     "license": "CC0", | ||||
|  | @ -551,12 +539,6 @@ | |||
|       "https://www.iconpacks.net/free-icon-pack/gender-107.html" | ||||
|     ] | ||||
|   }, | ||||
|   { | ||||
|     "path": "gender_trans.svg", | ||||
|     "license": "CC0", | ||||
|     "authors": [], | ||||
|     "sources": [] | ||||
|   }, | ||||
|   { | ||||
|     "path": "gender_trans.svg", | ||||
|     "license": "CC0", | ||||
|  | @ -899,7 +881,7 @@ | |||
|   }, | ||||
|   { | ||||
|     "path": "none.svg", | ||||
|     "license": "CC0", | ||||
|     "license": "trivial", | ||||
|     "authors": [], | ||||
|     "sources": [] | ||||
|   }, | ||||
|  | @ -1277,9 +1259,11 @@ | |||
|   }, | ||||
|   { | ||||
|     "path": "teardrop_with_hole_green.svg", | ||||
|     "license": "CC0", | ||||
|     "license": "Creative Commons 4.0 BY-NC", | ||||
|     "authors": [], | ||||
|     "sources": [] | ||||
|     "sources": [ | ||||
|       "https://pngimg.com/image/46283" | ||||
|     ] | ||||
|   }, | ||||
|   { | ||||
|     "path": "translate.svg", | ||||
|  |  | |||
|  | @ -919,6 +919,7 @@ | |||
|           "icon": "./assets/tagRenderings/coins.svg", | ||||
|           "then": { | ||||
|             "en": "Coins are accepted here", | ||||
|             "nl": "Muntgeld wordt hier aanvaard", | ||||
|             "de": "Münzen werden hier akzeptiert" | ||||
|           } | ||||
|         }, | ||||
|  | @ -928,6 +929,7 @@ | |||
|           "icon": "./assets/tagRenderings/notes.svg", | ||||
|           "then": { | ||||
|             "en": "Bank notes are accepted here", | ||||
|             "nl": "Bankbiljetten worden hier aanvaard", | ||||
|             "de": "Geldscheine werden hier akzeptiert" | ||||
|           } | ||||
|         }, | ||||
|  | @ -937,6 +939,7 @@ | |||
|           "icon": "./assets/tagRenderings/payment_card.svg", | ||||
|           "then": { | ||||
|             "en": "Debit cards are accepted here", | ||||
|             "nl": "Betalen met debetkaarten kan hier", | ||||
|             "de": "Debitkarten werden hier akzeptiert" | ||||
|           } | ||||
|         }, | ||||
|  | @ -946,6 +949,7 @@ | |||
|           "icon": "./assets/tagRenderings/payment_card.svg", | ||||
|           "then": { | ||||
|             "en": "Credit cards are accepted here", | ||||
|             "nl": "Betalen met creditkaarten kan hier", | ||||
|             "de": "Kreditkarten werden hier akzeptiert" | ||||
|           } | ||||
|         } | ||||
|  |  | |||
|  | @ -13,8 +13,13 @@ | |||
|   { | ||||
|     "path": "car.svg", | ||||
|     "license": "CC0", | ||||
|     "authors": [], | ||||
|     "sources": [] | ||||
|     "authors": [ | ||||
|       "Simon Child", | ||||
|       "The Noun Project" | ||||
|     ], | ||||
|     "sources": [ | ||||
|       "https://thenounproject.com/icon/electric-car-55511/" | ||||
|     ] | ||||
|   }, | ||||
|   { | ||||
|     "path": "logo.svg", | ||||
|  |  | |||
|  | @ -39,6 +39,7 @@ | |||
|   "socialImage": "./assets/themes/cyclofix/logo.svg", | ||||
|   "layers": [ | ||||
|     "bike_cafe", | ||||
|     "bike_shop", | ||||
|     { | ||||
|       "builtin": [ | ||||
|         "bicycle_rental" | ||||
|  | @ -87,7 +88,6 @@ | |||
|         ] | ||||
|       } | ||||
|     }, | ||||
|     "bike_shop", | ||||
|     { | ||||
|       "builtin": "bicycle_library", | ||||
|       "override": { | ||||
|  |  | |||
|  | @ -3,37 +3,49 @@ | |||
|     "path": "bench.svg", | ||||
|     "license": "CC0", | ||||
|     "authors": [], | ||||
|     "sources": [] | ||||
|     "sources": [ | ||||
|       "https://osoc.be/editions/2021/nature-moves" | ||||
|     ] | ||||
|   }, | ||||
|   { | ||||
|     "path": "birdhide.svg", | ||||
|     "license": "CC0", | ||||
|     "authors": [], | ||||
|     "sources": [] | ||||
|     "sources": [ | ||||
|       "https://osoc.be/editions/2021/nature-moves" | ||||
|     ] | ||||
|   }, | ||||
|   { | ||||
|     "path": "drips.svg", | ||||
|     "license": "CC0", | ||||
|     "authors": [], | ||||
|     "sources": [] | ||||
|     "sources": [ | ||||
|       "https://osoc.be/editions/2021/nature-moves" | ||||
|     ] | ||||
|   }, | ||||
|   { | ||||
|     "path": "information.svg", | ||||
|     "license": "CC0", | ||||
|     "authors": [], | ||||
|     "sources": [] | ||||
|     "sources": [ | ||||
|       "https://osoc.be/editions/2021/nature-moves" | ||||
|     ] | ||||
|   }, | ||||
|   { | ||||
|     "path": "information_board.svg", | ||||
|     "license": "CC0", | ||||
|     "authors": [], | ||||
|     "sources": [] | ||||
|     "sources": [ | ||||
|       "https://osoc.be/editions/2021/nature-moves" | ||||
|     ] | ||||
|   }, | ||||
|   { | ||||
|     "path": "nature_reserve.svg", | ||||
|     "license": "CC0", | ||||
|     "authors": [], | ||||
|     "sources": [] | ||||
|     "sources": [ | ||||
|       "https://osoc.be/editions/2021/nature-moves" | ||||
|     ] | ||||
|   }, | ||||
|   { | ||||
|     "path": "natuurpunt.png", | ||||
|  | @ -49,72 +61,88 @@ | |||
|     "path": "parking.svg", | ||||
|     "license": "CC0", | ||||
|     "authors": [], | ||||
|     "sources": [] | ||||
|     "sources": [ | ||||
|       "https://osoc.be/editions/2021/nature-moves" | ||||
|     ] | ||||
|   }, | ||||
|   { | ||||
|     "path": "parkingbike.svg", | ||||
|     "license": "CC0", | ||||
|     "authors": [], | ||||
|     "sources": [] | ||||
|     "sources": [ | ||||
|       "https://osoc.be/editions/2021/nature-moves" | ||||
|     ] | ||||
|   }, | ||||
|   { | ||||
|     "path": "parkingmotor.svg", | ||||
|     "license": "CC0", | ||||
|     "authors": [], | ||||
|     "sources": [] | ||||
|     "sources": [ | ||||
|       "https://osoc.be/editions/2021/nature-moves" | ||||
|     ] | ||||
|   }, | ||||
|   { | ||||
|     "path": "parkingwheels.svg", | ||||
|     "license": "CC0", | ||||
|     "authors": [], | ||||
|     "sources": [] | ||||
|     "sources": [ | ||||
|       "https://osoc.be/editions/2021/nature-moves" | ||||
|     ] | ||||
|   }, | ||||
|   { | ||||
|     "path": "picnic_table.svg", | ||||
|     "license": "CC0", | ||||
|     "authors": [], | ||||
|     "sources": [] | ||||
|     "sources": [ | ||||
|       "https://osoc.be/editions/2021/nature-moves" | ||||
|     ] | ||||
|   }, | ||||
|   { | ||||
|     "path": "pushchair.svg", | ||||
|     "license": "CC0", | ||||
|     "authors": [], | ||||
|     "sources": [] | ||||
|     "sources": [ | ||||
|       "https://osoc.be/editions/2021/nature-moves" | ||||
|     ] | ||||
|   }, | ||||
|   { | ||||
|     "path": "toilets.svg", | ||||
|     "license": "CC0", | ||||
|     "authors": [], | ||||
|     "sources": [] | ||||
|     "sources": [ | ||||
|       "https://osoc.be/editions/2021/nature-moves" | ||||
|     ] | ||||
|   }, | ||||
|   { | ||||
|     "path": "trail.svg", | ||||
|     "license": "CC0", | ||||
|     "authors": [], | ||||
|     "sources": [] | ||||
|     "sources": [ | ||||
|       "https://osoc.be/editions/2021/nature-moves" | ||||
|     ] | ||||
|   }, | ||||
|   { | ||||
|     "path": "urinal.svg", | ||||
|     "license": "CC0", | ||||
|     "authors": [], | ||||
|     "sources": [] | ||||
|     "sources": [ | ||||
|       "https://osoc.be/editions/2021/nature-moves" | ||||
|     ] | ||||
|   }, | ||||
|   { | ||||
|     "path": "walk_wheelchair.svg", | ||||
|     "license": "CC0", | ||||
|     "authors": [], | ||||
|     "sources": [] | ||||
|   }, | ||||
|   { | ||||
|     "path": "watermill.svg", | ||||
|     "license": "CC0", | ||||
|     "authors": [], | ||||
|     "sources": [] | ||||
|     "sources": [ | ||||
|       "https://osoc.be/editions/2021/nature-moves" | ||||
|     ] | ||||
|   }, | ||||
|   { | ||||
|     "path": "wheelchair.svg", | ||||
|     "license": "CC0", | ||||
|     "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", | ||||
|       "override": { | ||||
|  |  | |||
|  | @ -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", | ||||
|       "override": { | ||||
|  |  | |||
|  | @ -107,9 +107,11 @@ | |||
|   }, | ||||
|   { | ||||
|     "path": "logo.svg", | ||||
|     "license": "CC0", | ||||
|     "license": "Creative Commons 4.0 BY-NC", | ||||
|     "authors": [], | ||||
|     "sources": [] | ||||
|     "sources": [ | ||||
|       "https://pngimg.com/image/46283" | ||||
|     ] | ||||
|   }, | ||||
|   { | ||||
|     "path": "observation_platform.svg", | ||||
|  |  | |||
|  | @ -2,7 +2,9 @@ | |||
|   { | ||||
|     "path": "icon.svg", | ||||
|     "license": "CC0; trivial", | ||||
|     "authors": [], | ||||
|     "authors": [ | ||||
|       "Pieter Vander Vennet" | ||||
|     ], | ||||
|     "sources": [] | ||||
|   } | ||||
| ] | ||||
|  | @ -913,6 +913,7 @@ | |||
|         "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", | ||||
|         "split": "Split", | ||||
|         "splitAgain": "Split this road again", | ||||
|         "splitTitle": "Choose on the map where the properties of this road change" | ||||
|     }, | ||||
|     "translations": { | ||||
|  |  | |||
|  | @ -881,31 +881,31 @@ | |||
|                     "2": { | ||||
|                         "then": "Ací es poden reciclar llaunes" | ||||
|                     }, | ||||
|                     "7": { | ||||
|                     "8": { | ||||
|                         "then": "Ací es poden reciclar residus orgànics" | ||||
|                     }, | ||||
|                     "8": { | ||||
|                     "9": { | ||||
|                         "then": "Ací es poden reciclar ampolles de vidre" | ||||
|                     }, | ||||
|                     "9": { | ||||
|                     "10": { | ||||
|                         "then": "Ací es pot reciclar vidre" | ||||
|                     }, | ||||
|                     "10": { | ||||
|                     "12": { | ||||
|                         "then": "Ací es poden reciclar diaris" | ||||
|                     }, | ||||
|                     "11": { | ||||
|                     "13": { | ||||
|                         "then": "Ací es pot reciclar paper" | ||||
|                     }, | ||||
|                     "12": { | ||||
|                     "14": { | ||||
|                         "then": "Ací es poden reciclar ampolles de plàstic" | ||||
|                     }, | ||||
|                     "13": { | ||||
|                     "15": { | ||||
|                         "then": "Ací es poden reciclar envasos de plàstic" | ||||
|                     }, | ||||
|                     "14": { | ||||
|                     "16": { | ||||
|                         "then": "Ací es pot reciclar plàstic" | ||||
|                     }, | ||||
|                     "20": { | ||||
|                     "22": { | ||||
|                         "then": "Ací es pot reciclar el rebuig" | ||||
|                     } | ||||
|                 } | ||||
|  | @ -1052,9 +1052,6 @@ | |||
|             "render": "Paperera" | ||||
|         } | ||||
|     }, | ||||
|     "watermill": { | ||||
|         "name": "Molí d'aigua" | ||||
|     }, | ||||
|     "windturbine": { | ||||
|         "title": { | ||||
|             "mappings": { | ||||
|  |  | |||
|  | @ -6322,37 +6322,37 @@ | |||
|                     "6": { | ||||
|                         "question": "Recycling von Motoröl" | ||||
|                     }, | ||||
|                     "7": { | ||||
|                     "8": { | ||||
|                         "question": "Recycling von Grünabfällen" | ||||
|                     }, | ||||
|                     "8": { | ||||
|                     "9": { | ||||
|                         "question": "Recycling von Glasflaschen" | ||||
|                     }, | ||||
|                     "9": { | ||||
|                     "10": { | ||||
|                         "question": "Recycling von Glas" | ||||
|                     }, | ||||
|                     "10": { | ||||
|                     "12": { | ||||
|                         "question": "Recycling von Zeitungen" | ||||
|                     }, | ||||
|                     "11": { | ||||
|                     "13": { | ||||
|                         "question": "Recycling von Papier" | ||||
|                     }, | ||||
|                     "12": { | ||||
|                     "14": { | ||||
|                         "question": "Recycling von Plastikflaschen" | ||||
|                     }, | ||||
|                     "13": { | ||||
|                     "15": { | ||||
|                         "question": "Recycling von Kunststoffverpackungen" | ||||
|                     }, | ||||
|                     "14": { | ||||
|                     "16": { | ||||
|                         "question": "Recycling von Kunststoffen" | ||||
|                     }, | ||||
|                     "15": { | ||||
|                     "17": { | ||||
|                         "question": "Recycling von Metallschrott" | ||||
|                     }, | ||||
|                     "16": { | ||||
|                     "18": { | ||||
|                         "question": "Recycling von Elektrokleingeräten" | ||||
|                     }, | ||||
|                     "17": { | ||||
|                     "19": { | ||||
|                         "question": "Recycling von Restabfällen" | ||||
|                     } | ||||
|                 } | ||||
|  | @ -6411,49 +6411,49 @@ | |||
|                     "5": { | ||||
|                         "then": "Motoröl kann hier recycelt werden" | ||||
|                     }, | ||||
|                     "6": { | ||||
|                     "7": { | ||||
|                         "then": "Grünabfälle können hier recycelt werden" | ||||
|                     }, | ||||
|                     "7": { | ||||
|                     "8": { | ||||
|                         "then": "Bio-Abfall kann hier recycelt werden" | ||||
|                     }, | ||||
|                     "8": { | ||||
|                     "9": { | ||||
|                         "then": "Glasflaschen können hier recycelt werden" | ||||
|                     }, | ||||
|                     "9": { | ||||
|                     "10": { | ||||
|                         "then": "Glas kann hier recycelt werden" | ||||
|                     }, | ||||
|                     "10": { | ||||
|                     "12": { | ||||
|                         "then": "Zeitungen können hier recycelt werden" | ||||
|                     }, | ||||
|                     "11": { | ||||
|                     "13": { | ||||
|                         "then": "Papier kann hier recycelt werden" | ||||
|                     }, | ||||
|                     "12": { | ||||
|                     "14": { | ||||
|                         "then": "Plastikflaschen können hier recycelt werden" | ||||
|                     }, | ||||
|                     "13": { | ||||
|                     "15": { | ||||
|                         "then": "Kunststoffverpackungen können hier recycelt werden" | ||||
|                     }, | ||||
|                     "14": { | ||||
|                     "16": { | ||||
|                         "then": "Kunststoff kann hier recycelt werden" | ||||
|                     }, | ||||
|                     "15": { | ||||
|                     "17": { | ||||
|                         "then": "Metallschrott kann hier recycelt werden" | ||||
|                     }, | ||||
|                     "16": { | ||||
|                     "18": { | ||||
|                         "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": { | ||||
|                         "then": "Nadeln können hier recycelt werden" | ||||
|                         "then": "Elektrokleingeräte können hier recycelt werden" | ||||
|                     }, | ||||
|                     "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" | ||||
|                     } | ||||
|                 }, | ||||
|  | @ -8308,9 +8308,6 @@ | |||
|             "render": "Mülltonne" | ||||
|         } | ||||
|     }, | ||||
|     "watermill": { | ||||
|         "name": "Wassermühlen" | ||||
|     }, | ||||
|     "windturbine": { | ||||
|         "description": "Moderne Windmühlen zur Stromerzeugung", | ||||
|         "name": "Windräder", | ||||
|  |  | |||
|  | @ -169,6 +169,15 @@ | |||
|     }, | ||||
|     "atm": { | ||||
|         "description": "ATMS to withdraw money", | ||||
|         "filter": { | ||||
|             "1": { | ||||
|                 "options": { | ||||
|                     "0": { | ||||
|                         "question": "With speech output" | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         }, | ||||
|         "name": "ATMs", | ||||
|         "presets": { | ||||
|             "0": { | ||||
|  | @ -186,6 +195,34 @@ | |||
|                 "question": "What brand is this ATM?", | ||||
|                 "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": { | ||||
|                 "render": "The name of this ATM is {name}" | ||||
|             }, | ||||
|  | @ -195,6 +232,26 @@ | |||
|                 }, | ||||
|                 "question": "What company operates this ATM?", | ||||
|                 "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": { | ||||
|  | @ -6323,36 +6380,42 @@ | |||
|                         "question": "Recycling of engine oil" | ||||
|                     }, | ||||
|                     "7": { | ||||
|                         "question": "Recycling of green waste" | ||||
|                         "question": "Recycling of fluorescent tubes" | ||||
|                     }, | ||||
|                     "8": { | ||||
|                         "question": "Recycling of glass bottles" | ||||
|                         "question": "Recycling of green waste" | ||||
|                     }, | ||||
|                     "9": { | ||||
|                         "question": "Recycling of glass" | ||||
|                         "question": "Recycling of glass bottles" | ||||
|                     }, | ||||
|                     "10": { | ||||
|                         "question": "Recycling of newspapers" | ||||
|                         "question": "Recycling of glass" | ||||
|                     }, | ||||
|                     "11": { | ||||
|                         "question": "Recycling of paper" | ||||
|                         "question": "Recycling of light bulbs" | ||||
|                     }, | ||||
|                     "12": { | ||||
|                         "question": "Recycling of plastic bottles" | ||||
|                         "question": "Recycling of newspapers" | ||||
|                     }, | ||||
|                     "13": { | ||||
|                         "question": "Recycling of plastic packaging" | ||||
|                         "question": "Recycling of paper" | ||||
|                     }, | ||||
|                     "14": { | ||||
|                         "question": "Recycling of plastic" | ||||
|                         "question": "Recycling of plastic bottles" | ||||
|                     }, | ||||
|                     "15": { | ||||
|                         "question": "Recycling of scrap metal" | ||||
|                         "question": "Recycling of plastic packaging" | ||||
|                     }, | ||||
|                     "16": { | ||||
|                         "question": "Recycling of small electrical appliances" | ||||
|                         "question": "Recycling of plastic" | ||||
|                     }, | ||||
|                     "17": { | ||||
|                         "question": "Recycling of scrap metal" | ||||
|                     }, | ||||
|                     "18": { | ||||
|                         "question": "Recycling of small electrical appliances" | ||||
|                     }, | ||||
|                     "19": { | ||||
|                         "question": "Recycling of residual waste" | ||||
|                     } | ||||
|                 } | ||||
|  | @ -6412,48 +6475,54 @@ | |||
|                         "then": "Engine oil can be recycled here" | ||||
|                     }, | ||||
|                     "6": { | ||||
|                         "then": "Green waste can be recycled here" | ||||
|                         "then": "Fluorescent tubes can be recycled here" | ||||
|                     }, | ||||
|                     "7": { | ||||
|                         "then": "Organic waste can be recycled here" | ||||
|                         "then": "Green waste can be recycled here" | ||||
|                     }, | ||||
|                     "8": { | ||||
|                         "then": "Glass bottles can be recycled here" | ||||
|                         "then": "Organic waste can be recycled here" | ||||
|                     }, | ||||
|                     "9": { | ||||
|                         "then": "Glass can be recycled here" | ||||
|                         "then": "Glass bottles can be recycled here" | ||||
|                     }, | ||||
|                     "10": { | ||||
|                         "then": "Newspapers can be recycled here" | ||||
|                         "then": "Glass can be recycled here" | ||||
|                     }, | ||||
|                     "11": { | ||||
|                         "then": "Paper can be recycled here" | ||||
|                         "then": "Light bulbs can be recycled here" | ||||
|                     }, | ||||
|                     "12": { | ||||
|                         "then": "Plastic bottles can be recycled here" | ||||
|                         "then": "Newspapers can be recycled here" | ||||
|                     }, | ||||
|                     "13": { | ||||
|                         "then": "Plastic packaging can be recycled here" | ||||
|                         "then": "Paper can be recycled here" | ||||
|                     }, | ||||
|                     "14": { | ||||
|                         "then": "Plastic can be recycled here" | ||||
|                         "then": "Plastic bottles can be recycled here" | ||||
|                     }, | ||||
|                     "15": { | ||||
|                         "then": "Scrap metal can be recycled here" | ||||
|                         "then": "Plastic packaging can be recycled here" | ||||
|                     }, | ||||
|                     "16": { | ||||
|                         "then": "Shoes can be recycled here" | ||||
|                         "then": "Plastic can be recycled here" | ||||
|                     }, | ||||
|                     "17": { | ||||
|                         "then": "Small electrical appliances can be recycled here" | ||||
|                         "then": "Scrap metal can be recycled here" | ||||
|                     }, | ||||
|                     "18": { | ||||
|                         "then": "Small electrical appliances can be recycled here" | ||||
|                         "then": "Shoes can be recycled here" | ||||
|                     }, | ||||
|                     "19": { | ||||
|                         "then": "Needles can be recycled here" | ||||
|                         "then": "Small electrical appliances can be recycled here" | ||||
|                     }, | ||||
|                     "20": { | ||||
|                         "then": "Small electrical appliances can be recycled here" | ||||
|                     }, | ||||
|                     "21": { | ||||
|                         "then": "Needles can be recycled here" | ||||
|                     }, | ||||
|                     "22": { | ||||
|                         "then": "Residual waste can be recycled here" | ||||
|                     } | ||||
|                 }, | ||||
|  | @ -7407,6 +7476,7 @@ | |||
|         "name": "Ticket Validators", | ||||
|         "presets": { | ||||
|             "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" | ||||
|             } | ||||
|         }, | ||||
|  | @ -8308,9 +8378,6 @@ | |||
|             "render": "Waste Disposal" | ||||
|         } | ||||
|     }, | ||||
|     "watermill": { | ||||
|         "name": "Watermill" | ||||
|     }, | ||||
|     "windturbine": { | ||||
|         "description": "Modern windmills generating electricity", | ||||
|         "name": "wind turbine", | ||||
|  |  | |||
|  | @ -3189,28 +3189,28 @@ | |||
|                     "6": { | ||||
|                         "question": "Reciclaje de aceite de motor" | ||||
|                     }, | ||||
|                     "8": { | ||||
|                     "9": { | ||||
|                         "question": "Reciclaje de botellas de cristal" | ||||
|                     }, | ||||
|                     "9": { | ||||
|                     "10": { | ||||
|                         "question": "Reciclaje de cristal" | ||||
|                     }, | ||||
|                     "10": { | ||||
|                     "12": { | ||||
|                         "question": "Reciclaje de periódicos" | ||||
|                     }, | ||||
|                     "11": { | ||||
|                     "13": { | ||||
|                         "question": "Reciclaje de papel" | ||||
|                     }, | ||||
|                     "12": { | ||||
|                     "14": { | ||||
|                         "question": "Reciclaje de botellas de papel" | ||||
|                     }, | ||||
|                     "13": { | ||||
|                     "15": { | ||||
|                         "question": "Reciclaje de embalajes plásticos" | ||||
|                     }, | ||||
|                     "14": { | ||||
|                     "16": { | ||||
|                         "question": "Reciclaje de plástico" | ||||
|                     }, | ||||
|                     "15": { | ||||
|                     "17": { | ||||
|                         "question": "Reciclaje de chatarra" | ||||
|                     } | ||||
|                 } | ||||
|  | @ -3266,34 +3266,34 @@ | |||
|                     "5": { | ||||
|                         "then": "Aquí se puede reciclar aceite de motor" | ||||
|                     }, | ||||
|                     "7": { | ||||
|                     "8": { | ||||
|                         "then": "Aquí se pueden reciclar residuos orgánicos" | ||||
|                     }, | ||||
|                     "8": { | ||||
|                     "9": { | ||||
|                         "then": "Aquí se pueden reciclar botellas de cristal" | ||||
|                     }, | ||||
|                     "9": { | ||||
|                     "10": { | ||||
|                         "then": "Aquí se puede reciclar cristal" | ||||
|                     }, | ||||
|                     "10": { | ||||
|                     "12": { | ||||
|                         "then": "Aquí se pueden reciclar periódicos" | ||||
|                     }, | ||||
|                     "11": { | ||||
|                     "13": { | ||||
|                         "then": "Aquí se puede reciclar papel" | ||||
|                     }, | ||||
|                     "12": { | ||||
|                     "14": { | ||||
|                         "then": "Aquí se pueden reciclar botellas de plástico" | ||||
|                     }, | ||||
|                     "13": { | ||||
|                     "15": { | ||||
|                         "then": "Aquí se pueden reciclar embalajes plásticos" | ||||
|                     }, | ||||
|                     "14": { | ||||
|                     "16": { | ||||
|                         "then": "Aquí se puede reciclar plástico" | ||||
|                     }, | ||||
|                     "15": { | ||||
|                     "17": { | ||||
|                         "then": "Aquí se puede reciclar chatarra" | ||||
|                     }, | ||||
|                     "16": { | ||||
|                     "18": { | ||||
|                         "then": "Aquí se pueden reciclar zapatos" | ||||
|                     } | ||||
|                 }, | ||||
|  |  | |||
|  | @ -1766,37 +1766,37 @@ | |||
|                     "6": { | ||||
|                         "question": "Riciclo di olio da motore" | ||||
|                     }, | ||||
|                     "7": { | ||||
|                     "8": { | ||||
|                         "question": "Riciclo di umido" | ||||
|                     }, | ||||
|                     "8": { | ||||
|                     "9": { | ||||
|                         "question": "Riciclo di bottiglie di vetro" | ||||
|                     }, | ||||
|                     "9": { | ||||
|                     "10": { | ||||
|                         "question": "Riciclo di vetro" | ||||
|                     }, | ||||
|                     "10": { | ||||
|                     "12": { | ||||
|                         "question": "Riciclo di giornali" | ||||
|                     }, | ||||
|                     "11": { | ||||
|                     "13": { | ||||
|                         "question": "Riciclo di carta" | ||||
|                     }, | ||||
|                     "12": { | ||||
|                     "14": { | ||||
|                         "question": "Riciclo di bottiglie di plastica" | ||||
|                     }, | ||||
|                     "13": { | ||||
|                     "15": { | ||||
|                         "question": "Riciclo di confezioni di plastica" | ||||
|                     }, | ||||
|                     "14": { | ||||
|                     "16": { | ||||
|                         "question": "Riciclo di plastica" | ||||
|                     }, | ||||
|                     "15": { | ||||
|                     "17": { | ||||
|                         "question": "Riciclo di rottami metallici" | ||||
|                     }, | ||||
|                     "16": { | ||||
|                     "18": { | ||||
|                         "question": "Riciclo di piccoli elettrodomestici" | ||||
|                     }, | ||||
|                     "17": { | ||||
|                     "19": { | ||||
|                         "question": "Riciclo di secco" | ||||
|                     } | ||||
|                 } | ||||
|  | @ -1855,49 +1855,49 @@ | |||
|                     "5": { | ||||
|                         "then": "Olio di motore" | ||||
|                     }, | ||||
|                     "6": { | ||||
|                     "7": { | ||||
|                         "then": "Verde" | ||||
|                     }, | ||||
|                     "7": { | ||||
|                     "8": { | ||||
|                         "then": "Umido" | ||||
|                     }, | ||||
|                     "8": { | ||||
|                     "9": { | ||||
|                         "then": "Bottiglie di vetro" | ||||
|                     }, | ||||
|                     "9": { | ||||
|                     "10": { | ||||
|                         "then": "Vetro" | ||||
|                     }, | ||||
|                     "10": { | ||||
|                     "12": { | ||||
|                         "then": "Giornali" | ||||
|                     }, | ||||
|                     "11": { | ||||
|                     "13": { | ||||
|                         "then": "Carta" | ||||
|                     }, | ||||
|                     "12": { | ||||
|                     "14": { | ||||
|                         "then": "Bottiglie di platica" | ||||
|                     }, | ||||
|                     "13": { | ||||
|                     "15": { | ||||
|                         "then": "Confezioni di plastica" | ||||
|                     }, | ||||
|                     "14": { | ||||
|                     "16": { | ||||
|                         "then": "Plastica" | ||||
|                     }, | ||||
|                     "15": { | ||||
|                     "17": { | ||||
|                         "then": "Rottami metallici" | ||||
|                     }, | ||||
|                     "16": { | ||||
|                     "18": { | ||||
|                         "then": "Scarpe" | ||||
|                     }, | ||||
|                     "17": { | ||||
|                         "then": "Piccoli elettrodomestici" | ||||
|                     }, | ||||
|                     "18": { | ||||
|                         "then": "Piccoli elettrodomestici" | ||||
|                     }, | ||||
|                     "19": { | ||||
|                         "then": "Aghi e oggetti appuntiti" | ||||
|                         "then": "Piccoli elettrodomestici" | ||||
|                     }, | ||||
|                     "20": { | ||||
|                         "then": "Piccoli elettrodomestici" | ||||
|                     }, | ||||
|                     "21": { | ||||
|                         "then": "Aghi e oggetti appuntiti" | ||||
|                     }, | ||||
|                     "22": { | ||||
|                         "then": "Secco" | ||||
|                     } | ||||
|                 }, | ||||
|  |  | |||
|  | @ -5988,36 +5988,42 @@ | |||
|                         "question": "Recycling van motorolie" | ||||
|                     }, | ||||
|                     "7": { | ||||
|                         "question": "Recycling van groen afval" | ||||
|                         "question": "Recycling van tl-buizen" | ||||
|                     }, | ||||
|                     "8": { | ||||
|                         "question": "Recycling van glazen flessen" | ||||
|                         "question": "Recycling van groen afval" | ||||
|                     }, | ||||
|                     "9": { | ||||
|                         "question": "Recycling van glas" | ||||
|                         "question": "Recycling van glazen flessen" | ||||
|                     }, | ||||
|                     "10": { | ||||
|                         "question": "Recycling van kranten" | ||||
|                         "question": "Recycling van glas" | ||||
|                     }, | ||||
|                     "11": { | ||||
|                         "question": "Recycling van papier" | ||||
|                         "question": "Recycling van lampen" | ||||
|                     }, | ||||
|                     "12": { | ||||
|                         "question": "Recycling van plastic flessen" | ||||
|                         "question": "Recycling van kranten" | ||||
|                     }, | ||||
|                     "13": { | ||||
|                         "question": "Recycling van plastic verpakking" | ||||
|                         "question": "Recycling van papier" | ||||
|                     }, | ||||
|                     "14": { | ||||
|                         "question": "Recycling van plastic" | ||||
|                         "question": "Recycling van plastic flessen" | ||||
|                     }, | ||||
|                     "15": { | ||||
|                         "question": "Recycling van oud metaal" | ||||
|                         "question": "Recycling van plastic verpakking" | ||||
|                     }, | ||||
|                     "16": { | ||||
|                         "question": "Recycling van kleine elektrische apparaten" | ||||
|                         "question": "Recycling van plastic" | ||||
|                     }, | ||||
|                     "17": { | ||||
|                         "question": "Recycling van oud metaal" | ||||
|                     }, | ||||
|                     "18": { | ||||
|                         "question": "Recycling van kleine elektrische apparaten" | ||||
|                     }, | ||||
|                     "19": { | ||||
|                         "question": "Recycling van restafval" | ||||
|                     } | ||||
|                 } | ||||
|  | @ -6077,48 +6083,54 @@ | |||
|                         "then": "Motorolie kan hier gerecycled worden" | ||||
|                     }, | ||||
|                     "6": { | ||||
|                         "then": "Groen afval kan hier gerecycled worden" | ||||
|                         "then": "TL-buizen kunnen hier gerecycled worden" | ||||
|                     }, | ||||
|                     "7": { | ||||
|                         "then": "Organisch afval kan hier gerecycled worden" | ||||
|                         "then": "Groen afval kan hier gerecycled worden" | ||||
|                     }, | ||||
|                     "8": { | ||||
|                         "then": "Glazen flessen kunnen hier gerecycled worden" | ||||
|                         "then": "Organisch afval kan hier gerecycled worden" | ||||
|                     }, | ||||
|                     "9": { | ||||
|                         "then": "Glas kan hier gerecycled worden" | ||||
|                         "then": "Glazen flessen kunnen hier gerecycled worden" | ||||
|                     }, | ||||
|                     "10": { | ||||
|                         "then": "Kranten kunnen hier gerecycled worden" | ||||
|                         "then": "Glas kan hier gerecycled worden" | ||||
|                     }, | ||||
|                     "11": { | ||||
|                         "then": "Papier kan hier gerecycled worden" | ||||
|                         "then": "Lampen kunnen hier gerecycled worden" | ||||
|                     }, | ||||
|                     "12": { | ||||
|                         "then": "Plastic flessen kunnen hier gerecycled worden" | ||||
|                         "then": "Kranten kunnen hier gerecycled worden" | ||||
|                     }, | ||||
|                     "13": { | ||||
|                         "then": "Plastic verpakking kan hier gerecycled worden" | ||||
|                         "then": "Papier kan hier gerecycled worden" | ||||
|                     }, | ||||
|                     "14": { | ||||
|                         "then": "Plastic kan hier gerecycled worden" | ||||
|                         "then": "Plastic flessen kunnen hier gerecycled worden" | ||||
|                     }, | ||||
|                     "15": { | ||||
|                         "then": "Oud metaal kan hier gerecycled worden" | ||||
|                         "then": "Plastic verpakking kan hier gerecycled worden" | ||||
|                     }, | ||||
|                     "16": { | ||||
|                         "then": "Schoenen kunnen hier gerecycled worden" | ||||
|                         "then": "Plastic kan hier gerecycled worden" | ||||
|                     }, | ||||
|                     "17": { | ||||
|                         "then": "Kleine elektrische apparaten kunnen hier gerecycled worden" | ||||
|                         "then": "Oud metaal kan hier gerecycled worden" | ||||
|                     }, | ||||
|                     "18": { | ||||
|                         "then": "Kleine elektrische apparaten kunnen hier gerecycled worden" | ||||
|                         "then": "Schoenen kunnen hier gerecycled worden" | ||||
|                     }, | ||||
|                     "19": { | ||||
|                         "then": "Injectienaalden kunnen hier gerecycled worden" | ||||
|                         "then": "Kleine elektrische apparaten kunnen hier gerecycled worden" | ||||
|                     }, | ||||
|                     "20": { | ||||
|                         "then": "Kleine elektrische apparaten kunnen hier gerecycled worden" | ||||
|                     }, | ||||
|                     "21": { | ||||
|                         "then": "Injectienaalden kunnen hier gerecycled worden" | ||||
|                     }, | ||||
|                     "22": { | ||||
|                         "then": "Restafval kan hier gerecycled worden" | ||||
|                     } | ||||
|                 }, | ||||
|  | @ -7872,59 +7884,6 @@ | |||
|             "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": { | ||||
|         "description": "Windturbines (moderne windmolens die elektriciteit genereren)", | ||||
|         "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": { | ||||
|             "question": "Wat is het telefoonnummer van {title()}?" | ||||
|         }, | ||||
|  |  | |||
|  | @ -443,14 +443,14 @@ | |||
|     }, | ||||
|     "onwheels": { | ||||
|         "layers": { | ||||
|             "18": { | ||||
|             "19": { | ||||
|                 "override": { | ||||
|                     "=title": { | ||||
|                         "render": "Estadístiques" | ||||
|                     } | ||||
|                 } | ||||
|             }, | ||||
|             "19": { | ||||
|             "20": { | ||||
|                 "override": { | ||||
|                     "+tagRenderings": { | ||||
|                         "0": { | ||||
|  |  | |||
|  | @ -680,14 +680,14 @@ | |||
|                     } | ||||
|                 } | ||||
|             }, | ||||
|             "18": { | ||||
|             "19": { | ||||
|                 "override": { | ||||
|                     "=title": { | ||||
|                         "render": "Statistikker" | ||||
|                     } | ||||
|                 } | ||||
|             }, | ||||
|             "19": { | ||||
|             "20": { | ||||
|                 "override": { | ||||
|                     "+tagRenderings": { | ||||
|                         "0": { | ||||
|  |  | |||
|  | @ -909,14 +909,14 @@ | |||
|                     } | ||||
|                 } | ||||
|             }, | ||||
|             "18": { | ||||
|             "19": { | ||||
|                 "override": { | ||||
|                     "=title": { | ||||
|                         "render": "Statistik" | ||||
|                     } | ||||
|                 } | ||||
|             }, | ||||
|             "19": { | ||||
|             "20": { | ||||
|                 "override": { | ||||
|                     "+tagRenderings": { | ||||
|                         "0": { | ||||
|  |  | |||
|  | @ -909,14 +909,14 @@ | |||
|                     } | ||||
|                 } | ||||
|             }, | ||||
|             "18": { | ||||
|             "19": { | ||||
|                 "override": { | ||||
|                     "=title": { | ||||
|                         "render": "Statistics" | ||||
|                     } | ||||
|                 } | ||||
|             }, | ||||
|             "19": { | ||||
|             "20": { | ||||
|                 "override": { | ||||
|                     "+tagRenderings": { | ||||
|                         "0": { | ||||
|  |  | |||
|  | @ -888,14 +888,14 @@ | |||
|                     } | ||||
|                 } | ||||
|             }, | ||||
|             "18": { | ||||
|             "19": { | ||||
|                 "override": { | ||||
|                     "=title": { | ||||
|                         "render": "Statistiques" | ||||
|                     } | ||||
|                 } | ||||
|             }, | ||||
|             "19": { | ||||
|             "20": { | ||||
|                 "override": { | ||||
|                     "+tagRenderings": { | ||||
|                         "0": { | ||||
|  |  | |||
|  | @ -441,14 +441,14 @@ | |||
|     }, | ||||
|     "onwheels": { | ||||
|         "layers": { | ||||
|             "18": { | ||||
|             "19": { | ||||
|                 "override": { | ||||
|                     "=title": { | ||||
|                         "render": "Statistikk" | ||||
|                     } | ||||
|                 } | ||||
|             }, | ||||
|             "19": { | ||||
|             "20": { | ||||
|                 "override": { | ||||
|                     "+tagRenderings": { | ||||
|                         "0": { | ||||
|  |  | |||
|  | @ -1036,14 +1036,14 @@ | |||
|                     } | ||||
|                 } | ||||
|             }, | ||||
|             "18": { | ||||
|             "19": { | ||||
|                 "override": { | ||||
|                     "=title": { | ||||
|                         "render": "Statistieken" | ||||
|                     } | ||||
|                 } | ||||
|             }, | ||||
|             "19": { | ||||
|             "20": { | ||||
|                 "override": { | ||||
|                     "+tagRenderings": { | ||||
|                         "0": { | ||||
|  |  | |||
|  | @ -168,14 +168,14 @@ | |||
|     }, | ||||
|     "onwheels": { | ||||
|         "layers": { | ||||
|             "18": { | ||||
|             "19": { | ||||
|                 "override": { | ||||
|                     "=title": { | ||||
|                         "render": "انکڑے" | ||||
|                     } | ||||
|                 } | ||||
|             }, | ||||
|             "19": { | ||||
|             "20": { | ||||
|                 "override": { | ||||
|                     "+tagRenderings": { | ||||
|                         "0": { | ||||
|  |  | |||
							
								
								
									
										4111
									
								
								package-lock.json
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										4111
									
								
								package-lock.json
									
										
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							|  | @ -1,6 +1,6 @@ | |||
| { | ||||
|   "name": "mapcomplete", | ||||
|   "version": "0.0.5", | ||||
|   "version": "0.25.1", | ||||
|   "repository": "https://github.com/pietervdvn/MapComplete", | ||||
|   "description": "A small website to edit OSM easily", | ||||
|   "bugs": "https://github.com/pietervdvn/MapComplete/issues", | ||||
|  |  | |||
|  | @ -16,7 +16,13 @@ import SharedTagRenderings from "../Customizations/SharedTagRenderings" | |||
| import { writeFile } from "fs" | ||||
| import Translations from "../UI/i18n/Translations" | ||||
| 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( | ||||
|     filename, | ||||
|     html: BaseUIElement, | ||||
|  | @ -217,5 +223,13 @@ WriteFile("./Docs/URL_Parameters.md", QueryParameterDocumentation.GenerateQueryP | |||
|     "Logic/Web/QueryParameters.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") | ||||
|  |  | |||
|  | @ -279,7 +279,23 @@ function main(args: string[]) { | |||
|     const invalidLicenses = licenseInfos | ||||
|         .filter((l) => (l.license ?? "") === "") | ||||
|         .map((l) => `License for artwork ${l.path} is empty string or undefined`) | ||||
| 
 | ||||
|     let invalid = 0 | ||||
|     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) { | ||||
|             if (source == "") { | ||||
|                 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.` | ||||
|         console.log(missingLicenses.concat(invalidLicenses).join("\n")) | ||||
|         console.error(msg) | ||||
|  |  | |||
|  | @ -7594,7 +7594,7 @@ describe("GenerateCache", () => { | |||
|         } | ||||
|         mkdirSync(dir + "np-cache") | ||||
|         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([ | ||||
|             "natuurpunt", | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue