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 = " "
+ public static crosshair = " "
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 = " "
+ public static direction_gradient = " "
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 @@
-
-
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 @@
+
+
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 @@
+
+
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>/, `${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) {