From 2177db376ccbe1aef3e662abf7fc71dc074c93cc Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Tue, 17 Nov 2020 16:29:51 +0100 Subject: [PATCH] More fancyness, less bugs --- Customizations/JSON/LayerConfig.ts | 21 +- Customizations/JSON/LayerConfigJson.ts | 3 +- Customizations/JSON/LayoutConfig.ts | 2 +- Customizations/SharedLayers.ts | 32 +- Logic/FilteredLayer.ts | 135 +++---- Logic/MetaTagging.ts | 37 +- Logic/UpdateFromOverpass.ts | 1 + Logic/Web/Imgur.ts | 4 +- State.ts | 2 +- Svg.ts | 4 +- UI/Popup/EditableTagRendering.ts | 3 +- UI/Popup/FeatureInfoBox.ts | 4 +- UI/SpecialVisualizations.ts | 31 +- assets/layers/direction/direction.json | 4 +- .../surveillance_cameras.json | 379 ++++++++++++++++++ assets/questions/questions.json | 2 +- assets/svg/crosshair.svg | 90 ++--- assets/svg/direction_gradient.svg | 48 ++- .../{cam.svg => cam_left.svg} | 58 +-- .../themes/surveillance_cameras/cam_right.svg | 68 ++++ .../surveillance_cameras/direction_360.svg | 78 ++++ assets/themes/surveillance_cameras/dome.svg | 38 +- assets/themes/surveillance_cameras/logo.svg | 9 +- .../surveillance_cameras.json | 348 +--------------- index.css | 5 + package.json | 2 +- scripts/createLayouts.ts | 20 +- 27 files changed, 821 insertions(+), 607 deletions(-) create mode 100644 assets/layers/surveillance_cameras/surveillance_cameras.json rename assets/themes/surveillance_cameras/{cam.svg => cam_left.svg} (58%) create mode 100644 assets/themes/surveillance_cameras/cam_right.svg create mode 100644 assets/themes/surveillance_cameras/direction_360.svg diff --git a/Customizations/JSON/LayerConfig.ts b/Customizations/JSON/LayerConfig.ts index 533658b..4d8279c 100644 --- a/Customizations/JSON/LayerConfig.ts +++ b/Customizations/JSON/LayerConfig.ts @@ -11,7 +11,7 @@ import Svg from "../../Svg"; import {SubstitutedTranslation} from "../../UI/SpecialVisualizations"; import {Utils} from "../../Utils"; import Combine from "../../UI/Base/Combine"; -import {Browser} from "leaflet"; +import {VariableUiElement} from "../../UI/Base/VariableUIElement"; export default class LayerConfig { @@ -56,7 +56,8 @@ export default class LayerConfig { tagRenderings: TagRenderingConfig []; - constructor(json: LayerConfigJson, context?: string) { + constructor(json: LayerConfigJson, roamingRenderings: TagRenderingConfig[], + context?: string) { context = context + "." + json.id; this.id = json.id; @@ -140,7 +141,7 @@ export default class LayerConfig { } - public GenerateLeafletStyle(tags: any): + public GenerateLeafletStyle(tags: any, clickable: boolean): { color: string; icon: { @@ -149,7 +150,8 @@ export default class LayerConfig { iconAnchor: [number, number]; iconSize: [number, number]; html: string; - rotation: number; + rotation: string; + className?: string; }; weight: number; dashArray: number[] } { @@ -186,7 +188,7 @@ export default class LayerConfig { } const weight = rendernum(this.width, 5); - const rotation = rendernum(this.rotation, 0); + const rotation = render(this.rotation, "0deg"); const iconW = num(iconSize[0]); @@ -209,12 +211,14 @@ export default class LayerConfig { anchorH = iconH; } - let html = ``; + + let html = ``; + if (iconUrl.startsWith(Utils.assets_path)) { const key = iconUrl.substr(Utils.assets_path.length); html = new Combine([ (Svg.All[key] as string).replace(/stop-color:#000000/g, 'stop-color:' + color) - ]).SetStyle(`width:100%;height:100%;rotate:${rotation}deg;display:block;`).Render(); + ]).SetStyle(`width:100%;height:100%;rotate:${rotation};display:block;`).Render(); } return { icon: @@ -224,7 +228,8 @@ export default class LayerConfig { iconAnchor: [anchorW, anchorH], popupAnchor: [0, 3 - anchorH], rotation: rotation, - iconUrl: iconUrl + iconUrl: iconUrl, + className: clickable ? "leaflet-div-icon" : "leaflet-div-icon unclickable" }, color: color, weight: weight, diff --git a/Customizations/JSON/LayerConfigJson.ts b/Customizations/JSON/LayerConfigJson.ts index b7371c5..98df035 100644 --- a/Customizations/JSON/LayerConfigJson.ts +++ b/Customizations/JSON/LayerConfigJson.ts @@ -66,7 +66,8 @@ export interface LayerConfigJson { */ iconSize?: string | TagRenderingConfigJson; /** - * The rotation of an icon, useful for e.g. directions + * The rotation of an icon, useful for e.g. directions. + * Usage: as if it were a css property for 'rotate', thus has to end with 'deg', e.g. `90deg`, `{direction}deg`, `calc(90deg - {camera:direction}deg)`` */ rotation?: string | TagRenderingConfigJson; diff --git a/Customizations/JSON/LayoutConfig.ts b/Customizations/JSON/LayoutConfig.ts index 2a22ccd..b914a79 100644 --- a/Customizations/JSON/LayoutConfig.ts +++ b/Customizations/JSON/LayoutConfig.ts @@ -80,7 +80,7 @@ export default class LayoutConfig { } else { throw "Unkown fixed layer " + layer; } - return new LayerConfig(layer, `${this.id}.layers[${i}]`); + return new LayerConfig(layer, this.roamingRenderings,`${this.id}.layers[${i}]`); }); this.hideFromOverview = json.hideFromOverview ?? false; diff --git a/Customizations/SharedLayers.ts b/Customizations/SharedLayers.ts index 202b289..4beaa3e 100644 --- a/Customizations/SharedLayers.ts +++ b/Customizations/SharedLayers.ts @@ -14,6 +14,7 @@ import * as bike_cleaning from "../assets/layers/bike_cleaning/bike_cleaning.jso import * as maps from "../assets/layers/maps/maps.json" import * as information_boards from "../assets/layers/information_board/information_board.json" import * as direction from "../assets/layers/direction/direction.json" +import * as surveillance_camera from "../assets/layers/surveillance_cameras/surveillance_cameras.json" import LayerConfig from "./JSON/LayerConfig"; export default class SharedLayers { @@ -25,21 +26,22 @@ export default class SharedLayers { private static getSharedLayers(){ const sharedLayersList = [ - new LayerConfig(drinkingWater, "shared_layers"), - new LayerConfig(ghostbikes, "shared_layers"), - new LayerConfig(viewpoint, "shared_layers"), - new LayerConfig(bike_parking, "shared_layers"), - new LayerConfig(bike_repair_station, "shared_layers"), - new LayerConfig(bike_monitoring_station, "shared_layers"), - new LayerConfig(birdhides, "shared_layers"), - new LayerConfig(nature_reserve, "shared_layers"), - new LayerConfig(bike_cafes, "shared_layers"), - new LayerConfig(cycling_themed_objects, "shared_layers"), - new LayerConfig(bike_shops, "shared_layers"), - new LayerConfig(bike_cleaning, "shared_layers"), - new LayerConfig(maps, "shared_layers"), - new LayerConfig(direction, "shared_layers"), - new LayerConfig(information_boards, "shared_layers") + new LayerConfig(drinkingWater,[], "shared_layers"), + new LayerConfig(ghostbikes,[], "shared_layers"), + new LayerConfig(viewpoint,[], "shared_layers"), + new LayerConfig(bike_parking,[], "shared_layers"), + new LayerConfig(bike_repair_station,[], "shared_layers"), + new LayerConfig(bike_monitoring_station,[], "shared_layers"), + new LayerConfig(birdhides,[], "shared_layers"), + new LayerConfig(nature_reserve,[], "shared_layers"), + new LayerConfig(bike_cafes,[], "shared_layers"), + new LayerConfig(cycling_themed_objects,[], "shared_layers"), + new LayerConfig(bike_shops,[], "shared_layers"), + new LayerConfig(bike_cleaning,[], "shared_layers"), + new LayerConfig(maps,[], "shared_layers"), + new LayerConfig(direction,[], "shared_layers"), + new LayerConfig(information_boards,[], "shared_layers"), + new LayerConfig(surveillance_camera,[], "shared_layers") ]; const sharedLayers = new Map(); diff --git a/Logic/FilteredLayer.ts b/Logic/FilteredLayer.ts index bafdc28..c978185 100644 --- a/Logic/FilteredLayer.ts +++ b/Logic/FilteredLayer.ts @@ -129,88 +129,65 @@ export class FilteredLayer { let self = this; this._geolayer = L.geoJSON(data, { - style: feature => - self.layerDef.GenerateLeafletStyle(feature.properties), - pointToLayer: function (feature, latLng) { - // Point to layer converts the 'point' to a layer object - as the geojson layer natively cannot handle points - // Click handling is done in the next step + style: feature => + self.layerDef.GenerateLeafletStyle(feature.properties, self._showOnPopup !== undefined), + pointToLayer: function (feature, latLng) { + // Point to layer converts the 'point' to a layer object - as the geojson layer natively cannot handle points + // Click handling is done in the next step - const style = self.layerDef.GenerateLeafletStyle(feature.properties); - let marker; - if (style.icon === undefined) { - marker = L.circle(latLng, { - radius: 25, - color: style.color - }); - } else if (style.icon.iconUrl.startsWith("$circle")) { - marker = L.circle(latLng, { - radius: 25, - color: style.color - }); - } else { - marker = L.marker(latLng, { - icon: L.divIcon(style.icon) - }); - } - return marker; - }, - onEachFeature: function (feature, layer: Layer) { - - if (self._showOnPopup === undefined) { - // No popup contents defined -> don't do anything - return; - } - - - function openPopup(latlng: any) { - if (layer.getPopup() === undefined - && (window.screen.availHeight > 600 || window.screen.availWidth > 600) // We DON'T trigger this code on small screens! No need to create a popup - ) { - const popup = L.popup({ - autoPan: true, - closeOnEscapeKey: true, - }, layer); - - popup.setLatLng(latlng) - - layer.bindPopup(popup); - const eventSource = State.state.allElements.addOrGetElement(feature); - const uiElement = self._showOnPopup(eventSource, feature); - // We first render the UIelement (which'll still need an update later on...) - // But at least it'll be visible already - popup.setContent(uiElement.Render()); - popup.openOn(State.state.bm.map); - // popup.openOn(State.state.bm.map); - // ANd we perform the pending update - uiElement.Update(); - // @ts-ignore - popup.Update = () => { - uiElement.Update(); - } - } else { - // @ts-ignore - layer.getPopup().Update(); - } - - - // We set the element as selected... - State.state.selectedElement.setData(feature); - - } - - layer.on("click", (e) => { - // @ts-ignore - openPopup(e.latlng); - // We mark the event as consumed - L.DomEvent.stop(e); + const style = self.layerDef.GenerateLeafletStyle(feature.properties, self._showOnPopup !== undefined); + let marker; + if (style.icon === undefined) { + marker = L.circle(latLng, { + radius: 25, + color: style.color + }); + } else if (style.icon.iconUrl.startsWith("$circle")) { + marker = L.circle(latLng, { + radius: 25, + color: style.color + }); + } else { + marker = L.marker(latLng, { + icon: L.divIcon(style.icon) }); - - if (feature.properties.id.replace(/\//g, "_") === Hash.Get().data) { - const center = GeoOperations.centerpoint(feature).geometry.coordinates; - openPopup({lat: center[1], lng: center[0]}) - } - } + return marker; + }, + onEachFeature: function (feature, layer: Layer) { + + if (self._showOnPopup === undefined) { + // No popup contents defined -> don't do anything + return; + } + const popup = L.popup({ + autoPan: true, + closeOnEscapeKey: true, + }, layer); + + let uiElement: UIElement; + + const eventSource = State.state.allElements.addOrGetElement(feature); + uiElement = self._showOnPopup(eventSource, feature); + popup.setContent(uiElement.Render()); + layer.bindPopup(popup); + // We first render the UIelement (which'll still need an update later on...) + // But at least it'll be visible already + + + layer.on("click", (e) => { + // We set the element as selected... + State.state.selectedElement.setData(feature); + uiElement.Update(); + }); + + if (feature.properties.id.replace(/\//g, "_") === Hash.Get().data) { + const center = GeoOperations.centerpoint(feature).geometry.coordinates; + popup.setLatLng({lat: center[1], lng: center[0]}); + popup.openOn(State.state.bm.map) + } + + } }); if (this.combinedIsDisplayed.data) { diff --git a/Logic/MetaTagging.ts b/Logic/MetaTagging.ts index e4acd42..d2991ea 100644 --- a/Logic/MetaTagging.ts +++ b/Logic/MetaTagging.ts @@ -82,7 +82,7 @@ export default class MetaTagging { }) ) private static isOpen = new SimpleMetaTagger( - ["_isOpen", "_isOpen:description"], "If 'opening_hours' is present, it will add the current state of the feature (being 'yes' or 'no", + ["_isOpen", "_isOpen:description"], "If 'opening_hours' is present, it will add the current state of the feature (being 'yes' or 'no')", (feature => { const tagsSource = State.state.allElements.addOrGetElement(feature); tagsSource.addCallback(tags => { @@ -123,16 +123,40 @@ export default class MetaTagging { }) ) - public static carriageWayWidth = new SimpleMetaTagger( - ["_width:needed","_width:needed:no_pedestrians", "_width:difference"], + private static directionSimplified = new SimpleMetaTagger( + ["_direction:simplified", "_direction:leftright"], "_direction:simplified turns 'camera:direction' and 'direction' into either 0, 45, 90, 135, 180, 225, 270 or 315, whichever is closest. _direction:leftright is either 'left' or 'right', which is left-looking on the map or 'right-looking' on the map", + (feature => { + const tags = feature.properties; + const direction = tags["camera:direction"] ?? tags["direction"]; + if (direction === undefined) { + return; + } + let n = Number(direction); + if (isNaN(n)) { + return; + } + + // [22.5 -> 67.5] is sector 1 + // [67.5 -> ] is sector 1 + n = (n + 22.5) % 360; + n = Math.floor(n / 45); + tags["_direction:simplified"] = n; + tags["_direction:leftright"] = n <= 3 ? "right" : "left"; + + + }) + ) + + private static carriageWayWidth = new SimpleMetaTagger( + ["_width:needed", "_width:needed:no_pedestrians", "_width:difference"], "Legacy for a specific project calculating the needed width for safe traffic on a road. Only activated if 'width:carriageway' is present", (feature: any, index: number) => { const properties = feature.properties; - if(properties["width:carriageway"] === undefined){ + if (properties["width:carriageway"] === undefined) { return; } - + const carWidth = 2; const cyclistWidth = 1.5; const pedestrianWidth = 0.75; @@ -239,7 +263,8 @@ export default class MetaTagging { MetaTagging.surfaceArea, MetaTagging.country, MetaTagging.isOpen, - MetaTagging.carriageWayWidth + MetaTagging.carriageWayWidth, + MetaTagging.directionSimplified ]; diff --git a/Logic/UpdateFromOverpass.ts b/Logic/UpdateFromOverpass.ts index bbf83c4..62f9a93 100644 --- a/Logic/UpdateFromOverpass.ts +++ b/Logic/UpdateFromOverpass.ts @@ -136,6 +136,7 @@ export class UpdateFromOverpass { const self = this; window?.setTimeout( function () { + self.runningQuery.setData(false) self.update(state) }, this.retries.data * 5000 ) diff --git a/Logic/Web/Imgur.ts b/Logic/Web/Imgur.ts index ec47fc3..6fe620c 100644 --- a/Logic/Web/Imgur.ts +++ b/Logic/Web/Imgur.ts @@ -56,8 +56,8 @@ export class Imgur { }, }; $.ajax(settings).done(function (response) { - const descr : string= response.data.description; - const data : any = {}; + const descr: string = response.data.description ?? ""; + const data: any = {}; for (const tag of descr.split("\n")) { const kv = tag.split(":"); const k = kv[0]; diff --git a/State.ts b/State.ts index a1921d1..3353baf 100644 --- a/State.ts +++ b/State.ts @@ -23,7 +23,7 @@ export default class State { // The singleton of the global state public static state: State; - public static vNumber = "0.1.3-rc4"; + public static vNumber = "0.1.3-rc5"; // The user journey states thresholds when a new feature gets unlocked public static userJourney = { diff --git a/Svg.ts b/Svg.ts index e1a6704..fbcbcdd 100644 --- a/Svg.ts +++ b/Svg.ts @@ -64,7 +64,7 @@ export default class Svg { public static crosshair_blue_svg() { return new FixedUiElement(Svg.crosshair_blue);} public static crosshair_blue_ui() { return new FixedUiElement(Svg.crosshair_blue_img);} - public static crosshair = " image/svg+xml " + public static crosshair = " image/svg+xml " public static crosshair_img = Img.AsImageElement(Svg.crosshair) public static crosshair_svg() { return new FixedUiElement(Svg.crosshair);} public static crosshair_ui() { return new FixedUiElement(Svg.crosshair_img);} @@ -79,7 +79,7 @@ export default class Svg { public static direction_svg() { return new FixedUiElement(Svg.direction);} public static direction_ui() { return new FixedUiElement(Svg.direction_img);} - public static direction_gradient = " image/svg+xml " + public static direction_gradient = " image/svg+xml " public static direction_gradient_img = Img.AsImageElement(Svg.direction_gradient) public static direction_gradient_svg() { return new FixedUiElement(Svg.direction_gradient);} public static direction_gradient_ui() { return new FixedUiElement(Svg.direction_gradient_img);} diff --git a/UI/Popup/EditableTagRendering.ts b/UI/Popup/EditableTagRendering.ts index b8e9d5b..ea61c89 100644 --- a/UI/Popup/EditableTagRendering.ts +++ b/UI/Popup/EditableTagRendering.ts @@ -29,6 +29,8 @@ export default class EditableTagRendering extends UIElement { this._answer = new TagRenderingAnswer(tags, configuration); this._answer.SetStyle("width:100%;") + this._question = this.GenerateQuestion(); + this.dumbMode = false; if (this._configuration.question !== undefined) { if (State.state.featureSwitchUserbadge.data) { @@ -66,7 +68,6 @@ export default class EditableTagRendering extends UIElement { InnerRender(): string { if (this._editMode.data) { - this._question = this.GenerateQuestion(); return this._question.Render(); } diff --git a/UI/Popup/FeatureInfoBox.ts b/UI/Popup/FeatureInfoBox.ts index 71145f9..9a669dc 100644 --- a/UI/Popup/FeatureInfoBox.ts +++ b/UI/Popup/FeatureInfoBox.ts @@ -21,7 +21,7 @@ export class FeatureInfoBox extends UIElement { layerConfig: LayerConfig ) { super(); - if(layerConfig === undefined){ + if (layerConfig === undefined) { throw "Undefined layerconfig" } this._tags = tags; @@ -30,7 +30,7 @@ export class FeatureInfoBox extends UIElement { this._title = layerConfig.title === undefined ? undefined : new TagRenderingAnswer(tags, layerConfig.title) - .SetClass("featureinfobox-title"); + .SetClass("featureinfobox-title"); this._titleIcons = new Combine( layerConfig.titleIcons.map(icon => new TagRenderingAnswer(tags, icon))) .SetClass("featureinfobox-icons"); diff --git a/UI/SpecialVisualizations.ts b/UI/SpecialVisualizations.ts index 8e1b31f..a20a1bf 100644 --- a/UI/SpecialVisualizations.ts +++ b/UI/SpecialVisualizations.ts @@ -102,7 +102,20 @@ export default class SpecialVisualizations { args: { name: string, defaultValue?: string, doc: string }[] }[] = - [ + [{ + funcName: "all_tags", + docs: "Prints all key-value pairs of the object - used for debugging", + args: [], + constr: ((tags: UIEventSource) => { + return new VariableUiElement(tags.map(tags => { + const parts = []; + for (const key in tags) { + parts.push(key + "=" + tags[key]); + } + return parts.join("
") + })).SetStyle("border: 1px solid black; border-radius: 1em;padding:1em;display:block;") + }) + }, { funcName: "image_carousel", docs: "Creates an image carousel for the given sources. An attempt will be made to guess what source is used. Supported: Wikidata identifiers, Wikipedia pages, Wikimedia categories, IMGUR (with attribution, direct links)", @@ -170,21 +183,7 @@ export default class SpecialVisualizations { return new VariableUiElement(source.map(data => data[neededValue] ?? "Loading...")); } }, - - { - funcName: "all_tags", - docs: "Prints all key-value pairs of the object - used for debugging", - args:[], - constr: ((tags: UIEventSource) => { - return new VariableUiElement(tags.map(tags => { - const parts = []; - for (const key in tags) { - parts.push(key+"="+tags[key]); - } - return parts.join("
") - })).SetStyle("border: 1px solid black; border-radius: 1em;padding:1em;display:block;") - }) - } + ] static HelpMessage: UIElement = SpecialVisualizations.GenHelpMessage(); diff --git a/assets/layers/direction/direction.json b/assets/layers/direction/direction.json index ac096e2..f153319 100644 --- a/assets/layers/direction/direction.json +++ b/assets/layers/direction/direction.json @@ -16,11 +16,11 @@ "tagRenderings": [], "icon": "./assets/svg/direction_gradient.svg", "rotation": { - "render": "{camera:direction}", + "render": "{camera:direction}deg", "mappings": [ { "if": "direction~*", - "then": "{direction}" + "then": "{direction}deg" } ] }, diff --git a/assets/layers/surveillance_cameras/surveillance_cameras.json b/assets/layers/surveillance_cameras/surveillance_cameras.json new file mode 100644 index 0000000..bf4c430 --- /dev/null +++ b/assets/layers/surveillance_cameras/surveillance_cameras.json @@ -0,0 +1,379 @@ +{ + "id": "surveillance_cameras", + "name": { + "en": "Surveillance camera's", + "nl": "Bewakingscamera's" + }, + "minzoom": 12, + "overpassTags": { + "and": [ + "man_made=surveillance", + { + "or": [ + "surveillance:type=camera", + "surveillance:type=ALPR", + "surveillance:type=ANPR" + ] + } + ] + }, + "title": { + "render": { + "en": "Surveillance Camera", + "nl": "Bewakingscamera" + } + }, + "description": {}, + "tagRenderings": [ + "images", + { + "#": "Camera type: fixed; panning; dome", + "question": { + "en": "What kind of camera is this?", + "nl": "Wat voor soort camera is dit?" + }, + "mappings": [ + { + "if": { + "and": [ + "camera:type=fixed" + ] + }, + "then": { + "en": "A fixed (non-moving) camera", + "nl": "Een vaste camera" + } + }, + { + "if": { + "and": [ + "camera:type=dome" + ] + }, + "then": { + "en": "A dome camera (which can turn)", + "nl": "Een dome (bolvormige camera die kan draaien)" + } + }, + { + "if": { + "and": [ + "camera:type=panning" + ] + }, + "then": { + "en": "A panning camera", + "nl": "Een camera die (met een motor) van links naar rechts kan draaien" + } + } + ] + }, + { + "#": "direction. We don't ask this for a dome on a pole or ceiling as it has a 360° view", + "question": { + "en": "In which geographical direction does this camera film?", + "nl": "Naar welke geografische richting filmt deze camera?" + }, + "render": "Films to {camera:direction}", + "condition": { + "or": [ + "camera:type!=dome", + { + "and": [ + "camera:mount!=ceiling", + "camera:mount!=pole" + ] + } + ] + }, + "freeform": { + "key": "camera:direction", + "type": "direction" + } + }, + { + "#": "Operator", + "freeform": { + "key": "operator" + }, + "question": { + "en": "Who operates this CCTV?", + "nl": "Wie beheert deze bewakingscamera?" + }, + "render": { + "en": "Operated by {operator}", + "nl": "Beheer door {operator}" + } + }, + { + "#": "Surveillance type: public, outdoor, indoor", + "question": { + "en": "What kind of surveillance is this camera", + "nl": "Wat soort bewaking wordt hier uitgevoerd?" + }, + "mappings": [ + { + "if": { + "and": [ + "surveillance=public" + ] + }, + "then": { + "en": "A public area is surveilled, such as a street, a bridge, a square, a park, a train station, a public corridor or tunnel,...", + "nl": "Bewaking van de publieke ruilmte, dus een straat, een brug, een park, een plein, een stationsgebouw, een publiek toegankelijke gang of tunnel..." + } + }, + { + "if": { + "and": [ + "surveillance=outdoor" + ] + }, + "then": { + "en": "An outdoor, yet private area is surveilled (e.g. a parking lot, a fuel station, courtyard, entrance, private driveway, ...)", + "nl": "Een buitenruimte met privaat karakter (zoals een privé-oprit, een parking, tankstation, ...)" + } + }, + { + "if": { + "and": [ + "surveillance=indoor" + ] + }, + "then": { + "nl": "Een private binnenruimte wordt bewaakt, bv. een winkel, een parkeergarage, ...", + "en": "A private indoor area is surveilled, e.g. a shop, a private underground parking, ..." + } + } + ] + }, + { + "#": "Indoor camera? This isn't clear for 'public'-cameras", + "question": { + "en": "Is the public space surveilled by this camera an indoor or outdoor space?", + "nl": "Bevindt de bewaakte publieke ruimte camera zich binnen of buiten?" + }, + "condition": { + "and": [ + "surveillance:type=public" + ] + }, + "mappings": [ + { + "if": "indoor=yes", + "then": { + "en": "This camera is located indoors", + "nl": "Deze camera bevindt zich binnen" + } + }, + { + "if": "indoor=no", + "then": { + "en": "This camera is located outdoors", + "nl": "Deze camera bevindt zich buiten" + } + }, + { + "if": "indoor=", + "then": { + "en": "This camera is probably located outdoors", + "nl": "Deze camera bevindt zich waarschijnlijk buiten" + }, + "hideInAnswer": true + } + ] + }, + { + "#": "Level", + "question": { + "en": "On which level is this camera located?", + "nl": "Op welke verdieping bevindt deze camera zich?" + }, + "freeform": { + "key": "level", + "type": "nat" + }, + "condition": { + "or": [ + "indoor=yes", + "surveillance:type=ye" + ] + } + }, + { + "#": "Surveillance:zone", + "question": { + "en": "What exactly is surveilled here?", + "nl": "Wat wordt hier precies bewaakt?" + }, + "freeform": { + "key": "surveillance:zone" + }, + "render": { + "en": " Surveills a {surveillance:zone}", + "nl": "Bewaakt een {surveillance:zone}" + }, + "mappings": [ + { + "if": { + "and": [ + "surveillance:zone=parking" + ] + }, + "then": { + "en": "Surveills a parking", + "nl": "Bewaakt een parking" + } + }, + { + "if": { + "and": [ + "surveillance:zone=traffic" + ] + }, + "then": { + "en": "Surveills the traffic", + "nl": "Bewaakt het verkeer" + } + }, + { + "if": { + "and": [ + "surveillance:zone=entrance" + ] + }, + "then": { + "en": "Surveills an entrance", + "nl": "Bewaakt een ingang" + } + }, + { + "if": { + "and": [ + "surveillance:zone=corridor" + ] + }, + "then": { + "en": "Surveills a corridor", + "nl": "Bewaakt een gang" + } + }, + { + "if": { + "and": [ + "surveillance:zone=public_transport_platform" + ] + }, + "then": { + "en": "Surveills a public tranport platform", + "nl": "Bewaakt een perron of bushalte" + } + }, + { + "if": { + "and": [ + "surveillance:zone=shop" + ] + }, + "then": { + "en": "Surveills a shop", + "nl": "Bewaakt een winkel" + } + } + ] + }, + { + "#": "camera:mount", + "question": { + "en": "How is this camera placed?", + "nl": "Hoe is deze camera geplaatst?" + }, + "freeform": { + "key": "camera:mount" + }, + "mappings": [ + { + "if": "camera:mount=wall", + "then": { + "en": "This camera is placed against a wall", + "nl": "Deze camera hangt aan een muur" + } + }, + { + "if": "camera:mount=pole", + "then": { + "en": "This camera is placed one a pole", + "nl": "Deze camera staat op een paal" + } + }, + { + "if": "camera:mount=ceiling", + "then": { + "en": "This camera is placed on the ceiling", + "nl": "Deze camera hangt aan het plafond" + } + } + ] + } + ], + "hideUnderlayingFeaturesMinPercentage": 0, + "icon": { + "render": "./assets/themes/surveillance_cameras/logo.svg", + "mappings": [ + { + "if": "camera:type=dome", + "then": "./assets/themes/surveillance_cameras/dome.svg" + }, + { + "if": "_direction:leftright=right", + "then": "./assets/themes/surveillance_cameras/cam_right.svg" + }, + { + "if": "_direction:leftright=left", + "then": "./assets/themes/surveillance_cameras/cam_left.svg" + } + ] + }, + "rotation": { + "render": "calc({camera:direction}deg + 90deg)", + "mappings": [ + { + "if": "camera:type=dome", + "then": "0" + }, + { + "if": "_direction:leftright=right", + "then": "calc({camera:direction}deg - 90deg)" + } + ] + }, + "width": { + "render": "8" + }, + "iconSize": { + "mappings": [ + { + "if": "camera:type=dome", + "then": "50,50,center" + }, + { + "if": "_direction:leftright~*", + "then": "100,35,center" + } + ], + "render": "50,50,center" + }, + "color": { + "render": "#f00" + }, + "presets": [ + { + "tags": [ + "man_made=surveillance", + "surveillance:type=camera" + ], + "title": "Surveillance camera" + } + ], + "wayHandling": 2 +} \ No newline at end of file diff --git a/assets/questions/questions.json b/assets/questions/questions.json index 678df97..2595eab 100644 --- a/assets/questions/questions.json +++ b/assets/questions/questions.json @@ -4,7 +4,7 @@ }, "osmlink": { - "render": "", + "render": "", "mappings":[{ "if": "id~=-", "then": "Uploading..." diff --git a/assets/svg/crosshair.svg b/assets/svg/crosshair.svg index ff7f105..9705586 100644 --- a/assets/svg/crosshair.svg +++ b/assets/svg/crosshair.svg @@ -1,55 +1,17 @@ - - + version="1.1" + viewBox="0 0 26.458333 26.458334" + height="100" + width="100"> - - - - @@ -58,46 +20,40 @@ image/svg+xml - + + transform="translate(0,-270.54165)" + id="layer1"> + id="path815" + style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:2.64583335;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.98823529" /> + d="M 3.2841366,283.77082 H 1.0418969" + style="fill:none;stroke:#000000;stroke-width:2.09723878;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.98823529" /> + d="M 25.405696,283.77082 H 23.286471" + style="fill:none;stroke:#000000;stroke-width:2.11666679;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.98823529" /> + d="m 13.229167,295.9489 v -2.11763" + style="fill:none;stroke:#000000;stroke-width:2.11666679;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.98823529" /> + d="m 13.229167,275.05759 v -3.44507" + style="fill:none;stroke:#000000;stroke-width:2.11666668;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.98823529" /> + cx="13.229166" + id="path866" + style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:2.81138086;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> diff --git a/assets/svg/direction_gradient.svg b/assets/svg/direction_gradient.svg index aa274b4..8ba4912 100644 --- a/assets/svg/direction_gradient.svg +++ b/assets/svg/direction_gradient.svg @@ -6,11 +6,48 @@ xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" width="100" height="100" viewBox="0 0 100 100" version="1.1" - id="svg8"> + id="svg8" + sodipodi:docname="direction_gradient.svg" + inkscape:version="0.92.4 (5da689c313, 2019-01-14)"> + + + + @@ -19,7 +56,7 @@ image/svg+xml - + @@ -38,7 +75,7 @@ + d="M 50,50 21.042889,9.3993342 C 36.191421,-2.001434 60.726552,-3.6768009 78.8105,9.1490935 Z" + id="path821" + inkscape:connector-curvature="0" /> diff --git a/assets/themes/surveillance_cameras/cam.svg b/assets/themes/surveillance_cameras/cam_left.svg similarity index 58% rename from assets/themes/surveillance_cameras/cam.svg rename to assets/themes/surveillance_cameras/cam_left.svg index d4f54e8..aa8a983 100644 --- a/assets/themes/surveillance_cameras/cam.svg +++ b/assets/themes/surveillance_cameras/cam_left.svg @@ -7,25 +7,12 @@ xmlns="http://www.w3.org/2000/svg" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" - width="300" - height="300" - version="1.1" id="svg6" + version="1.1" + height="210" + width="600" sodipodi:docname="cam.svg" inkscape:version="0.92.4 (5da689c313, 2019-01-14)"> - - - - image/svg+xml - - - - - + inkscape:current-layer="svg6"> + + + + + + image/svg+xml + + + + + + + d="m 275.84577,72.939087 31.07239,-1.014243 26.20685,70.688356 -54.3295,3.69054 z m 20.0366,-37.552344 269.15255,-4.09941 c 6.98913,0.09009 15.84901,3.554569 15.7875,16.336335 0,0 -2.99159,70.195112 -3.76963,103.307682 -0.27414,11.66795 -4.43254,15.57088 -12.5089,15.57422 l -214.63586,4.14367 z" + inkscape:connector-curvature="0" /> diff --git a/assets/themes/surveillance_cameras/cam_right.svg b/assets/themes/surveillance_cameras/cam_right.svg new file mode 100644 index 0000000..78fd86f --- /dev/null +++ b/assets/themes/surveillance_cameras/cam_right.svg @@ -0,0 +1,68 @@ + + + + + + + + + + image/svg+xml + + + + + + + + diff --git a/assets/themes/surveillance_cameras/direction_360.svg b/assets/themes/surveillance_cameras/direction_360.svg new file mode 100644 index 0000000..fc60a9a --- /dev/null +++ b/assets/themes/surveillance_cameras/direction_360.svg @@ -0,0 +1,78 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + diff --git a/assets/themes/surveillance_cameras/dome.svg b/assets/themes/surveillance_cameras/dome.svg index ce0f5c0..b1e7eea 100644 --- a/assets/themes/surveillance_cameras/dome.svg +++ b/assets/themes/surveillance_cameras/dome.svg @@ -7,8 +7,8 @@ xmlns="http://www.w3.org/2000/svg" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" - width="300" - height="300" + width="100" + height="100" version="1.1" id="svg6" sodipodi:docname="dome.svg" @@ -21,6 +21,7 @@ image/svg+xml + @@ -39,29 +40,42 @@ inkscape:window-height="1001" id="namedview8" showgrid="false" - inkscape:zoom="1.5733334" - inkscape:cx="-20.982269" - inkscape:cy="188.2981" + inkscape:zoom="11.313708" + inkscape:cx="52.167677" + inkscape:cy="58.44587" inkscape:window-x="0" inkscape:window-y="0" inkscape:window-maximized="1" - inkscape:current-layer="svg6" /> + inkscape:current-layer="svg6" + showguides="true" + inkscape:guide-bbox="true"> + + + + transform="matrix(0.47524917,0,0,0.45894372,-20.936465,-23.265017)" + style="stroke-width:1.86916912"> + r="148.76208" + transform="scale(-1,1)" /> + ` let icon = layout.icon; if (icon.startsWith("", og) .replace(/.+?<\/title>/, `<title>${ogTitle}`) .replace("Loading MapComplete, hang on...", `Loading MapComplete theme ${ogTitle}...`) @@ -243,6 +243,11 @@ function createLandingPage(layout: LayoutConfig) { return output; } +const generatedDir = "./assets/generated"; +if (! existsSync(generatedDir)) { + mkdirSync(generatedDir) +} + const blacklist = ["", "test", ".", "..", "manifest", "index", "land", "preferences", "account", "openstreetmap"] const all = AllKnownLayouts.allSets; @@ -251,10 +256,7 @@ let wikiPage = "{|class=\"wikitable sortable\"\n" + "|-"; -const generatedDir = "../assets/generated"; -if (! existsSync(generatedDir)) { - mkdirSync(generatedDir) -} + for (const layoutName in all) { if (blacklist.indexOf(layoutName.toLowerCase()) >= 0) {