From 28bf8cca9fcc81940d081d574db250ef89dc719a Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Tue, 23 Apr 2024 15:35:18 +0200 Subject: [PATCH] Studio: studio now supports loading self-made layers in themes --- Docs/Schemas/LayerConfigJson.schema.json | 26 +- Docs/Schemas/LayerConfigJsonJSC.ts | 26 +- Docs/Schemas/LayoutConfigJson.schema.json | 60 +- Docs/Schemas/LayoutConfigJsonJSC.ts | 60 +- .../LineRenderingConfigJson.schema.json | 2 +- Docs/Schemas/LineRenderingConfigJsonJSC.ts | 2 +- .../PointRenderingConfigJson.schema.json | 2 +- Docs/Schemas/PointRenderingConfigJsonJSC.ts | 2 +- package.json | 2 +- src/Logic/DetermineLayout.ts | 40 +- src/Logic/UIEventSource.ts | 5 + .../ThemeConfig/Conversion/Conversion.ts | 2 +- .../ThemeConfig/Conversion/PrepareTheme.ts | 2 +- .../ThemeConfig/Json/LayoutConfigJson.ts | 2 +- src/Models/ThemeConfig/TagRenderingConfig.ts | 2 +- src/UI/Studio/EditLayerState.ts | 130 +++- src/UI/Studio/EditTheme.svelte | 42 +- src/UI/Studio/SchemaBasedArray.svelte | 10 - src/UI/Studio/StudioServer.ts | 8 +- src/UI/StudioGUI.svelte | 6 +- src/assets/schemas/layerconfigmeta.json | 149 ++-- src/assets/schemas/layoutconfigmeta.json | 706 +++++++++++------- src/index.ts | 2 +- studio.html | 2 +- 24 files changed, 826 insertions(+), 464 deletions(-) diff --git a/Docs/Schemas/LayerConfigJson.schema.json b/Docs/Schemas/LayerConfigJson.schema.json index 97b11e22b..e0875b940 100644 --- a/Docs/Schemas/LayerConfigJson.schema.json +++ b/Docs/Schemas/LayerConfigJson.schema.json @@ -37,10 +37,6 @@ "osmTags": { "$ref": "#/definitions/TagConfigJson", "description": "question: Which tags must be present on the feature to show it in this layer?\nEvery source must set which tags have to be present in order to load the given layer." - }, - "maxCacheAge": { - "description": "question: How long (in seconds) is the data allowed to remain cached until it must be refreshed?\nThe maximum amount of seconds that a tile is allowed to linger in the cache\n\ntype: nat\ndefault: 30 days\ngroup: expert", - "type": "number" } }, "required": [ @@ -58,10 +54,6 @@ "description": "To load a tiled geojson layer, set the zoomlevel of the tiles\n\nquestion: If using a tiled geojson, what is the zoomlevel of the tiles?\nifunset: This is not a tiled geojson", "type": "number" }, - "isOsmCache": { - "description": "Indicates that the upstream geojson data is OSM-derived.\nUseful for e.g. merging or for scripts generating this cache.\nThis also indicates that making changes on this data is possible\n\nquestion: Is this geojson a cache of OpenStreetMap data?\nifunset: This is not an OpenStreetMap cache\niftrue: this is based on OpenStreetMap and can thus be edited\ngroup: expert", - "type": "boolean" - }, "mercatorCrs": { "description": "Some API's use a mercator-projection (EPSG:900913) instead of WGS84. Set the flag `mercatorCrs: true` in the source for this\n\nquestion: Does this geojson use EPSG:900913 instead of WGS84 as projection?\niftrue: This geojson uses EPSG:900913 instead of WGS84\nifunset: This geojson uses WGS84 just like most geojson (default)", "type": "boolean" @@ -105,6 +97,10 @@ } ] }, + "isCounted": { + "description": "question: should this layer be included in the summary counts?\n\nThe layer server can give summary counts for a tile.\nThis should however be disabled for some layers, e.g. because there are too many features (walls_and_buildings) or because the count is irrelevant.\n\nifunset: Do count\niffalse: Do not include the counts\niftrue: Do include the count", + "type": "boolean" + }, "minzoom": { "description": "The minimum needed zoomlevel required to start loading and displaying the data.\nThis can be used to only show common features (e.g. a bicycle parking) only when the map is zoomed in very much (17).\nThis prevents cluttering the map with thousands of parkings if one is looking to an entire city.\n\nDefault: 0\ngroup: Basic\ntype: nat\nquestion: At what zoom level should features of the layer be shown?\nifunset: Always load this layer, even if the entire world is in view.", "type": "number" @@ -132,8 +128,11 @@ ] }, "popupInFloatover": { - "description": "Question: Should the information for this layer be shown in the sidebar or in a splash screen?\n\nIf set, open the selectedElementView in a floatOver instead of on the right.\n\niftrue: show the infobox in the splashscreen floating over the entire UI\niffalse: show the infobox in a sidebar on the right\ngroup: advanced\ndefault: sidebar", - "type": "boolean" + "description": "Question: Should the information for this layer be shown in the sidebar or in a splash screen?\n\nIf set, open the selectedElementView in a floatOver instead of on the right.\n\niftrue: show the infobox in the splashscreen floating over the entire UI; hide the title bar\niffalse: show the infobox in a sidebar on the right\nsuggestions: return [{if: \"value=title\", then: \"Show in a floatover and show the title bar\"}]\ngroup: advanced\ndefault: sidebar", + "type": [ + "string", + "boolean" + ] }, "titleIcons": { "description": "Small icons shown next to the title.\nIf not specified, the OsmLink and wikipedia links will be used by default.\nUse an empty array to hide them.\nNote that \"defaults\" will insert all the default titleIcons (which are added automatically)\n\nUse `auto:` to automatically create an icon based on a tagRendering which has icons\n\nType: icon[]\ngroup: infobox", @@ -325,7 +324,7 @@ } }, "filter": { - "description": "All the extra questions for filtering.\nIf a string is given, mapComplete will search in 'filters.json' for the appropriate filter or will try to parse it as `layername.filterid` and us that one\n\ngroup: filters", + "description": "All the extra questions for filtering.\nIf a string is given, mapComplete will search in\n1. The tagrenderings for a match on ID and use the mappings as options\n2. search 'filters.json' for the appropriate filter or\n3. will try to parse it as `layername.filterid` and us that one.\n\n\ngroup: filters", "anyOf": [ { "type": "array", @@ -380,6 +379,7 @@ "type": "boolean" }, "units": { + "description": "Either a list with [{\"key\": \"unitname\", \"key2\": {\"quantity\": \"unitname\", \"denominations\": [\"denom\", \"denom\"]}}]", "type": "array", "items": { "anyOf": [ @@ -946,7 +946,7 @@ "type": "object", "properties": { "location": { - "description": "question: At what location should this icon be shown?\nmultianswer: true\nsuggestions: return [{if: \"value=point\",then: \"Show an icon for point (node) objects\"},{if: \"value=centroid\",then: \"Show an icon for line or polygon (way) objects at their centroid location\"}, {if: \"value=start\",then: \"Show an icon for line (way) objects at the start\"},{if: \"value=end\",then: \"Show an icon for line (way) object at the end\"},{if: \"value=projected_centerpoint\",then: \"Show an icon for line (way) object near the centroid location, but moved onto the line\"}]", + "description": "question: At what location should this icon be shown?\nmultianswer: true\nsuggestions: return [{if: \"value=point\",then: \"Show an icon for point (node) objects\"},{if: \"value=centroid\",then: \"Show an icon for line or polygon (way) objects at their centroid location\"}, {if: \"value=start\",then: \"Show an icon for line (way) objects at the start\"},{if: \"value=end\",then: \"Show an icon for line (way) object at the end\"},{if: \"value=projected_centerpoint\",then: \"Show an icon for line (way) object near the centroid location, but moved onto the line. Does not show an item on polygons\"}, ,{if: \"value=polygon_centroid\",then: \"Show an icon at a polygon centroid (but not if it is a way)\"}]", "type": "array", "items": { "type": "string" @@ -1141,7 +1141,7 @@ ] }, "dashArray": { - "description": "question: Should a dasharray be used to render the lines?\nThe dasharray defines 'pixels of line, pixels of gap, pixels of line, pixels of gap, ...'. For example, `5 6` will be 5 pixels of line followed by a 6 pixel gap.\nCannot be a dynamic property due to a mapbox limitation\nifunset: Ways are rendered with a full line", + "description": "question: Should a dasharray be used to render the lines?\nThe dasharray defines 'pixels of line, pixels of gap, pixels of line, pixels of gap, ...'. For example, `5 6` will be 5 pixels of line followed by a 6 pixel gap.\nCannot be a dynamic property due to a MapLibre limitation (see https://github.com/maplibre/maplibre-gl-js/issues/1235)\nifunset: Ways are rendered with a full line", "type": "string" }, "lineCap": { diff --git a/Docs/Schemas/LayerConfigJsonJSC.ts b/Docs/Schemas/LayerConfigJsonJSC.ts index be49c35b9..866115e4f 100644 --- a/Docs/Schemas/LayerConfigJsonJSC.ts +++ b/Docs/Schemas/LayerConfigJsonJSC.ts @@ -37,10 +37,6 @@ export default { "osmTags": { "$ref": "#/definitions/TagConfigJson", "description": "question: Which tags must be present on the feature to show it in this layer?\nEvery source must set which tags have to be present in order to load the given layer." - }, - "maxCacheAge": { - "description": "question: How long (in seconds) is the data allowed to remain cached until it must be refreshed?\nThe maximum amount of seconds that a tile is allowed to linger in the cache\n\ntype: nat\ndefault: 30 days\ngroup: expert", - "type": "number" } }, "required": [ @@ -58,10 +54,6 @@ export default { "description": "To load a tiled geojson layer, set the zoomlevel of the tiles\n\nquestion: If using a tiled geojson, what is the zoomlevel of the tiles?\nifunset: This is not a tiled geojson", "type": "number" }, - "isOsmCache": { - "description": "Indicates that the upstream geojson data is OSM-derived.\nUseful for e.g. merging or for scripts generating this cache.\nThis also indicates that making changes on this data is possible\n\nquestion: Is this geojson a cache of OpenStreetMap data?\nifunset: This is not an OpenStreetMap cache\niftrue: this is based on OpenStreetMap and can thus be edited\ngroup: expert", - "type": "boolean" - }, "mercatorCrs": { "description": "Some API's use a mercator-projection (EPSG:900913) instead of WGS84. Set the flag `mercatorCrs: true` in the source for this\n\nquestion: Does this geojson use EPSG:900913 instead of WGS84 as projection?\niftrue: This geojson uses EPSG:900913 instead of WGS84\nifunset: This geojson uses WGS84 just like most geojson (default)", "type": "boolean" @@ -105,6 +97,10 @@ export default { } ] }, + "isCounted": { + "description": "question: should this layer be included in the summary counts?\n\nThe layer server can give summary counts for a tile.\nThis should however be disabled for some layers, e.g. because there are too many features (walls_and_buildings) or because the count is irrelevant.\n\nifunset: Do count\niffalse: Do not include the counts\niftrue: Do include the count", + "type": "boolean" + }, "minzoom": { "description": "The minimum needed zoomlevel required to start loading and displaying the data.\nThis can be used to only show common features (e.g. a bicycle parking) only when the map is zoomed in very much (17).\nThis prevents cluttering the map with thousands of parkings if one is looking to an entire city.\n\nDefault: 0\ngroup: Basic\ntype: nat\nquestion: At what zoom level should features of the layer be shown?\nifunset: Always load this layer, even if the entire world is in view.", "type": "number" @@ -132,8 +128,11 @@ export default { ] }, "popupInFloatover": { - "description": "Question: Should the information for this layer be shown in the sidebar or in a splash screen?\n\nIf set, open the selectedElementView in a floatOver instead of on the right.\n\niftrue: show the infobox in the splashscreen floating over the entire UI\niffalse: show the infobox in a sidebar on the right\ngroup: advanced\ndefault: sidebar", - "type": "boolean" + "description": "Question: Should the information for this layer be shown in the sidebar or in a splash screen?\n\nIf set, open the selectedElementView in a floatOver instead of on the right.\n\niftrue: show the infobox in the splashscreen floating over the entire UI; hide the title bar\niffalse: show the infobox in a sidebar on the right\nsuggestions: return [{if: \"value=title\", then: \"Show in a floatover and show the title bar\"}]\ngroup: advanced\ndefault: sidebar", + "type": [ + "string", + "boolean" + ] }, "titleIcons": { "description": "Small icons shown next to the title.\nIf not specified, the OsmLink and wikipedia links will be used by default.\nUse an empty array to hide them.\nNote that \"defaults\" will insert all the default titleIcons (which are added automatically)\n\nUse `auto:` to automatically create an icon based on a tagRendering which has icons\n\nType: icon[]\ngroup: infobox", @@ -325,7 +324,7 @@ export default { } }, "filter": { - "description": "All the extra questions for filtering.\nIf a string is given, mapComplete will search in 'filters.json' for the appropriate filter or will try to parse it as `layername.filterid` and us that one\n\ngroup: filters", + "description": "All the extra questions for filtering.\nIf a string is given, mapComplete will search in\n1. The tagrenderings for a match on ID and use the mappings as options\n2. search 'filters.json' for the appropriate filter or\n3. will try to parse it as `layername.filterid` and us that one.\n\n\ngroup: filters", "anyOf": [ { "type": "array", @@ -380,6 +379,7 @@ export default { "type": "boolean" }, "units": { + "description": "Either a list with [{\"key\": \"unitname\", \"key2\": {\"quantity\": \"unitname\", \"denominations\": [\"denom\", \"denom\"]}}]", "type": "array", "items": { "anyOf": [ @@ -935,7 +935,7 @@ export default { "type": "object", "properties": { "location": { - "description": "question: At what location should this icon be shown?\nmultianswer: true\nsuggestions: return [{if: \"value=point\",then: \"Show an icon for point (node) objects\"},{if: \"value=centroid\",then: \"Show an icon for line or polygon (way) objects at their centroid location\"}, {if: \"value=start\",then: \"Show an icon for line (way) objects at the start\"},{if: \"value=end\",then: \"Show an icon for line (way) object at the end\"},{if: \"value=projected_centerpoint\",then: \"Show an icon for line (way) object near the centroid location, but moved onto the line\"}]", + "description": "question: At what location should this icon be shown?\nmultianswer: true\nsuggestions: return [{if: \"value=point\",then: \"Show an icon for point (node) objects\"},{if: \"value=centroid\",then: \"Show an icon for line or polygon (way) objects at their centroid location\"}, {if: \"value=start\",then: \"Show an icon for line (way) objects at the start\"},{if: \"value=end\",then: \"Show an icon for line (way) object at the end\"},{if: \"value=projected_centerpoint\",then: \"Show an icon for line (way) object near the centroid location, but moved onto the line. Does not show an item on polygons\"}, ,{if: \"value=polygon_centroid\",then: \"Show an icon at a polygon centroid (but not if it is a way)\"}]", "type": "array", "items": { "type": "string" @@ -1129,7 +1129,7 @@ export default { ] }, "dashArray": { - "description": "question: Should a dasharray be used to render the lines?\nThe dasharray defines 'pixels of line, pixels of gap, pixels of line, pixels of gap, ...'. For example, `5 6` will be 5 pixels of line followed by a 6 pixel gap.\nCannot be a dynamic property due to a mapbox limitation\nifunset: Ways are rendered with a full line", + "description": "question: Should a dasharray be used to render the lines?\nThe dasharray defines 'pixels of line, pixels of gap, pixels of line, pixels of gap, ...'. For example, `5 6` will be 5 pixels of line followed by a 6 pixel gap.\nCannot be a dynamic property due to a MapLibre limitation (see https://github.com/maplibre/maplibre-gl-js/issues/1235)\nifunset: Ways are rendered with a full line", "type": "string" }, "lineCap": { diff --git a/Docs/Schemas/LayoutConfigJson.schema.json b/Docs/Schemas/LayoutConfigJson.schema.json index c8df46021..9192e9f9b 100644 --- a/Docs/Schemas/LayoutConfigJson.schema.json +++ b/Docs/Schemas/LayoutConfigJson.schema.json @@ -104,7 +104,7 @@ "type": "boolean" }, "layers": { - "description": "question: What layers should this map show?\ntype: layer[]\ntypes: hidden | layer | hidden\ngroup: layers\nsuggestions: return Array.from(layers.keys()).map(key => ({if: \"value=\"+key, then: key+\" - \"+layers.get(key).description}))\nEvery layer contains a description of which feature to display - the overpassTags which are queried.\nInstead of running one query for every layer, the query is fused.\n\nAfterwards, every layer is given the list of features.\nEvery layer takes away the features that match with them*, and give the leftovers to the next layers.\n\nThis implies that the _order_ of the layers is important in the case of features with the same tags;\nas the later layers might never receive their feature.\n\n*layers can also remove 'leftover'-features if the leftovers overlap with a feature in the layer itself\n\nNote that builtin layers can be reused. Either put in the name of the layer to reuse, or use {builtin: \"layername\", override: ...}\n\nThe 'override'-object will be copied over the original values of the layer, which allows to change certain aspects of the layer\n\nFor example: If you would like to use layer nature reserves, but only from a specific operator (eg. Natuurpunt) you would use the following in your theme:\n\n```\n\"layer\": {\n \"builtin\": \"nature_reserve\",\n \"override\": {\"source\":\n {\"osmTags\": {\n \"+and\":[\"operator=Natuurpunt\"]\n }\n }\n }\n}\n```\n\nIt's also possible to load multiple layers at once, for example, if you would like for both drinking water and benches to start at the zoomlevel at 12, you would use the following:\n\n```\n\"layer\": {\n \"builtin\": [\"benches\", \"drinking_water\"],\n \"override\": {\"minzoom\": 12}\n}\n```", + "description": "question: What layers should this map show?\ntype: layer[]\ntypes: hidden | layer | hidden\ngroup: layers\nsuggestions: return Array.from(layers.keys()).map(key => ({if: \"value=\"+key, then: \"\"+key+\" (builtin) - \"+layers.get(key).description}))\nEvery layer contains a description of which feature to display - the overpassTags which are queried.\nInstead of running one query for every layer, the query is fused.\n\nAfterwards, every layer is given the list of features.\nEvery layer takes away the features that match with them*, and give the leftovers to the next layers.\n\nThis implies that the _order_ of the layers is important in the case of features with the same tags;\nas the later layers might never receive their feature.\n\n*layers can also remove 'leftover'-features if the leftovers overlap with a feature in the layer itself\n\nNote that builtin layers can be reused. Either put in the name of the layer to reuse, or use {builtin: \"layername\", override: ...}\n\nThe 'override'-object will be copied over the original values of the layer, which allows to change certain aspects of the layer\n\nFor example: If you would like to use layer nature reserves, but only from a specific operator (eg. Natuurpunt) you would use the following in your theme:\n\n```\n\"layer\": {\n \"builtin\": \"nature_reserve\",\n \"override\": {\"source\":\n {\"osmTags\": {\n \"+and\":[\"operator=Natuurpunt\"]\n }\n }\n }\n}\n```\n\nIt's also possible to load multiple layers at once, for example, if you would like for both drinking water and benches to start at the zoomlevel at 12, you would use the following:\n\n```\n\"layer\": {\n \"builtin\": [\"benches\", \"drinking_water\"],\n \"override\": {\"minzoom\": 12}\n}\n```", "type": "array", "items": { "anyOf": [ @@ -272,7 +272,7 @@ "type": "boolean" }, "enableTerrain": { - "description": "question: Should the map use elevation data to give a 3D-feel?\n\nThis is especially useful for hiking maps, skiing maps etc...\n\nfunset: MapComplete default: don't use terrain\niftrue: Use elevation and render 3D\niffalse: Do not use terrain\ngroup: advanced", + "description": "question: Should the map use elevation data to give a 3D-feel?\n\nThis is especially useful for hiking maps, skiing maps etc...\n\nifunset: MapComplete default: don't use terrain\niftrue: Use elevation and render 3D\niffalse: Do not use terrain\ngroup: advanced", "type": "boolean" }, "overpassUrl": { @@ -286,10 +286,6 @@ "description": "question: After how much seconds should the overpass-query stop?\nIf a query takes too long, the overpass-server will abort.\nOnce can set the amount of time before overpass gives up here.\nifunset: use the default amount of 30 seconds as timeout\ntype: pnat\ngroup: advanced", "type": "number" }, - "widenFactor": { - "description": "When a query is run, the data within bounds of the visible map is loaded.\nHowever, users tend to pan and zoom a lot. It is pretty annoying if every single pan means a reloading of the data.\nFor this, the bounds are widened in order to make a small pan still within bounds of the loaded data.\n\nIF widenfactor is 1, this feature is disabled. A recommended value is between 1 and 3", - "type": "number" - }, "overpassMaxZoom": { "description": "At low zoom levels, overpass is used to query features.\nAt high zoom level, the OSM api is used to fetch one or more BBOX aligning with a slippy tile.\nThe overpassMaxZoom controls the flipoverpoint: if the zoom is this or lower, overpass is used.", "type": "number" @@ -840,7 +836,7 @@ "type": "object", "properties": { "location": { - "description": "question: At what location should this icon be shown?\nmultianswer: true\nsuggestions: return [{if: \"value=point\",then: \"Show an icon for point (node) objects\"},{if: \"value=centroid\",then: \"Show an icon for line or polygon (way) objects at their centroid location\"}, {if: \"value=start\",then: \"Show an icon for line (way) objects at the start\"},{if: \"value=end\",then: \"Show an icon for line (way) object at the end\"},{if: \"value=projected_centerpoint\",then: \"Show an icon for line (way) object near the centroid location, but moved onto the line\"}]", + "description": "question: At what location should this icon be shown?\nmultianswer: true\nsuggestions: return [{if: \"value=point\",then: \"Show an icon for point (node) objects\"},{if: \"value=centroid\",then: \"Show an icon for line or polygon (way) objects at their centroid location\"}, {if: \"value=start\",then: \"Show an icon for line (way) objects at the start\"},{if: \"value=end\",then: \"Show an icon for line (way) object at the end\"},{if: \"value=projected_centerpoint\",then: \"Show an icon for line (way) object near the centroid location, but moved onto the line. Does not show an item on polygons\"}, ,{if: \"value=polygon_centroid\",then: \"Show an icon at a polygon centroid (but not if it is a way)\"}]", "type": "array", "items": { "type": "string" @@ -1035,7 +1031,7 @@ ] }, "dashArray": { - "description": "question: Should a dasharray be used to render the lines?\nThe dasharray defines 'pixels of line, pixels of gap, pixels of line, pixels of gap, ...'. For example, `5 6` will be 5 pixels of line followed by a 6 pixel gap.\nCannot be a dynamic property due to a mapbox limitation\nifunset: Ways are rendered with a full line", + "description": "question: Should a dasharray be used to render the lines?\nThe dasharray defines 'pixels of line, pixels of gap, pixels of line, pixels of gap, ...'. For example, `5 6` will be 5 pixels of line followed by a 6 pixel gap.\nCannot be a dynamic property due to a MapLibre limitation (see https://github.com/maplibre/maplibre-gl-js/issues/1235)\nifunset: Ways are rendered with a full line", "type": "string" }, "lineCap": { @@ -1932,10 +1928,6 @@ "osmTags": { "$ref": "#/definitions/TagConfigJson", "description": "question: Which tags must be present on the feature to show it in this layer?\nEvery source must set which tags have to be present in order to load the given layer." - }, - "maxCacheAge": { - "description": "question: How long (in seconds) is the data allowed to remain cached until it must be refreshed?\nThe maximum amount of seconds that a tile is allowed to linger in the cache\n\ntype: nat\ndefault: 30 days\ngroup: expert", - "type": "number" } }, "required": [ @@ -1953,10 +1945,6 @@ "description": "To load a tiled geojson layer, set the zoomlevel of the tiles\n\nquestion: If using a tiled geojson, what is the zoomlevel of the tiles?\nifunset: This is not a tiled geojson", "type": "number" }, - "isOsmCache": { - "description": "Indicates that the upstream geojson data is OSM-derived.\nUseful for e.g. merging or for scripts generating this cache.\nThis also indicates that making changes on this data is possible\n\nquestion: Is this geojson a cache of OpenStreetMap data?\nifunset: This is not an OpenStreetMap cache\niftrue: this is based on OpenStreetMap and can thus be edited\ngroup: expert", - "type": "boolean" - }, "mercatorCrs": { "description": "Some API's use a mercator-projection (EPSG:900913) instead of WGS84. Set the flag `mercatorCrs: true` in the source for this\n\nquestion: Does this geojson use EPSG:900913 instead of WGS84 as projection?\niftrue: This geojson uses EPSG:900913 instead of WGS84\nifunset: This geojson uses WGS84 just like most geojson (default)", "type": "boolean" @@ -2000,6 +1988,10 @@ } ] }, + "isCounted": { + "description": "question: should this layer be included in the summary counts?\n\nThe layer server can give summary counts for a tile.\nThis should however be disabled for some layers, e.g. because there are too many features (walls_and_buildings) or because the count is irrelevant.\n\nifunset: Do count\niffalse: Do not include the counts\niftrue: Do include the count", + "type": "boolean" + }, "minzoom": { "description": "The minimum needed zoomlevel required to start loading and displaying the data.\nThis can be used to only show common features (e.g. a bicycle parking) only when the map is zoomed in very much (17).\nThis prevents cluttering the map with thousands of parkings if one is looking to an entire city.\n\nDefault: 0\ngroup: Basic\ntype: nat\nquestion: At what zoom level should features of the layer be shown?\nifunset: Always load this layer, even if the entire world is in view.", "type": "number" @@ -2027,8 +2019,11 @@ ] }, "popupInFloatover": { - "description": "Question: Should the information for this layer be shown in the sidebar or in a splash screen?\n\nIf set, open the selectedElementView in a floatOver instead of on the right.\n\niftrue: show the infobox in the splashscreen floating over the entire UI\niffalse: show the infobox in a sidebar on the right\ngroup: advanced\ndefault: sidebar", - "type": "boolean" + "description": "Question: Should the information for this layer be shown in the sidebar or in a splash screen?\n\nIf set, open the selectedElementView in a floatOver instead of on the right.\n\niftrue: show the infobox in the splashscreen floating over the entire UI; hide the title bar\niffalse: show the infobox in a sidebar on the right\nsuggestions: return [{if: \"value=title\", then: \"Show in a floatover and show the title bar\"}]\ngroup: advanced\ndefault: sidebar", + "type": [ + "string", + "boolean" + ] }, "titleIcons": { "description": "Small icons shown next to the title.\nIf not specified, the OsmLink and wikipedia links will be used by default.\nUse an empty array to hide them.\nNote that \"defaults\" will insert all the default titleIcons (which are added automatically)\n\nUse `auto:` to automatically create an icon based on a tagRendering which has icons\n\nType: icon[]\ngroup: infobox", @@ -2220,7 +2215,7 @@ } }, "filter": { - "description": "All the extra questions for filtering.\nIf a string is given, mapComplete will search in 'filters.json' for the appropriate filter or will try to parse it as `layername.filterid` and us that one\n\ngroup: filters", + "description": "All the extra questions for filtering.\nIf a string is given, mapComplete will search in\n1. The tagrenderings for a match on ID and use the mappings as options\n2. search 'filters.json' for the appropriate filter or\n3. will try to parse it as `layername.filterid` and us that one.\n\n\ngroup: filters", "anyOf": [ { "type": "array", @@ -2275,6 +2270,7 @@ "type": "boolean" }, "units": { + "description": "Either a list with [{\"key\": \"unitname\", \"key2\": {\"quantity\": \"unitname\", \"denominations\": [\"denom\", \"denom\"]}}]", "type": "array", "items": { "anyOf": [ @@ -2351,10 +2347,6 @@ "osmTags": { "$ref": "#/definitions/TagConfigJson", "description": "question: Which tags must be present on the feature to show it in this layer?\nEvery source must set which tags have to be present in order to load the given layer." - }, - "maxCacheAge": { - "description": "question: How long (in seconds) is the data allowed to remain cached until it must be refreshed?\nThe maximum amount of seconds that a tile is allowed to linger in the cache\n\ntype: nat\ndefault: 30 days\ngroup: expert", - "type": "number" } }, "required": [ @@ -2372,10 +2364,6 @@ "description": "To load a tiled geojson layer, set the zoomlevel of the tiles\n\nquestion: If using a tiled geojson, what is the zoomlevel of the tiles?\nifunset: This is not a tiled geojson", "type": "number" }, - "isOsmCache": { - "description": "Indicates that the upstream geojson data is OSM-derived.\nUseful for e.g. merging or for scripts generating this cache.\nThis also indicates that making changes on this data is possible\n\nquestion: Is this geojson a cache of OpenStreetMap data?\nifunset: This is not an OpenStreetMap cache\niftrue: this is based on OpenStreetMap and can thus be edited\ngroup: expert", - "type": "boolean" - }, "mercatorCrs": { "description": "Some API's use a mercator-projection (EPSG:900913) instead of WGS84. Set the flag `mercatorCrs: true` in the source for this\n\nquestion: Does this geojson use EPSG:900913 instead of WGS84 as projection?\niftrue: This geojson uses EPSG:900913 instead of WGS84\nifunset: This geojson uses WGS84 just like most geojson (default)", "type": "boolean" @@ -2419,6 +2407,10 @@ } ] }, + "isCounted": { + "description": "question: should this layer be included in the summary counts?\n\nThe layer server can give summary counts for a tile.\nThis should however be disabled for some layers, e.g. because there are too many features (walls_and_buildings) or because the count is irrelevant.\n\nifunset: Do count\niffalse: Do not include the counts\niftrue: Do include the count", + "type": "boolean" + }, "minzoom": { "description": "The minimum needed zoomlevel required to start loading and displaying the data.\nThis can be used to only show common features (e.g. a bicycle parking) only when the map is zoomed in very much (17).\nThis prevents cluttering the map with thousands of parkings if one is looking to an entire city.\n\nDefault: 0\ngroup: Basic\ntype: nat\nquestion: At what zoom level should features of the layer be shown?\nifunset: Always load this layer, even if the entire world is in view.", "type": "number" @@ -2446,8 +2438,11 @@ ] }, "popupInFloatover": { - "description": "Question: Should the information for this layer be shown in the sidebar or in a splash screen?\n\nIf set, open the selectedElementView in a floatOver instead of on the right.\n\niftrue: show the infobox in the splashscreen floating over the entire UI\niffalse: show the infobox in a sidebar on the right\ngroup: advanced\ndefault: sidebar", - "type": "boolean" + "description": "Question: Should the information for this layer be shown in the sidebar or in a splash screen?\n\nIf set, open the selectedElementView in a floatOver instead of on the right.\n\niftrue: show the infobox in the splashscreen floating over the entire UI; hide the title bar\niffalse: show the infobox in a sidebar on the right\nsuggestions: return [{if: \"value=title\", then: \"Show in a floatover and show the title bar\"}]\ngroup: advanced\ndefault: sidebar", + "type": [ + "string", + "boolean" + ] }, "titleIcons": { "description": "Small icons shown next to the title.\nIf not specified, the OsmLink and wikipedia links will be used by default.\nUse an empty array to hide them.\nNote that \"defaults\" will insert all the default titleIcons (which are added automatically)\n\nUse `auto:` to automatically create an icon based on a tagRendering which has icons\n\nType: icon[]\ngroup: infobox", @@ -2639,7 +2634,7 @@ } }, "filter": { - "description": "All the extra questions for filtering.\nIf a string is given, mapComplete will search in 'filters.json' for the appropriate filter or will try to parse it as `layername.filterid` and us that one\n\ngroup: filters", + "description": "All the extra questions for filtering.\nIf a string is given, mapComplete will search in\n1. The tagrenderings for a match on ID and use the mappings as options\n2. search 'filters.json' for the appropriate filter or\n3. will try to parse it as `layername.filterid` and us that one.\n\n\ngroup: filters", "anyOf": [ { "type": "array", @@ -2694,6 +2689,7 @@ "type": "boolean" }, "units": { + "description": "Either a list with [{\"key\": \"unitname\", \"key2\": {\"quantity\": \"unitname\", \"denominations\": [\"denom\", \"denom\"]}}]", "type": "array", "items": { "anyOf": [ @@ -2732,6 +2728,7 @@ "additionalProperties": false }, "RasterLayerProperties": { + "description": "This class has grown beyond the point of only containing Raster Layers", "type": "object", "properties": { "name": { @@ -2760,6 +2757,9 @@ "type": { "type": "string" }, + "style": { + "type": "string" + }, "attribution": { "type": "object", "properties": { diff --git a/Docs/Schemas/LayoutConfigJsonJSC.ts b/Docs/Schemas/LayoutConfigJsonJSC.ts index 53eee8aef..eeb2663a6 100644 --- a/Docs/Schemas/LayoutConfigJsonJSC.ts +++ b/Docs/Schemas/LayoutConfigJsonJSC.ts @@ -104,7 +104,7 @@ export default { "type": "boolean" }, "layers": { - "description": "question: What layers should this map show?\ntype: layer[]\ntypes: hidden | layer | hidden\ngroup: layers\nsuggestions: return Array.from(layers.keys()).map(key => ({if: \"value=\"+key, then: key+\" - \"+layers.get(key).description}))\nEvery layer contains a description of which feature to display - the overpassTags which are queried.\nInstead of running one query for every layer, the query is fused.\n\nAfterwards, every layer is given the list of features.\nEvery layer takes away the features that match with them*, and give the leftovers to the next layers.\n\nThis implies that the _order_ of the layers is important in the case of features with the same tags;\nas the later layers might never receive their feature.\n\n*layers can also remove 'leftover'-features if the leftovers overlap with a feature in the layer itself\n\nNote that builtin layers can be reused. Either put in the name of the layer to reuse, or use {builtin: \"layername\", override: ...}\n\nThe 'override'-object will be copied over the original values of the layer, which allows to change certain aspects of the layer\n\nFor example: If you would like to use layer nature reserves, but only from a specific operator (eg. Natuurpunt) you would use the following in your theme:\n\n```\n\"layer\": {\n \"builtin\": \"nature_reserve\",\n \"override\": {\"source\":\n {\"osmTags\": {\n \"+and\":[\"operator=Natuurpunt\"]\n }\n }\n }\n}\n```\n\nIt's also possible to load multiple layers at once, for example, if you would like for both drinking water and benches to start at the zoomlevel at 12, you would use the following:\n\n```\n\"layer\": {\n \"builtin\": [\"benches\", \"drinking_water\"],\n \"override\": {\"minzoom\": 12}\n}\n```", + "description": "question: What layers should this map show?\ntype: layer[]\ntypes: hidden | layer | hidden\ngroup: layers\nsuggestions: return Array.from(layers.keys()).map(key => ({if: \"value=\"+key, then: \"\"+key+\" (builtin) - \"+layers.get(key).description}))\nEvery layer contains a description of which feature to display - the overpassTags which are queried.\nInstead of running one query for every layer, the query is fused.\n\nAfterwards, every layer is given the list of features.\nEvery layer takes away the features that match with them*, and give the leftovers to the next layers.\n\nThis implies that the _order_ of the layers is important in the case of features with the same tags;\nas the later layers might never receive their feature.\n\n*layers can also remove 'leftover'-features if the leftovers overlap with a feature in the layer itself\n\nNote that builtin layers can be reused. Either put in the name of the layer to reuse, or use {builtin: \"layername\", override: ...}\n\nThe 'override'-object will be copied over the original values of the layer, which allows to change certain aspects of the layer\n\nFor example: If you would like to use layer nature reserves, but only from a specific operator (eg. Natuurpunt) you would use the following in your theme:\n\n```\n\"layer\": {\n \"builtin\": \"nature_reserve\",\n \"override\": {\"source\":\n {\"osmTags\": {\n \"+and\":[\"operator=Natuurpunt\"]\n }\n }\n }\n}\n```\n\nIt's also possible to load multiple layers at once, for example, if you would like for both drinking water and benches to start at the zoomlevel at 12, you would use the following:\n\n```\n\"layer\": {\n \"builtin\": [\"benches\", \"drinking_water\"],\n \"override\": {\"minzoom\": 12}\n}\n```", "type": "array", "items": { "anyOf": [ @@ -272,7 +272,7 @@ export default { "type": "boolean" }, "enableTerrain": { - "description": "question: Should the map use elevation data to give a 3D-feel?\n\nThis is especially useful for hiking maps, skiing maps etc...\n\nfunset: MapComplete default: don't use terrain\niftrue: Use elevation and render 3D\niffalse: Do not use terrain\ngroup: advanced", + "description": "question: Should the map use elevation data to give a 3D-feel?\n\nThis is especially useful for hiking maps, skiing maps etc...\n\nifunset: MapComplete default: don't use terrain\niftrue: Use elevation and render 3D\niffalse: Do not use terrain\ngroup: advanced", "type": "boolean" }, "overpassUrl": { @@ -286,10 +286,6 @@ export default { "description": "question: After how much seconds should the overpass-query stop?\nIf a query takes too long, the overpass-server will abort.\nOnce can set the amount of time before overpass gives up here.\nifunset: use the default amount of 30 seconds as timeout\ntype: pnat\ngroup: advanced", "type": "number" }, - "widenFactor": { - "description": "When a query is run, the data within bounds of the visible map is loaded.\nHowever, users tend to pan and zoom a lot. It is pretty annoying if every single pan means a reloading of the data.\nFor this, the bounds are widened in order to make a small pan still within bounds of the loaded data.\n\nIF widenfactor is 1, this feature is disabled. A recommended value is between 1 and 3", - "type": "number" - }, "overpassMaxZoom": { "description": "At low zoom levels, overpass is used to query features.\nAt high zoom level, the OSM api is used to fetch one or more BBOX aligning with a slippy tile.\nThe overpassMaxZoom controls the flipoverpoint: if the zoom is this or lower, overpass is used.", "type": "number" @@ -829,7 +825,7 @@ export default { "type": "object", "properties": { "location": { - "description": "question: At what location should this icon be shown?\nmultianswer: true\nsuggestions: return [{if: \"value=point\",then: \"Show an icon for point (node) objects\"},{if: \"value=centroid\",then: \"Show an icon for line or polygon (way) objects at their centroid location\"}, {if: \"value=start\",then: \"Show an icon for line (way) objects at the start\"},{if: \"value=end\",then: \"Show an icon for line (way) object at the end\"},{if: \"value=projected_centerpoint\",then: \"Show an icon for line (way) object near the centroid location, but moved onto the line\"}]", + "description": "question: At what location should this icon be shown?\nmultianswer: true\nsuggestions: return [{if: \"value=point\",then: \"Show an icon for point (node) objects\"},{if: \"value=centroid\",then: \"Show an icon for line or polygon (way) objects at their centroid location\"}, {if: \"value=start\",then: \"Show an icon for line (way) objects at the start\"},{if: \"value=end\",then: \"Show an icon for line (way) object at the end\"},{if: \"value=projected_centerpoint\",then: \"Show an icon for line (way) object near the centroid location, but moved onto the line. Does not show an item on polygons\"}, ,{if: \"value=polygon_centroid\",then: \"Show an icon at a polygon centroid (but not if it is a way)\"}]", "type": "array", "items": { "type": "string" @@ -1023,7 +1019,7 @@ export default { ] }, "dashArray": { - "description": "question: Should a dasharray be used to render the lines?\nThe dasharray defines 'pixels of line, pixels of gap, pixels of line, pixels of gap, ...'. For example, `5 6` will be 5 pixels of line followed by a 6 pixel gap.\nCannot be a dynamic property due to a mapbox limitation\nifunset: Ways are rendered with a full line", + "description": "question: Should a dasharray be used to render the lines?\nThe dasharray defines 'pixels of line, pixels of gap, pixels of line, pixels of gap, ...'. For example, `5 6` will be 5 pixels of line followed by a 6 pixel gap.\nCannot be a dynamic property due to a MapLibre limitation (see https://github.com/maplibre/maplibre-gl-js/issues/1235)\nifunset: Ways are rendered with a full line", "type": "string" }, "lineCap": { @@ -1910,10 +1906,6 @@ export default { "osmTags": { "$ref": "#/definitions/TagConfigJson", "description": "question: Which tags must be present on the feature to show it in this layer?\nEvery source must set which tags have to be present in order to load the given layer." - }, - "maxCacheAge": { - "description": "question: How long (in seconds) is the data allowed to remain cached until it must be refreshed?\nThe maximum amount of seconds that a tile is allowed to linger in the cache\n\ntype: nat\ndefault: 30 days\ngroup: expert", - "type": "number" } }, "required": [ @@ -1931,10 +1923,6 @@ export default { "description": "To load a tiled geojson layer, set the zoomlevel of the tiles\n\nquestion: If using a tiled geojson, what is the zoomlevel of the tiles?\nifunset: This is not a tiled geojson", "type": "number" }, - "isOsmCache": { - "description": "Indicates that the upstream geojson data is OSM-derived.\nUseful for e.g. merging or for scripts generating this cache.\nThis also indicates that making changes on this data is possible\n\nquestion: Is this geojson a cache of OpenStreetMap data?\nifunset: This is not an OpenStreetMap cache\niftrue: this is based on OpenStreetMap and can thus be edited\ngroup: expert", - "type": "boolean" - }, "mercatorCrs": { "description": "Some API's use a mercator-projection (EPSG:900913) instead of WGS84. Set the flag `mercatorCrs: true` in the source for this\n\nquestion: Does this geojson use EPSG:900913 instead of WGS84 as projection?\niftrue: This geojson uses EPSG:900913 instead of WGS84\nifunset: This geojson uses WGS84 just like most geojson (default)", "type": "boolean" @@ -1978,6 +1966,10 @@ export default { } ] }, + "isCounted": { + "description": "question: should this layer be included in the summary counts?\n\nThe layer server can give summary counts for a tile.\nThis should however be disabled for some layers, e.g. because there are too many features (walls_and_buildings) or because the count is irrelevant.\n\nifunset: Do count\niffalse: Do not include the counts\niftrue: Do include the count", + "type": "boolean" + }, "minzoom": { "description": "The minimum needed zoomlevel required to start loading and displaying the data.\nThis can be used to only show common features (e.g. a bicycle parking) only when the map is zoomed in very much (17).\nThis prevents cluttering the map with thousands of parkings if one is looking to an entire city.\n\nDefault: 0\ngroup: Basic\ntype: nat\nquestion: At what zoom level should features of the layer be shown?\nifunset: Always load this layer, even if the entire world is in view.", "type": "number" @@ -2005,8 +1997,11 @@ export default { ] }, "popupInFloatover": { - "description": "Question: Should the information for this layer be shown in the sidebar or in a splash screen?\n\nIf set, open the selectedElementView in a floatOver instead of on the right.\n\niftrue: show the infobox in the splashscreen floating over the entire UI\niffalse: show the infobox in a sidebar on the right\ngroup: advanced\ndefault: sidebar", - "type": "boolean" + "description": "Question: Should the information for this layer be shown in the sidebar or in a splash screen?\n\nIf set, open the selectedElementView in a floatOver instead of on the right.\n\niftrue: show the infobox in the splashscreen floating over the entire UI; hide the title bar\niffalse: show the infobox in a sidebar on the right\nsuggestions: return [{if: \"value=title\", then: \"Show in a floatover and show the title bar\"}]\ngroup: advanced\ndefault: sidebar", + "type": [ + "string", + "boolean" + ] }, "titleIcons": { "description": "Small icons shown next to the title.\nIf not specified, the OsmLink and wikipedia links will be used by default.\nUse an empty array to hide them.\nNote that \"defaults\" will insert all the default titleIcons (which are added automatically)\n\nUse `auto:` to automatically create an icon based on a tagRendering which has icons\n\nType: icon[]\ngroup: infobox", @@ -2198,7 +2193,7 @@ export default { } }, "filter": { - "description": "All the extra questions for filtering.\nIf a string is given, mapComplete will search in 'filters.json' for the appropriate filter or will try to parse it as `layername.filterid` and us that one\n\ngroup: filters", + "description": "All the extra questions for filtering.\nIf a string is given, mapComplete will search in\n1. The tagrenderings for a match on ID and use the mappings as options\n2. search 'filters.json' for the appropriate filter or\n3. will try to parse it as `layername.filterid` and us that one.\n\n\ngroup: filters", "anyOf": [ { "type": "array", @@ -2253,6 +2248,7 @@ export default { "type": "boolean" }, "units": { + "description": "Either a list with [{\"key\": \"unitname\", \"key2\": {\"quantity\": \"unitname\", \"denominations\": [\"denom\", \"denom\"]}}]", "type": "array", "items": { "anyOf": [ @@ -2328,10 +2324,6 @@ export default { "osmTags": { "$ref": "#/definitions/TagConfigJson", "description": "question: Which tags must be present on the feature to show it in this layer?\nEvery source must set which tags have to be present in order to load the given layer." - }, - "maxCacheAge": { - "description": "question: How long (in seconds) is the data allowed to remain cached until it must be refreshed?\nThe maximum amount of seconds that a tile is allowed to linger in the cache\n\ntype: nat\ndefault: 30 days\ngroup: expert", - "type": "number" } }, "required": [ @@ -2349,10 +2341,6 @@ export default { "description": "To load a tiled geojson layer, set the zoomlevel of the tiles\n\nquestion: If using a tiled geojson, what is the zoomlevel of the tiles?\nifunset: This is not a tiled geojson", "type": "number" }, - "isOsmCache": { - "description": "Indicates that the upstream geojson data is OSM-derived.\nUseful for e.g. merging or for scripts generating this cache.\nThis also indicates that making changes on this data is possible\n\nquestion: Is this geojson a cache of OpenStreetMap data?\nifunset: This is not an OpenStreetMap cache\niftrue: this is based on OpenStreetMap and can thus be edited\ngroup: expert", - "type": "boolean" - }, "mercatorCrs": { "description": "Some API's use a mercator-projection (EPSG:900913) instead of WGS84. Set the flag `mercatorCrs: true` in the source for this\n\nquestion: Does this geojson use EPSG:900913 instead of WGS84 as projection?\niftrue: This geojson uses EPSG:900913 instead of WGS84\nifunset: This geojson uses WGS84 just like most geojson (default)", "type": "boolean" @@ -2396,6 +2384,10 @@ export default { } ] }, + "isCounted": { + "description": "question: should this layer be included in the summary counts?\n\nThe layer server can give summary counts for a tile.\nThis should however be disabled for some layers, e.g. because there are too many features (walls_and_buildings) or because the count is irrelevant.\n\nifunset: Do count\niffalse: Do not include the counts\niftrue: Do include the count", + "type": "boolean" + }, "minzoom": { "description": "The minimum needed zoomlevel required to start loading and displaying the data.\nThis can be used to only show common features (e.g. a bicycle parking) only when the map is zoomed in very much (17).\nThis prevents cluttering the map with thousands of parkings if one is looking to an entire city.\n\nDefault: 0\ngroup: Basic\ntype: nat\nquestion: At what zoom level should features of the layer be shown?\nifunset: Always load this layer, even if the entire world is in view.", "type": "number" @@ -2423,8 +2415,11 @@ export default { ] }, "popupInFloatover": { - "description": "Question: Should the information for this layer be shown in the sidebar or in a splash screen?\n\nIf set, open the selectedElementView in a floatOver instead of on the right.\n\niftrue: show the infobox in the splashscreen floating over the entire UI\niffalse: show the infobox in a sidebar on the right\ngroup: advanced\ndefault: sidebar", - "type": "boolean" + "description": "Question: Should the information for this layer be shown in the sidebar or in a splash screen?\n\nIf set, open the selectedElementView in a floatOver instead of on the right.\n\niftrue: show the infobox in the splashscreen floating over the entire UI; hide the title bar\niffalse: show the infobox in a sidebar on the right\nsuggestions: return [{if: \"value=title\", then: \"Show in a floatover and show the title bar\"}]\ngroup: advanced\ndefault: sidebar", + "type": [ + "string", + "boolean" + ] }, "titleIcons": { "description": "Small icons shown next to the title.\nIf not specified, the OsmLink and wikipedia links will be used by default.\nUse an empty array to hide them.\nNote that \"defaults\" will insert all the default titleIcons (which are added automatically)\n\nUse `auto:` to automatically create an icon based on a tagRendering which has icons\n\nType: icon[]\ngroup: infobox", @@ -2616,7 +2611,7 @@ export default { } }, "filter": { - "description": "All the extra questions for filtering.\nIf a string is given, mapComplete will search in 'filters.json' for the appropriate filter or will try to parse it as `layername.filterid` and us that one\n\ngroup: filters", + "description": "All the extra questions for filtering.\nIf a string is given, mapComplete will search in\n1. The tagrenderings for a match on ID and use the mappings as options\n2. search 'filters.json' for the appropriate filter or\n3. will try to parse it as `layername.filterid` and us that one.\n\n\ngroup: filters", "anyOf": [ { "type": "array", @@ -2671,6 +2666,7 @@ export default { "type": "boolean" }, "units": { + "description": "Either a list with [{\"key\": \"unitname\", \"key2\": {\"quantity\": \"unitname\", \"denominations\": [\"denom\", \"denom\"]}}]", "type": "array", "items": { "anyOf": [ @@ -2707,6 +2703,7 @@ export default { "type": "object" }, "RasterLayerProperties": { + "description": "This class has grown beyond the point of only containing Raster Layers", "type": "object", "properties": { "name": { @@ -2735,6 +2732,9 @@ export default { "type": { "type": "string" }, + "style": { + "type": "string" + }, "attribution": { "type": "object", "properties": { diff --git a/Docs/Schemas/LineRenderingConfigJson.schema.json b/Docs/Schemas/LineRenderingConfigJson.schema.json index ba0c746f6..3232cebc8 100644 --- a/Docs/Schemas/LineRenderingConfigJson.schema.json +++ b/Docs/Schemas/LineRenderingConfigJson.schema.json @@ -28,7 +28,7 @@ ] }, "dashArray": { - "description": "question: Should a dasharray be used to render the lines?\nThe dasharray defines 'pixels of line, pixels of gap, pixels of line, pixels of gap, ...'. For example, `5 6` will be 5 pixels of line followed by a 6 pixel gap.\nCannot be a dynamic property due to a mapbox limitation\nifunset: Ways are rendered with a full line", + "description": "question: Should a dasharray be used to render the lines?\nThe dasharray defines 'pixels of line, pixels of gap, pixels of line, pixels of gap, ...'. For example, `5 6` will be 5 pixels of line followed by a 6 pixel gap.\nCannot be a dynamic property due to a MapLibre limitation (see https://github.com/maplibre/maplibre-gl-js/issues/1235)\nifunset: Ways are rendered with a full line", "type": "string" }, "lineCap": { diff --git a/Docs/Schemas/LineRenderingConfigJsonJSC.ts b/Docs/Schemas/LineRenderingConfigJsonJSC.ts index cb3f97cb8..a2165c728 100644 --- a/Docs/Schemas/LineRenderingConfigJsonJSC.ts +++ b/Docs/Schemas/LineRenderingConfigJsonJSC.ts @@ -28,7 +28,7 @@ export default { ] }, "dashArray": { - "description": "question: Should a dasharray be used to render the lines?\nThe dasharray defines 'pixels of line, pixels of gap, pixels of line, pixels of gap, ...'. For example, `5 6` will be 5 pixels of line followed by a 6 pixel gap.\nCannot be a dynamic property due to a mapbox limitation\nifunset: Ways are rendered with a full line", + "description": "question: Should a dasharray be used to render the lines?\nThe dasharray defines 'pixels of line, pixels of gap, pixels of line, pixels of gap, ...'. For example, `5 6` will be 5 pixels of line followed by a 6 pixel gap.\nCannot be a dynamic property due to a MapLibre limitation (see https://github.com/maplibre/maplibre-gl-js/issues/1235)\nifunset: Ways are rendered with a full line", "type": "string" }, "lineCap": { diff --git a/Docs/Schemas/PointRenderingConfigJson.schema.json b/Docs/Schemas/PointRenderingConfigJson.schema.json index 133d3c69d..1f5c37cfb 100644 --- a/Docs/Schemas/PointRenderingConfigJson.schema.json +++ b/Docs/Schemas/PointRenderingConfigJson.schema.json @@ -3,7 +3,7 @@ "type": "object", "properties": { "location": { - "description": "question: At what location should this icon be shown?\nmultianswer: true\nsuggestions: return [{if: \"value=point\",then: \"Show an icon for point (node) objects\"},{if: \"value=centroid\",then: \"Show an icon for line or polygon (way) objects at their centroid location\"}, {if: \"value=start\",then: \"Show an icon for line (way) objects at the start\"},{if: \"value=end\",then: \"Show an icon for line (way) object at the end\"},{if: \"value=projected_centerpoint\",then: \"Show an icon for line (way) object near the centroid location, but moved onto the line\"}]", + "description": "question: At what location should this icon be shown?\nmultianswer: true\nsuggestions: return [{if: \"value=point\",then: \"Show an icon for point (node) objects\"},{if: \"value=centroid\",then: \"Show an icon for line or polygon (way) objects at their centroid location\"}, {if: \"value=start\",then: \"Show an icon for line (way) objects at the start\"},{if: \"value=end\",then: \"Show an icon for line (way) object at the end\"},{if: \"value=projected_centerpoint\",then: \"Show an icon for line (way) object near the centroid location, but moved onto the line. Does not show an item on polygons\"}, ,{if: \"value=polygon_centroid\",then: \"Show an icon at a polygon centroid (but not if it is a way)\"}]", "type": "array", "items": { "type": "string" diff --git a/Docs/Schemas/PointRenderingConfigJsonJSC.ts b/Docs/Schemas/PointRenderingConfigJsonJSC.ts index 8fa653618..8d09bdea2 100644 --- a/Docs/Schemas/PointRenderingConfigJsonJSC.ts +++ b/Docs/Schemas/PointRenderingConfigJsonJSC.ts @@ -3,7 +3,7 @@ export default { "type": "object", "properties": { "location": { - "description": "question: At what location should this icon be shown?\nmultianswer: true\nsuggestions: return [{if: \"value=point\",then: \"Show an icon for point (node) objects\"},{if: \"value=centroid\",then: \"Show an icon for line or polygon (way) objects at their centroid location\"}, {if: \"value=start\",then: \"Show an icon for line (way) objects at the start\"},{if: \"value=end\",then: \"Show an icon for line (way) object at the end\"},{if: \"value=projected_centerpoint\",then: \"Show an icon for line (way) object near the centroid location, but moved onto the line\"}]", + "description": "question: At what location should this icon be shown?\nmultianswer: true\nsuggestions: return [{if: \"value=point\",then: \"Show an icon for point (node) objects\"},{if: \"value=centroid\",then: \"Show an icon for line or polygon (way) objects at their centroid location\"}, {if: \"value=start\",then: \"Show an icon for line (way) objects at the start\"},{if: \"value=end\",then: \"Show an icon for line (way) object at the end\"},{if: \"value=projected_centerpoint\",then: \"Show an icon for line (way) object near the centroid location, but moved onto the line. Does not show an item on polygons\"}, ,{if: \"value=polygon_centroid\",then: \"Show an icon at a polygon centroid (but not if it is a way)\"}]", "type": "array", "items": { "type": "string" diff --git a/package.json b/package.json index 984a859b1..1e7ac2d9a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mapcomplete", - "version": "0.41.7", + "version": "0.42.0", "repository": "https://github.com/pietervdvn/MapComplete", "description": "A small website to edit OSM easily", "bugs": "https://github.com/pietervdvn/MapComplete/issues", diff --git a/src/Logic/DetermineLayout.ts b/src/Logic/DetermineLayout.ts index a212e380c..a8cf4dff6 100644 --- a/src/Logic/DetermineLayout.ts +++ b/src/Logic/DetermineLayout.ts @@ -17,12 +17,13 @@ import questions from "../assets/generated/layers/questions.json" import { DoesImageExist, PrevalidateTheme, - ValidateThemeAndLayers, + ValidateThemeAndLayers } from "../Models/ThemeConfig/Conversion/Validation" import { DesugaringContext } from "../Models/ThemeConfig/Conversion/Conversion" import { TagRenderingConfigJson } from "../Models/ThemeConfig/Json/TagRenderingConfigJson" import Hash from "./Web/Hash" import { QuestionableTagRenderingConfigJson } from "../Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson" +import { LayoutConfigJson } from "../Models/ThemeConfig/Json/LayoutConfigJson" export default class DetermineLayout { private static readonly _knownImages = new Set(Array.from(licenses).map((l) => l.path)) @@ -31,6 +32,7 @@ export default class DetermineLayout { "false", "If not 'false', a custom (non-official) theme is loaded. This custom layout can be done in multiple ways: \n\n- The hash of the URL contains a base64-encoded .json-file containing the theme definition\n- The hash of the URL contains a lz-compressed .json-file, as generated by the custom theme generator\n- The parameter itself is an URL, in which case that URL will be downloaded. It should point to a .json of a theme" ) + public static getCustomDefinition(): string { const layoutFromBase64 = decodeURIComponent(DetermineLayout.loadCustomThemeParam.data) @@ -53,6 +55,25 @@ export default class DetermineLayout { return undefined } + private static async expandRemoteLayers(layoutConfig: LayoutConfigJson): Promise { + for (let i = 0; i < layoutConfig.layers.length; i++) { + const l = layoutConfig.layers[i] + if (typeof l !== "string") { + continue + } + try { + new URL(l) + console.log("Downloading remote layer " + l) + const layerConfig = await Utils.downloadJson(l) + layoutConfig.layers[i] = layerConfig + } catch (_) { + continue + } + + } + return layoutConfig + } + /** * Gets the correct layout for this website */ @@ -65,7 +86,7 @@ export default class DetermineLayout { if (layoutFromBase64 !== "false") { // We have to load something from the hash (or from disk) - return DetermineLayout.LoadLayoutFromHash(DetermineLayout.loadCustomThemeParam) + return await DetermineLayout.LoadLayoutFromHash(DetermineLayout.loadCustomThemeParam) } let layoutId: string = undefined @@ -90,7 +111,7 @@ export default class DetermineLayout { return layout } - public static LoadLayoutFromHash(userLayoutParam: UIEventSource): LayoutConfig | null { + public static async LoadLayoutFromHash(userLayoutParam: UIEventSource): Promise { let hash = location.hash.substr(1) let json: any @@ -118,6 +139,8 @@ export default class DetermineLayout { json = JSON.parse(Utils.UnMinify(LZString.decompressFromBase64(hash))) } + json = await this.expandRemoteLayers(json) + const layoutToUse = DetermineLayout.prepCustomTheme(json) userLayoutParam.setData(layoutToUse.id) return layoutToUse @@ -148,11 +171,11 @@ export default class DetermineLayout { id: json.id, description: json.description, descriptionTail: { - en: "
Layer only mode.
The loaded custom theme actually isn't a custom theme, but only contains a layer.", + en: "
Layer only mode.
The loaded custom theme actually isn't a custom theme, but only contains a layer." }, icon, title: json.name, - layers: [json], + layers: [json] } } @@ -164,7 +187,7 @@ export default class DetermineLayout { const convertState: DesugaringContext = { tagRenderings: DetermineLayout.getSharedTagRenderings(), sharedLayers: knownLayersDict, - publicLayers: new Set(), + publicLayers: new Set() } json = new FixLegacyTheme().convertStrict(json) const raw = json @@ -188,7 +211,7 @@ export default class DetermineLayout { } return new LayoutConfig(json, false, { definitionRaw: JSON.stringify(raw, null, " "), - definedAtUrl: sourceUrl, + definedAtUrl: sourceUrl }) } @@ -199,13 +222,14 @@ export default class DetermineLayout { "maindiv" ) - let parsed = await Utils.downloadJson(link) + let parsed = await Utils.downloadJson(link) let forcedId = parsed.id const url = new URL(link) if (!(url.hostname === "localhost" || url.hostname === "127.0.0.1")) { forcedId = link } console.log("Loaded remote link:", link) + parsed = await this.expandRemoteLayers(parsed) return DetermineLayout.prepCustomTheme(parsed, link, forcedId) } } diff --git a/src/Logic/UIEventSource.ts b/src/Logic/UIEventSource.ts index c21de2654..96b450a31 100644 --- a/src/Logic/UIEventSource.ts +++ b/src/Logic/UIEventSource.ts @@ -834,6 +834,10 @@ export class UIEventSource extends Store implements Writable { ) } + public mapAsyncD(f: (t: T) => Promise): Store { + return this.bindD(t => UIEventSource.FromPromise(f(t))) + } + /** * Two way sync with functions in both directions * Given a function 'f', will construct a new UIEventSource where the contents will always be "f(this.data)' @@ -897,4 +901,5 @@ export class UIEventSource extends Store implements Writable { update(f: Updater & ((value: T) => T)): void { this.setData(f(this.data)) } + } diff --git a/src/Models/ThemeConfig/Conversion/Conversion.ts b/src/Models/ThemeConfig/Conversion/Conversion.ts index d9c4eeece..bfae5c135 100644 --- a/src/Models/ThemeConfig/Conversion/Conversion.ts +++ b/src/Models/ThemeConfig/Conversion/Conversion.ts @@ -39,7 +39,7 @@ export abstract class Conversion { ConversionContext.print(msg) } if (context.hasErrors()) { - throw "Detected one or more errors, stopping now" + throw new Error(["Detected one or more errors, stopping now:", context.getAll("error").map(e => e.context.path.join(".")+": "+e.message)].join("\n\t")) } return fixed } diff --git a/src/Models/ThemeConfig/Conversion/PrepareTheme.ts b/src/Models/ThemeConfig/Conversion/PrepareTheme.ts index 8c43691ea..815860321 100644 --- a/src/Models/ThemeConfig/Conversion/PrepareTheme.ts +++ b/src/Models/ThemeConfig/Conversion/PrepareTheme.ts @@ -186,7 +186,7 @@ class AddDefaultLayers extends DesugaringStep { convert(json: LayoutConfigJson, context: ConversionContext): LayoutConfigJson { const state = this._state - json.layers = [...(json.layers ?? [])] + json.layers = Utils.NoNull([...(json.layers ?? [])]) const alreadyLoaded = new Set(json.layers.map((l) => l["id"])) for (const layerName of Constants.added_by_default) { diff --git a/src/Models/ThemeConfig/Json/LayoutConfigJson.ts b/src/Models/ThemeConfig/Json/LayoutConfigJson.ts index 707e67eff..12f00d858 100644 --- a/src/Models/ThemeConfig/Json/LayoutConfigJson.ts +++ b/src/Models/ThemeConfig/Json/LayoutConfigJson.ts @@ -156,7 +156,7 @@ export interface LayoutConfigJson { * type: layer[] * types: hidden | layer | hidden * group: layers - * suggestions: return Array.from(layers.keys()).map(key => ({if: "value="+key, then: key+" - "+layers.get(key).description})) + * suggestions: return Array.from(layers.keys()).map(key => ({if: "value="+key, then: ""+key+" (builtin) - "+layers.get(key).description})) * Every layer contains a description of which feature to display - the overpassTags which are queried. * Instead of running one query for every layer, the query is fused. * diff --git a/src/Models/ThemeConfig/TagRenderingConfig.ts b/src/Models/ThemeConfig/TagRenderingConfig.ts index c26941621..5dec39a77 100644 --- a/src/Models/ThemeConfig/TagRenderingConfig.ts +++ b/src/Models/ThemeConfig/TagRenderingConfig.ts @@ -233,7 +233,7 @@ export default class TagRenderingConfig { } const firstMappingSize: string = json.mappings - .map((m) => m.icon?.["class"]) + .map((m) => m?.icon?.["class"]) .find((c) => !!c) const commonIconSize = firstMappingSize ?? json["#iconsize"] ?? "small" this.mappings = json.mappings.map((m, i) => diff --git a/src/UI/Studio/EditLayerState.ts b/src/UI/Studio/EditLayerState.ts index 480a568a6..a3397c359 100644 --- a/src/UI/Studio/EditLayerState.ts +++ b/src/UI/Studio/EditLayerState.ts @@ -5,7 +5,7 @@ import { Conversion, ConversionMessage, DesugaringContext, - Pipe, + Pipe } from "../../Models/ThemeConfig/Conversion/Conversion" import { PrepareLayer } from "../../Models/ThemeConfig/Conversion/PrepareLayer" import { ValidateLayer, ValidateTheme } from "../../Models/ThemeConfig/Conversion/Validation" @@ -64,7 +64,6 @@ export abstract class EditJsonState { this.category = category this.expertMode = options?.expertMode ?? new UIEventSource(false) - this.messages = this.setupErrorsForLayers() const layerId = this.getId() this.configuration @@ -84,6 +83,8 @@ export abstract class EditJsonState { } await this.server.update(id, config, this.category) }) + this.messages = this.createMessagesStore() + } public startSavingUpdates(enabled = true) { @@ -152,10 +153,10 @@ export abstract class EditJsonState { path, type: "translation", hints: { - typehint: "translation", + typehint: "translation" }, required: origConfig.required ?? false, - description: origConfig.description ?? "A translatable object", + description: origConfig.description ?? "A translatable object" } } @@ -227,28 +228,19 @@ export abstract class EditJsonState { protected abstract getId(): Store - private setupErrorsForLayers(): Store { - const layers = AllSharedLayers.getSharedLayersConfigs() - const questions = layers.get("questions") - const sharedQuestions = new Map() - for (const question of questions.tagRenderings) { - sharedQuestions.set(question["id"], question) - } - let state: DesugaringContext = { - tagRenderings: sharedQuestions, - sharedLayers: layers, - } - const prepare = this.buildValidation(state) - return this.configuration.mapD((config) => { - const context = ConversionContext.construct([], ["prepare"]) - try { - prepare.convert(config, context) - } catch (e) { - console.error(e) - context.err(e) + protected abstract validate(configuration: Partial): Promise; + + /** + * Creates a store that validates the configuration and which contains all relevant (error)-messages + * @private + */ + private createMessagesStore(): Store { + return this.configuration.mapAsyncD(async (config) => { + if(!this.validate){ + return [] } - return context.messages - }) + return await this.validate(config) + }).map(messages => messages ?? []) } } @@ -314,7 +306,7 @@ export default class EditLayerState extends EditJsonState { public readonly imageUploadManager = { getCountsFor() { return 0 - }, + } } public readonly layout: { getMatchingLayer: (key: any) => LayerConfig } public readonly featureSwitches: { @@ -330,8 +322,8 @@ export default class EditLayerState extends EditJsonState { properties: this.testTags.data, geometry: { type: "Point", - coordinates: [3.21, 51.2], - }, + coordinates: [3.21, 51.2] + } } constructor( @@ -343,16 +335,16 @@ export default class EditLayerState extends EditJsonState { super(schema, server, "layers", options) this.osmConnection = osmConnection this.layout = { - getMatchingLayer: (_) => { + getMatchingLayer: () => { try { return new LayerConfig(this.configuration.data, "dynamic") } catch (e) { return undefined } - }, + } } this.featureSwitches = { - featureSwitchIsDebugging: new UIEventSource(true), + featureSwitchIsDebugging: new UIEventSource(true) } this.addMissingTagRenderingIds() @@ -428,6 +420,30 @@ export default class EditLayerState extends EditJsonState { } }) } + + protected async validate(configuration: Partial): Promise { + + const layers = AllSharedLayers.getSharedLayersConfigs() + + const questions = layers.get("questions") + const sharedQuestions = new Map() + for (const question of questions.tagRenderings) { + sharedQuestions.set(question["id"], question) + } + const state: DesugaringContext = { + tagRenderings: sharedQuestions, + sharedLayers: layers + } + const prepare = this.buildValidation(state) + const context = ConversionContext.construct([], ["prepare"]) + try { + prepare.convert(configuration, context) + } catch (e) { + console.error(e) + context.err(e) + } + return context.messages + } } export class EditThemeState extends EditJsonState { @@ -437,6 +453,7 @@ export class EditThemeState extends EditJsonState { options: { expertMode: UIEventSource } ) { super(schema, server, "themes", options) + this.setupFixers() } protected buildValidation(state: DesugaringContext): Conversion { @@ -449,4 +466,55 @@ export class EditThemeState extends EditJsonState { protected getId(): Store { return this.configuration.mapD((config) => config.id) } + + /** Applies a few bandaids to get everything smoothed out in case of errors; a big bunch of hacks basically + */ + public setupFixers() { + this.configuration.addCallbackAndRunD(config => { + if (config.layers) { + // Remove 'null' and 'undefined' values from the layer array if any are found + for (let i = config.layers.length; i >= 0; i--) { + if (!config.layers[i]) { + config.layers.splice(i, 1) + } + } + } + }) + } + + protected async validate(configuration: Partial) { + + const layers = AllSharedLayers.getSharedLayersConfigs() + + for (const l of configuration.layers ?? []) { + if(typeof l !== "string"){ + continue + } + if (!l.startsWith("https://")) { + continue + } + const config = await Utils.downloadJsonCached(l, 1000*60*10) + layers.set(l, config) + } + + const questions = layers.get("questions") + const sharedQuestions = new Map() + for (const question of questions.tagRenderings) { + sharedQuestions.set(question["id"], question) + } + const state: DesugaringContext = { + tagRenderings: sharedQuestions, + sharedLayers: layers + } + const prepare = this.buildValidation(state) + const context = ConversionContext.construct([], ["prepare"]) + try { + prepare.convert(configuration, context) + } catch (e) { + console.error(e) + context.err(e) + } + return context.messages + } + } diff --git a/src/UI/Studio/EditTheme.svelte b/src/UI/Studio/EditTheme.svelte index bd71c0134..becea2ed6 100644 --- a/src/UI/Studio/EditTheme.svelte +++ b/src/UI/Studio/EditTheme.svelte @@ -7,10 +7,50 @@ import ShowConversionMessages from "./ShowConversionMessages.svelte" import Region from "./Region.svelte" import RawEditor from "./RawEditor.svelte" + import { Store, UIEventSource } from "../../Logic/UIEventSource" + import { OsmConnection } from "../../Logic/Osm/OsmConnection" export let state: EditThemeState + export let osmConnection: OsmConnection let schema: ConfigMeta[] = state.schema.filter((schema) => schema.path.length > 0) - let config = state.configuration + + export let selfLayers: { owner: number; id: string }[] + export let otherLayers: { owner: number; id: string }[] + { + + /** + * We modify the schema and inject options for self-declared layers + */ + + const layerSchema = schema.find(l => l.path.join(".") === "layers") + const suggestions: { if: string, then: string }[] = layerSchema.hints.suggestions + suggestions.unshift(...selfLayers.map( + l => ({ + if: `value=https://studio.mapcomplete.org/${l.owner}/layers/${l.id}/${l.id}.json`, + then: `${l.id} (made by you)` + }) + )) + + for (let i = 0; i < otherLayers.length; i++) { + const l = otherLayers[i] + const mapping = { + if: `value=https://studio.mapcomplete.org/${l.owner}/layers/${l.id}/${l.id}.json`, + then: `${l.id} (made by ${l.owner})` + } + /** + * This is a filthy hack which is time-sensitive and will break + * It downloads the username and patches the suggestion, assuming that the list with all layers will be shown a while _after_ loading the view. + * Caching in 'getInformationAboutUser' helps with this as well + */ + osmConnection.getInformationAboutUser(l.owner).then(userInfo => { + mapping.then = `${l.id} (made by ${userInfo.display_name})` + }) + suggestions.push(mapping) + } + + } + + let messages = state.messages let hasErrors = messages.map( (m: ConversionMessage[]) => m.filter((m) => m.level === "error").length diff --git a/src/UI/Studio/SchemaBasedArray.svelte b/src/UI/Studio/SchemaBasedArray.svelte index f1acdbee1..3967d0dbd 100644 --- a/src/UI/Studio/SchemaBasedArray.svelte +++ b/src/UI/Studio/SchemaBasedArray.svelte @@ -64,16 +64,6 @@ } } newPath.push(...toAdd) - console.log( - "Fused path ", - path.join("."), - "+", - i, - "+", - subpartPath.join("."), - "into", - newPath.join(".") - ) return newPath } diff --git a/src/UI/Studio/StudioServer.ts b/src/UI/Studio/StudioServer.ts index 56e230ccf..0033df071 100644 --- a/src/UI/Studio/StudioServer.ts +++ b/src/UI/Studio/StudioServer.ts @@ -4,6 +4,10 @@ import { LayerConfigJson } from "../../Models/ThemeConfig/Json/LayerConfigJson" import { Store } from "../../Logic/UIEventSource" import { LayoutConfigJson } from "../../Models/ThemeConfig/Json/LayoutConfigJson" +/** + * A small class wrapping around the Server API. + * This is _not_ the script which actually hosts! + */ export default class StudioServer { private readonly url: string private readonly _userId: Store @@ -29,7 +33,7 @@ export default class StudioServer { category: "layers" | "themes" }[] = [] for (let file of allFiles) { - let parts = file.split("/") + const parts = file.split("/") let owner = Number(parts[0]) if (!isNaN(owner)) { parts.splice(0, 1) @@ -55,7 +59,7 @@ export default class StudioServer { uid?: number ): Promise { try { - return await Utils.downloadJson(this.urlFor(layerId, category, uid)) + return await Utils.downloadJson(this.urlFor(layerId, category, uid)) } catch (e) { return undefined } diff --git a/src/UI/StudioGUI.svelte b/src/UI/StudioGUI.svelte index 10f544bfa..bdaa84c97 100644 --- a/src/UI/StudioGUI.svelte +++ b/src/UI/StudioGUI.svelte @@ -58,8 +58,8 @@ let layers: Store<{ owner: number; id: string }[]> = layersWithErr.mapD((l) => l["success"]?.filter((l) => l.category === "layers") ) - let selfLayers = layers.mapD((ls) => ls.filter((l) => l.owner === uid.data), [uid]) - let otherLayers = layers.mapD( + let selfLayers: Store<{ owner: number; id: string }[]> = layers.mapD((ls) => ls.filter((l) => l.owner === uid.data), [uid]) + let otherLayers: Store<{ owner: number; id: string }[]> = layers.mapD( (ls) => ls.filter((l) => l.owner !== undefined && l.owner !== uid.data), [uid] ) @@ -291,7 +291,7 @@ {:else if state === "editing_theme"} - + )}`", + "type": "array", + "items": { + "type": "string" + } + }, + "eraseInvalidValues": { + "description": "If set, invalid values will be erased in the MC application (but not in OSM of course!)\nBe careful with setting this", + "type": "boolean" + }, + "applicableUnits": { + "description": "The possible denominations for this unit.\nFor length, denominations could be \"meter\", \"kilometer\", \"miles\", \"foot\"", + "type": "array", + "items": { + "$ref": "#/definitions/DenominationConfigJson" + } + }, + "defaultInput": { + "description": "In some cases, the default denomination is not the most user friendly to input.\nE.g., when measuring kerb heights, it is illogical to ask contributors to input an amount in meters.\n\nWhen a default input method should be used, this can be specified by setting the canonical denomination here, e.g.\n`defaultInput: \"cm\"`. This must be a denomination which appears in the applicableUnits", + "type": "string" + } + }, + "required": [ + "applicableUnits" + ], + "additionalProperties": false + }, + { + "$ref": "#/definitions/Record" + } + ], + "description": "Either a list with [{\"key\": \"unitname\", \"key2\": {\"quantity\": \"unitname\", \"denominations\": [\"denom\", \"denom\"]}}]" + }, { "path": [ "units", diff --git a/src/assets/schemas/layoutconfigmeta.json b/src/assets/schemas/layoutconfigmeta.json index 682303ca9..3bbe20050 100644 --- a/src/assets/schemas/layoutconfigmeta.json +++ b/src/assets/schemas/layoutconfigmeta.json @@ -326,595 +326,615 @@ "suggestions": [ { "if": "value=address", - "then": "address - Addresses" + "then": "address (builtin) - Addresses" }, { "if": "value=advertising", - "then": "advertising - We will complete data from advertising features with reference, operator and lit" + "then": "advertising (builtin) - We will complete data from advertising features with reference, operator and lit" }, { "if": "value=aerialway", - "then": "aerialway - Various forms of transport for passengers and goods that use wires, including cable cars, gondolas, chair lifts, drag lifts, and zip lines. " + "then": "aerialway (builtin) - Various forms of transport for passengers and goods that use wires, including cable cars, gondolas, chair lifts, drag lifts, and zip lines. " }, { "if": "value=ambulancestation", - "then": "ambulancestation - An ambulance station is an area for storage of ambulance vehicles, medical equipment, personal protective equipment, and other medical supplies." + "then": "ambulancestation (builtin) - An ambulance station is an area for storage of ambulance vehicles, medical equipment, personal protective equipment, and other medical supplies." }, { "if": "value=animal_shelter", - "then": "animal_shelter - An animal shelter is a facility where animals in trouble are brought and facility's staff (volunteers or not) feeds them and cares of them, rehabilitating and healing them if necessary. This definition includes kennels for abandoned dogs, catteries for abandoned cats, shelters for other abandoned pets and wildlife recovery centres. " + "then": "animal_shelter (builtin) - An animal shelter is a facility where animals in trouble are brought and facility's staff (volunteers or not) feeds them and cares of them, rehabilitating and healing them if necessary. This definition includes kennels for abandoned dogs, catteries for abandoned cats, shelters for other abandoned pets and wildlife recovery centres. " }, { "if": "value=artwork", - "then": "artwork - An open map of statues, busts, graffitis and other artwork all over the world" + "then": "artwork (builtin) - An open map of statues, busts, graffitis and other artwork all over the world" }, { "if": "value=assembly_point", - "then": "assembly_point - This layer contains assembly points and waiting areas where all employees, passengers or a large crowd assemble in case of an emergency." + "then": "assembly_point (builtin) - This layer contains assembly points and waiting areas where all employees, passengers or a large crowd assemble in case of an emergency." }, { "if": "value=atm", - "then": "atm - ATMs to withdraw money" + "then": "atm (builtin) - ATMs to withdraw money" }, { "if": "value=bank", - "then": "bank - A financial institution to deposit money" + "then": "bank (builtin) - A financial institution to deposit money" }, { "if": "value=barrier", - "then": "barrier - Obstacles while cycling, such as bollards and cycle barriers" + "then": "barrier (builtin) - Obstacles while cycling, such as bollards and cycle barriers" }, { "if": "value=bench", - "then": "bench - A bench is a wooden, metal, stone, … surface where a human can sit. This layers visualises them and asks a few questions about them." + "then": "bench (builtin) - A bench is a wooden, metal, stone, … surface where a human can sit. This layers visualises them and asks a few questions about them." }, { "if": "value=bench_at_pt", - "then": "bench_at_pt - A layer showing all public-transport-stops which do have a bench" + "then": "bench_at_pt (builtin) - A layer showing all public-transport-stops which do have a bench" }, { "if": "value=bicycle_library", - "then": "bicycle_library - A facility where bicycles can be lent for longer period of times" + "then": "bicycle_library (builtin) - A facility where bicycles can be lent for longer period of times" }, { "if": "value=bicycle_rental", - "then": "bicycle_rental - Bicycle rental stations" + "then": "bicycle_rental (builtin) - Bicycle rental stations" }, { "if": "value=bicycle_tube_vending_machine", - "then": "bicycle_tube_vending_machine - A layer showing vending machines for bicycle tubes (either purpose-built bicycle tube vending machines or classical vending machines with bicycle tubes and optionally additional bicycle related objects such as lights, gloves, locks, …)" + "then": "bicycle_tube_vending_machine (builtin) - A layer showing vending machines for bicycle tubes (either purpose-built bicycle tube vending machines or classical vending machines with bicycle tubes and optionally additional bicycle related objects such as lights, gloves, locks, …)" }, { "if": "value=bike_cafe", - "then": "bike_cafe - A bike café is a café geared towards cyclists, for example with services such as a pump, with lots of bicycle-related decoration, …" + "then": "bike_cafe (builtin) - A bike café is a café geared towards cyclists, for example with services such as a pump, with lots of bicycle-related decoration, …" }, { "if": "value=bike_cleaning", - "then": "bike_cleaning - A layer showing facilities where one can clean their bike" + "then": "bike_cleaning (builtin) - A layer showing facilities where one can clean their bike" }, { "if": "value=bike_parking", - "then": "bike_parking - A layer showing where you can park your bike" + "then": "bike_parking (builtin) - A layer showing where you can park your bike" }, { "if": "value=bike_repair_station", - "then": "bike_repair_station - A layer showing bicycle pumps and bicycle repair tool stands" + "then": "bike_repair_station (builtin) - A layer showing bicycle pumps and bicycle repair tool stands" }, { "if": "value=bike_shop", - "then": "bike_shop - A shop specifically selling bicycles or related items" + "then": "bike_shop (builtin) - A shop specifically selling bicycles or related items" }, { "if": "value=bike_themed_object", - "then": "bike_themed_object - A layer with bike-themed objects but who don't match any other layer" + "then": "bike_themed_object (builtin) - A layer with bike-themed objects but who don't match any other layer" }, { "if": "value=binocular", - "then": "binocular - Binoculars" + "then": "binocular (builtin) - Binoculars" }, { "if": "value=birdhide", - "then": "birdhide - A birdhide" + "then": "birdhide (builtin) - A birdhide" }, { "if": "value=brothel", - "then": "brothel - An establishment specifically dedicated to prostitution. " + "then": "brothel (builtin) - An establishment specifically dedicated to prostitution. " }, { "if": "value=cafe_pub", - "then": "cafe_pub - A layer showing cafés and pubs where one can gather around a drink. The layer asks for some relevant questions" + "then": "cafe_pub (builtin) - A layer showing cafés and pubs where one can gather around a drink. The layer asks for some relevant questions" }, { "if": "value=car_rental", - "then": "car_rental - Places where you can rent a car" + "then": "car_rental (builtin) - Places where you can rent a car" }, { "if": "value=charging_station", - "then": "charging_station - A charging station" + "then": "charging_station (builtin) - A charging station" }, { "if": "value=cinema", - "then": "cinema - A place showing movies (films), generally open to the public for a fee. Commonly referred to as a movie theater in the US" + "then": "cinema (builtin) - A place showing movies (films), generally open to the public for a fee. Commonly referred to as a movie theater in the US" }, { "if": "value=climbing", - "then": "climbing - A dummy layer which contains tagrenderings, shared among the climbing layers" + "then": "climbing (builtin) - A dummy layer which contains tagrenderings, shared among the climbing layers" }, { "if": "value=climbing_area", - "then": "climbing_area - An area where climbing is possible, e.g. a crag, site, boulder, … Contains aggregation of routes" + "then": "climbing_area (builtin) - An area where climbing is possible, e.g. a crag, site, boulder, … Contains aggregation of routes" }, { "if": "value=climbing_club", - "then": "climbing_club - A climbing club or organisation" + "then": "climbing_club (builtin) - A climbing club or organisation" }, { "if": "value=climbing_gym", - "then": "climbing_gym - A climbing gym" + "then": "climbing_gym (builtin) - A climbing gym" }, { "if": "value=climbing_opportunity", - "then": "climbing_opportunity - Fallback layer with items on which climbing _might_ be possible. It is loaded when zoomed in a lot, to prevent duplicate items to be added" + "then": "climbing_opportunity (builtin) - Fallback layer with items on which climbing _might_ be possible. It is loaded when zoomed in a lot, to prevent duplicate items to be added" }, { "if": "value=climbing_route", - "then": "climbing_route - A single climbing route and its properties. Some properties are derived from the containing features" + "then": "climbing_route (builtin) - A single climbing route and its properties. Some properties are derived from the containing features" }, { "if": "value=clock", - "then": "clock - Layer with public clocks" + "then": "clock (builtin) - Layer with public clocks" }, { "if": "value=conflation", - "then": "conflation - If the import-button moves OSM points, the imported way points or conflates, a preview is shown. This layer defines how this preview is rendered. This layer cannot be included in a theme." + "then": "conflation (builtin) - If the import-button moves OSM points, the imported way points or conflates, a preview is shown. This layer defines how this preview is rendered. This layer cannot be included in a theme." }, { "if": "value=crab_address", - "then": "crab_address - Address data for Flanders by the governement, suited for import into OpenStreetMap. Datadump from 2021-10-26. This layer contains only visualisation logic. Import buttons should be added via an override. Note that HNRLABEL contains the original value, whereas _HNRLABEL contains a slightly cleaned version" + "then": "crab_address (builtin) - Address data for Flanders by the governement, suited for import into OpenStreetMap. Datadump from 2021-10-26. This layer contains only visualisation logic. Import buttons should be added via an override. Note that HNRLABEL contains the original value, whereas _HNRLABEL contains a slightly cleaned version" }, { "if": "value=crossings", - "then": "crossings - Crossings for pedestrians and cyclists" + "then": "crossings (builtin) - Crossings for pedestrians and cyclists" }, { "if": "value=current_view", - "then": "current_view - A meta-layer which contains one single feature, namely the bounding box of the current map view. This can be used to trigger special actions. If a popup is defined for this layer, this popup will be accessible via an extra button on screen.\n\nThe icon on the button is the default icon of the layer, but can be customized by detecting 'button=yes'." + "then": "current_view (builtin) - A meta-layer which contains one single feature, namely the bounding box of the current map view. This can be used to trigger special actions. If a popup is defined for this layer, this popup will be accessible via an extra button on screen.\n\nThe icon on the button is the default icon of the layer, but can be customized by detecting 'button=yes'." }, { "if": "value=cycleways_and_roads", - "then": "cycleways_and_roads - All infrastructure that someone can cycle over, accompanied with questions about this infrastructure" + "then": "cycleways_and_roads (builtin) - All infrastructure that someone can cycle over, accompanied with questions about this infrastructure" }, { "if": "value=defibrillator", - "then": "defibrillator - A layer showing defibrillators which can be used in case of emergency. This contains public defibrillators, but also defibrillators which might need staff to fetch the actual device" + "then": "defibrillator (builtin) - A layer showing defibrillators which can be used in case of emergency. This contains public defibrillators, but also defibrillators which might need staff to fetch the actual device" }, { "if": "value=dentist", - "then": "dentist - This layer shows dentist offices" + "then": "dentist (builtin) - This layer shows dentist offices" }, { "if": "value=direction", - "then": "direction - This layer visualizes directions" + "then": "direction (builtin) - This layer visualizes directions" }, { "if": "value=disaster_response", - "then": "disaster_response - This layer contains organizations that have the main objective to help the civil population during and after natural or anthropogenic disasters by working in the affected area." + "then": "disaster_response (builtin) - This layer contains organizations that have the main objective to help the civil population during and after natural or anthropogenic disasters by working in the affected area." }, { "if": "value=doctors", - "then": "doctors - This layer shows doctor offices" + "then": "doctors (builtin) - This layer shows doctor offices" }, { "if": "value=dogpark", - "then": "dogpark - A layer showing dogparks, which are areas where dog are allowed to run without a leash" + "then": "dogpark (builtin) - A layer showing dogparks, which are areas where dog are allowed to run without a leash" }, { "if": "value=drinking_water", - "then": "drinking_water - A layer showing drinking water fountains" + "then": "drinking_water (builtin) - A layer showing drinking water fountains" }, { "if": "value=elevator", - "then": "elevator - This layer show elevators and asks for operational status and elevator dimensions. Useful for wheelchair accessibility information" + "then": "elevator (builtin) - This layer show elevators and asks for operational status and elevator dimensions. Useful for wheelchair accessibility information" }, { "if": "value=elongated_coin", - "then": "elongated_coin - Layer showing penny presses." + "then": "elongated_coin (builtin) - Layer showing penny presses." }, { "if": "value=entrance", - "then": "entrance - A layer showing entrances and offering capabilities to survey some advanced data which is important for e.g. wheelchair users (but also bicycle users, people who want to deliver, …)" + "then": "entrance (builtin) - A layer showing entrances and offering capabilities to survey some advanced data which is important for e.g. wheelchair users (but also bicycle users, people who want to deliver, …)" }, { "if": "value=etymology", - "then": "etymology - All objects which have an etymology known" + "then": "etymology (builtin) - All objects which have an etymology known" }, { "if": "value=extinguisher", - "then": "extinguisher - Map layer to show fire extinguishers." + "then": "extinguisher (builtin) - Map layer to show fire extinguishers." }, { "if": "value=filters", - "then": "filters - This layer acts as library for common filters" + "then": "filters (builtin) - This layer acts as library for common filters" }, { "if": "value=fire_station", - "then": "fire_station - Map layer to show fire stations." + "then": "fire_station (builtin) - Map layer to show fire stations." }, { "if": "value=fitness_centre", - "then": "fitness_centre - Layer showing fitness centres" + "then": "fitness_centre (builtin) - Layer showing fitness centres" }, { "if": "value=fitness_station", - "then": "fitness_station - Find a fitness station near you, and add missing ones." + "then": "fitness_station (builtin) - Find a fitness station near you, and add missing ones." }, { "if": "value=fixme", - "then": "fixme - OSM objects that likely need to be fixed, based on a FIXME tag." + "then": "fixme (builtin) - OSM objects that likely need to be fixed, based on a FIXME tag." }, { "if": "value=food", - "then": "food - A layer showing restaurants and fast-food amenities (with a special rendering for friteries)" + "then": "food (builtin) - A layer showing restaurants and fast-food amenities (with a special rendering for friteries)" }, { "if": "value=ghost_bike", - "then": "ghost_bike - A layer showing memorials for cyclists, killed in road accidents" + "then": "ghost_bike (builtin) - A layer showing memorials for cyclists, killed in road accidents" }, { "if": "value=governments", - "then": "governments - This layer show governmental buildings. It was setup as commissioned layer for the client of OSOC '22" + "then": "governments (builtin) - This layer show governmental buildings. It was setup as commissioned layer for the client of OSOC '22" }, { "if": "value=gps_location", - "then": "gps_location - Meta layer showing the current location of the user. Add this to your theme and override the icon to change the appearance of the current location. The object will always have `id=gps` and will have _all_ the properties included in the [`Coordinates`-object](https://developer.mozilla.org/en-US/docs/Web/API/GeolocationCoordinates) (except latitude and longitude) returned by the browser, such as `speed`, `altitude`, `heading`, ...." + "then": "gps_location (builtin) - Meta layer showing the current location of the user. Add this to your theme and override the icon to change the appearance of the current location. The object will always have `id=gps` and will have _all_ the properties included in the [`Coordinates`-object](https://developer.mozilla.org/en-US/docs/Web/API/GeolocationCoordinates) (except latitude and longitude) returned by the browser, such as `speed`, `altitude`, `heading`, ...." }, { "if": "value=gps_location_history", - "then": "gps_location_history - Meta layer which contains the previous locations of the user as single points. This is mainly for technical reasons, e.g. to keep match the distance to the modified object" + "then": "gps_location_history (builtin) - Meta layer which contains the previous locations of the user as single points. This is mainly for technical reasons, e.g. to keep match the distance to the modified object" }, { "if": "value=gps_track", - "then": "gps_track - Meta layer showing the previous locations of the user as single line with controls, e.g. to erase, upload or download this track. Add this to your theme and override the maprendering to change the appearance of the travelled track." + "then": "gps_track (builtin) - Meta layer showing the previous locations of the user as single line with controls, e.g. to erase, upload or download this track. Add this to your theme and override the maprendering to change the appearance of the travelled track." }, { "if": "value=guidepost", - "then": "guidepost - Guideposts (also known as fingerposts or finger posts) are often found along official hiking/cycling/riding/skiing routes to indicate the directions to different destinations" + "then": "guidepost (builtin) - Guideposts (also known as fingerposts or finger posts) are often found along official hiking/cycling/riding/skiing routes to indicate the directions to different destinations" }, { "if": "value=hackerspace", - "then": "hackerspace - Hackerspace" + "then": "hackerspace (builtin) - Hackerspace" }, { "if": "value=home_location", - "then": "home_location - Meta layer showing the home location of the user. The home location can be set in the [profile settings](https://www.openstreetmap.org/profile/edit) of OpenStreetMap." + "then": "home_location (builtin) - Meta layer showing the home location of the user. The home location can be set in the [profile settings](https://www.openstreetmap.org/profile/edit) of OpenStreetMap." }, { "if": "value=hospital", - "then": "hospital - A layer showing hospital grounds" + "then": "hospital (builtin) - A layer showing hospital grounds" }, { "if": "value=hotel", - "then": "hotel - Layer showing all hotels" + "then": "hotel (builtin) - Layer showing all hotels" }, { "if": "value=hydrant", - "then": "hydrant - Map layer to show fire hydrants." + "then": "hydrant (builtin) - Map layer to show fire hydrants." }, { "if": "value=ice_cream", - "then": "ice_cream - A place where ice cream is sold over the counter" + "then": "ice_cream (builtin) - A place where ice cream is sold over the counter" }, { "if": "value=icons", - "then": "icons - A layer acting as library for icon-tagrenderings, especially to show as badge next to a POI" + "then": "icons (builtin) - A layer acting as library for icon-tagrenderings, especially to show as badge next to a POI" }, { "if": "value=id_presets", - "then": "id_presets - Layer containing various presets and questions generated by ID. These are meant to be reused in other layers by importing the tagRenderings with `id_preset." + "then": "id_presets (builtin) - Layer containing various presets and questions generated by ID. These are meant to be reused in other layers by importing the tagRenderings with `id_preset." }, { "if": "value=import_candidate", - "then": "import_candidate - Layer used as template in the importHelper" + "then": "import_candidate (builtin) - Layer used as template in the importHelper" }, { "if": "value=indoors", - "then": "indoors - Basic indoor mapping: shows room outlines" + "then": "indoors (builtin) - Basic indoor mapping: shows room outlines" }, { "if": "value=information_board", - "then": "information_board - A layer showing touristical, road side information boards (e.g. giving information about the landscape, a building, a feature, a map, …)" + "then": "information_board (builtin) - A layer showing touristical, road side information boards (e.g. giving information about the landscape, a building, a feature, a map, …)" + }, + { + "if": "value=item_with_image", + "then": "item_with_image (builtin) - All items with an image. All alone, not a layer which is relevant for any MapComplete theme, as it is a random collection of items. However, when put into the databank, this allows to quickly fetch (the URL of) pictures nearby a different object, to quickly link this" }, { "if": "value=kerbs", - "then": "kerbs - A layer showing kerbs." + "then": "kerbs (builtin) - A layer showing kerbs." }, { "if": "value=kindergarten_childcare", - "then": "kindergarten_childcare - Shows kindergartens and preschools. Both are grouped in one layer, as they are regularly confused with each other" + "then": "kindergarten_childcare (builtin) - Shows kindergartens and preschools. Both are grouped in one layer, as they are regularly confused with each other" }, { "if": "value=last_click", - "then": "last_click - This layer defines how to render the 'last click'-location. By default, it will show a marker with the possibility to add a new point (if there are some presets) and/or to add a new note (if the 'note' layer attribute is set). If none are possible, this layer won't show up" + "then": "last_click (builtin) - This 'layer' is not really a layer, but contains part of the code how the popup to 'add a new marker' is displayed" }, { "if": "value=love_hotel", - "then": "love_hotel - A love hotel is a type of short-stay hotel found around the world operated primarily for the purpose of allowing guests privacy for sexual activities" + "then": "love_hotel (builtin) - A love hotel is a type of short-stay hotel found around the world operated primarily for the purpose of allowing guests privacy for sexual activities" }, { "if": "value=map", - "then": "map - A map, meant for tourists which is permanently installed in the public space" + "then": "map (builtin) - A map, meant for tourists which is permanently installed in the public space" }, { "if": "value=maproulette", - "then": "maproulette - Layer showing all tasks in MapRoulette" + "then": "maproulette (builtin) - Layer showing all tasks in MapRoulette" }, { "if": "value=maproulette_challenge", - "then": "maproulette_challenge - Layer showing tasks of a single MapRoulette challenge. This layer is intended to be reused and extended in themes; refer to [the documentation](https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Integrating_Maproulette.md) on how to do this." + "then": "maproulette_challenge (builtin) - Layer showing tasks of a single MapRoulette challenge. This layer is intended to be reused and extended in themes; refer to [the documentation](https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Integrating_Maproulette.md) on how to do this." }, { "if": "value=maxspeed", - "then": "maxspeed - Shows the allowed speed for every road" + "then": "maxspeed (builtin) - Shows the allowed speed for every road" }, { "if": "value=memorial", - "then": "memorial - Layer showing memorial plaques, based upon a unofficial theme. Can be expanded to have multiple types of memorials later on" + "then": "memorial (builtin) - Layer showing memorial plaques, based upon a unofficial theme. Can be expanded to have multiple types of memorials later on" }, { "if": "value=mountain_rescue", - "then": "mountain_rescue - A building where first aid responders store material and might be on watch" + "then": "mountain_rescue (builtin) - A building where first aid responders store material and might be on watch" }, { "if": "value=named_streets", - "then": "named_streets - Hidden layer with all streets which have a name. Useful to detect addresses" + "then": "named_streets (builtin) - Hidden layer with all streets which have a name. Useful to detect addresses" }, { "if": "value=nature_reserve", - "then": "nature_reserve - A nature reserve is an area where nature can take its course" + "then": "nature_reserve (builtin) - A nature reserve is an area where nature can take its course" }, { "if": "value=note", - "then": "note - This layer shows notes on OpenStreetMap. Having this layer in your theme will trigger the 'add new note' functionality in the 'addNewPoint'-popup (or if your theme has no presets, it'll enable adding notes)" + "then": "note (builtin) - This layer shows notes on OpenStreetMap. Having this layer in your theme will trigger the 'add new note' functionality in the 'addNewPoint'-popup (or if your theme has no presets, it'll enable adding notes)" }, { "if": "value=observation_tower", - "then": "observation_tower - Towers with a panoramic view" + "then": "observation_tower (builtin) - Towers with a panoramic view" }, { "if": "value=osm_community_index", - "then": "osm_community_index - A layer showing the OpenStreetMap Communities" + "then": "osm_community_index (builtin) - A layer showing the OpenStreetMap Communities" }, { "if": "value=parcel_lockers", - "then": "parcel_lockers - Layer showing parcel lockers for collecting and sending parcels." + "then": "parcel_lockers (builtin) - Layer showing parcel lockers for collecting and sending parcels." }, { "if": "value=parking", - "then": "parking - A layer showing car parkings" + "then": "parking (builtin) - A layer showing car parkings" }, { "if": "value=parking_spaces", - "then": "parking_spaces - Layer showing individual parking spaces." + "then": "parking_spaces (builtin) - Layer showing individual parking spaces." }, { "if": "value=parking_ticket_machine", - "then": "parking_ticket_machine - Layer with parking ticket machines to pay for parking." + "then": "parking_ticket_machine (builtin) - Layer with parking ticket machines to pay for parking." }, { "if": "value=pedestrian_path", - "then": "pedestrian_path - Pedestrian footpaths, especially used for indoor navigation and snapping entrances to this layer" + "then": "pedestrian_path (builtin) - Pedestrian footpaths, especially used for indoor navigation and snapping entrances to this layer" }, { "if": "value=pharmacy", - "then": "pharmacy - A layer showing pharmacies, which (probably) dispense prescription drugs" + "then": "pharmacy (builtin) - A layer showing pharmacies, which (probably) dispense prescription drugs" }, { "if": "value=physiotherapist", - "then": "physiotherapist - This layer shows physiotherapists" + "then": "physiotherapist (builtin) - This layer shows physiotherapists" }, { "if": "value=picnic_table", - "then": "picnic_table - The layer showing picnic tables" + "then": "picnic_table (builtin) - The layer showing picnic tables" }, { "if": "value=play_forest", - "then": "play_forest - Een speelbos is een vrij toegankelijke zone in een bos" + "then": "play_forest (builtin) - Een speelbos is een vrij toegankelijke zone in een bos" }, { "if": "value=playground", - "then": "playground - Playgrounds" + "then": "playground (builtin) - Playgrounds" + }, + { + "if": "value=playground_equipment", + "then": "playground_equipment (builtin) - Layer showing playground equipment" }, { "if": "value=postboxes", - "then": "postboxes - The layer showing postboxes." + "then": "postboxes (builtin) - The layer showing postboxes." }, { "if": "value=postoffices", - "then": "postoffices - A layer showing post offices." + "then": "postoffices (builtin) - A layer showing post offices." }, { "if": "value=public_bookcase", - "then": "public_bookcase - A streetside cabinet with books, accessible to anyone" + "then": "public_bookcase (builtin) - A streetside cabinet with books, accessible to anyone" }, { "if": "value=questions", - "then": "questions - Special library layer which does not need a '.questions'-prefix before being imported" + "then": "questions (builtin) - Special library layer which does not need a '.questions'-prefix before being imported" }, { "if": "value=railway_platforms", - "then": "railway_platforms - Find every platform in the station, and the train routes that use them." + "then": "railway_platforms (builtin) - Find every platform in the station, and the train routes that use them." }, { "if": "value=rainbow_crossings", - "then": "rainbow_crossings - A layer showing pedestrian crossings with rainbow paintings" + "then": "rainbow_crossings (builtin) - A layer showing pedestrian crossings with rainbow paintings" }, { "if": "value=range", - "then": "range - Meta-layer, simply showing a bbox in red" + "then": "range (builtin) - Meta-layer, simply showing a bbox in red" }, { "if": "value=reception_desk", - "then": "reception_desk - A layer showing where the reception desks are and which asks some accessibility information" + "then": "reception_desk (builtin) - A layer showing where the reception desks are and which asks some accessibility information" }, { "if": "value=recycling", - "then": "recycling - A layer with recycling containers and centres" + "then": "recycling (builtin) - A layer with recycling containers and centres" }, { "if": "value=route_marker", - "then": "route_marker - Route markers are small markers often found along official hiking/cycling/riding/skiing routes to indicate the direction of the route." + "then": "route_marker (builtin) - Route markers are small markers often found along official hiking/cycling/riding/skiing routes to indicate the direction of the route." }, { "if": "value=school", - "then": "school - Schools giving primary and secondary education and post-secondary, non-tertiary education. Note that this level of education does not imply an age of the pupiles" + "then": "school (builtin) - Schools giving primary and secondary education and post-secondary, non-tertiary education. Note that this level of education does not imply an age of the pupiles" }, { "if": "value=selected_element", - "then": "selected_element - Highlights the currently selected element. Override this layer to have different colors" + "then": "selected_element (builtin) - Highlights the currently selected element. Override this layer to have different colors" }, { "if": "value=shelter", - "then": "shelter - Layer showing shelter structures" + "then": "shelter (builtin) - Layer showing shelter structures" }, { "if": "value=shops", - "then": "shops - A shop" + "then": "shops (builtin) - A shop" }, { "if": "value=shower", - "then": "shower - A layer showing (public) showers" + "then": "shower (builtin) - A layer showing (public) showers" }, { "if": "value=ski_piste", - "then": "ski_piste - Ski and snowboard pistes" + "then": "ski_piste (builtin) - Ski and snowboard pistes" }, { "if": "value=slow_roads", - "then": "slow_roads - All carfree roads" + "then": "slow_roads (builtin) - All carfree roads" + }, + { + "if": "value=souvenir_coin", + "then": "souvenir_coin (builtin) - Layer showing machines selling souvenir coins" + }, + { + "if": "value=souvenir_note", + "then": "souvenir_note (builtin) - Layer showing machines selling souvenir banknotes" }, { "if": "value=speed_camera", - "then": "speed_camera - Layer showing speed cameras" + "then": "speed_camera (builtin) - Layer showing speed cameras" }, { "if": "value=speed_display", - "then": "speed_display - Layer showing speed displays that alert drivers of their speed." + "then": "speed_display (builtin) - Layer showing speed displays that alert drivers of their speed." }, { "if": "value=split_point", - "then": "split_point - Layer rendering the little scissors for the minimap in the 'splitRoadWizard'" + "then": "split_point (builtin) - Layer rendering the little scissors for the minimap in the 'splitRoadWizard'" }, { "if": "value=split_road", - "then": "split_road - Layer rendering the way to split in the 'splitRoadWizard'. This one is used instead of the variable rendering by the themes themselves, as they might not always be very visible" + "then": "split_road (builtin) - Layer rendering the way to split in the 'splitRoadWizard'. This one is used instead of the variable rendering by the themes themselves, as they might not always be very visible" }, { "if": "value=sport_pitch", - "then": "sport_pitch - A sport pitch" + "then": "sport_pitch (builtin) - A sport pitch" }, { "if": "value=sports_centre", - "then": "sports_centre - Indoor and outdoor sports centres can be found on this layer" + "then": "sports_centre (builtin) - Indoor and outdoor sports centres can be found on this layer" }, { "if": "value=stairs", - "then": "stairs - Layer showing stairs and escalators" + "then": "stairs (builtin) - Layer showing stairs and escalators" }, { "if": "value=street_lamps", - "then": "street_lamps - A layer showing street lights" + "then": "street_lamps (builtin) - A layer showing street lights" }, { "if": "value=stripclub", - "then": "stripclub - A venue where erotic dance, striptease, or lap dances are performed commercially. " + "then": "stripclub (builtin) - A venue where erotic dance, striptease, or lap dances are performed commercially. " + }, + { + "if": "value=summary", + "then": "summary (builtin) - Special layer which shows `count`" }, { "if": "value=surveillance_camera", - "then": "surveillance_camera - This layer shows surveillance cameras and allows a contributor to update information and add new cameras" + "then": "surveillance_camera (builtin) - This layer shows surveillance cameras and allows a contributor to update information and add new cameras" }, { "if": "value=tertiary_education", - "then": "tertiary_education - Layer with all tertiary education institutes (ISCED:2011 levels 6,7 and 8)" + "then": "tertiary_education (builtin) - Layer with all tertiary education institutes (ISCED:2011 levels 6,7 and 8)" }, { "if": "value=ticket_machine", - "then": "ticket_machine - Find ticket machines for public transport tickets" + "then": "ticket_machine (builtin) - Find ticket machines for public transport tickets" }, { "if": "value=ticket_validator", - "then": "ticket_validator - Find ticket validators to validate public transport tickets" + "then": "ticket_validator (builtin) - Find ticket validators to validate public transport tickets" }, { "if": "value=toilet", - "then": "toilet - A layer showing (public) toilets" + "then": "toilet (builtin) - A layer showing (public) toilets" }, { "if": "value=toilet_at_amenity", - "then": "toilet_at_amenity - A layer showing (public) toilets located at different places." + "then": "toilet_at_amenity (builtin) - A layer showing (public) toilets located at different places." }, { "if": "value=trail", - "then": "trail - Waymarked trails" + "then": "trail (builtin) - Waymarked trails" }, { "if": "value=transit_routes", - "then": "transit_routes - Layer showing bus lines" + "then": "transit_routes (builtin) - Layer showing bus lines" }, { "if": "value=transit_stops", - "then": "transit_stops - Layer showing different types of transit stops." + "then": "transit_stops (builtin) - Layer showing different types of transit stops." }, { "if": "value=tree_node", - "then": "tree_node - A layer showing trees" + "then": "tree_node (builtin) - A layer showing trees" }, { "if": "value=trolley_bay", - "then": "trolley_bay - Find trolley bays for shopping trolleys." + "then": "trolley_bay (builtin) - Find trolley bays for shopping trolleys." }, { "if": "value=unit", - "then": "unit - Library layer with all common units. Units can _only_ be imported from this file." + "then": "unit (builtin) - Library layer with all common units. Units can _only_ be imported from this file." }, { "if": "value=usersettings", - "then": "usersettings - A special layer which is not meant to be shown on a map, but which is used to set user settings" + "then": "usersettings (builtin) - A special layer which is not meant to be shown on a map, but which is used to set user settings" }, { "if": "value=vending_machine", - "then": "vending_machine - Layer showing vending machines" + "then": "vending_machine (builtin) - Layer showing vending machines" }, { "if": "value=veterinary", - "then": "veterinary - A layer showing veterinarians" + "then": "veterinary (builtin) - A layer showing veterinarians" }, { "if": "value=viewpoint", - "then": "viewpoint - A nice viewpoint or nice view. Ideal to add an image if no other category fits" + "then": "viewpoint (builtin) - A nice viewpoint or nice view. Ideal to add an image if no other category fits" }, { "if": "value=village_green", - "then": "village_green - A layer showing village-green (which are communal green areas, but not quite parks)" + "then": "village_green (builtin) - A layer showing village-green (which are communal green areas, but not quite parks)" }, { "if": "value=visitor_information_centre", - "then": "visitor_information_centre - A visitor center offers information about a specific attraction or place of interest where it is located." + "then": "visitor_information_centre (builtin) - A visitor center offers information about a specific attraction or place of interest where it is located." }, { "if": "value=walls_and_buildings", - "then": "walls_and_buildings - Special builtin layer providing all walls and buildings. This layer is useful in presets for objects which can be placed against walls (e.g. AEDs, postboxes, entrances, addresses, surveillance cameras, …). This layer is invisible by default and not toggleable by the user." + "then": "walls_and_buildings (builtin) - Special builtin layer providing all walls and buildings. This layer is useful in presets for objects which can be placed against walls (e.g. AEDs, postboxes, entrances, addresses, surveillance cameras, …). This layer is invisible by default and not toggleable by the user." }, { "if": "value=waste_basket", - "then": "waste_basket - This is a public waste basket, thrash can, where you can throw away your thrash." + "then": "waste_basket (builtin) - This is a public waste basket, thrash can, where you can throw away your thrash." }, { "if": "value=waste_disposal", - "then": "waste_disposal - Waste Disposal Bin, medium to large bin for disposal of (household) waste" + "then": "waste_disposal (builtin) - Waste Disposal Bin, medium to large bin for disposal of (household) waste" }, { "if": "value=windturbine", - "then": "windturbine - Modern windmills generating electricity" + "then": "windturbine (builtin) - Modern windmills generating electricity" } ] }, @@ -958,10 +978,6 @@ "osmTags": { "$ref": "#/definitions/TagConfigJson", "description": "question: Which tags must be present on the feature to show it in this layer?\nEvery source must set which tags have to be present in order to load the given layer." - }, - "maxCacheAge": { - "description": "question: How long (in seconds) is the data allowed to remain cached until it must be refreshed?\nThe maximum amount of seconds that a tile is allowed to linger in the cache\n\ntype: nat\ndefault: 30 days\ngroup: expert", - "type": "number" } }, "required": [ @@ -979,10 +995,6 @@ "description": "To load a tiled geojson layer, set the zoomlevel of the tiles\n\nquestion: If using a tiled geojson, what is the zoomlevel of the tiles?\nifunset: This is not a tiled geojson", "type": "number" }, - "isOsmCache": { - "description": "Indicates that the upstream geojson data is OSM-derived.\nUseful for e.g. merging or for scripts generating this cache.\nThis also indicates that making changes on this data is possible\n\nquestion: Is this geojson a cache of OpenStreetMap data?\nifunset: This is not an OpenStreetMap cache\niftrue: this is based on OpenStreetMap and can thus be edited\ngroup: expert", - "type": "boolean" - }, "mercatorCrs": { "description": "Some API's use a mercator-projection (EPSG:900913) instead of WGS84. Set the flag `mercatorCrs: true` in the source for this\n\nquestion: Does this geojson use EPSG:900913 instead of WGS84 as projection?\niftrue: This geojson uses EPSG:900913 instead of WGS84\nifunset: This geojson uses WGS84 just like most geojson (default)", "type": "boolean" @@ -1026,6 +1038,10 @@ } ] }, + "isCounted": { + "description": "question: should this layer be included in the summary counts?\n\nThe layer server can give summary counts for a tile.\nThis should however be disabled for some layers, e.g. because there are too many features (walls_and_buildings) or because the count is irrelevant.\n\nifunset: Do count\niffalse: Do not include the counts\niftrue: Do include the count", + "type": "boolean" + }, "minzoom": { "description": "The minimum needed zoomlevel required to start loading and displaying the data.\nThis can be used to only show common features (e.g. a bicycle parking) only when the map is zoomed in very much (17).\nThis prevents cluttering the map with thousands of parkings if one is looking to an entire city.\n\nDefault: 0\ngroup: Basic\ntype: nat\nquestion: At what zoom level should features of the layer be shown?\nifunset: Always load this layer, even if the entire world is in view.", "type": "number" @@ -1223,8 +1239,11 @@ ] }, "popupInFloatover": { - "description": "Question: Should the information for this layer be shown in the sidebar or in a splash screen?\n\nIf set, open the selectedElementView in a floatOver instead of on the right.\n\niftrue: show the infobox in the splashscreen floating over the entire UI\niffalse: show the infobox in a sidebar on the right\ngroup: advanced\ndefault: sidebar", - "type": "boolean" + "description": "Question: Should the information for this layer be shown in the sidebar or in a splash screen?\n\nIf set, open the selectedElementView in a floatOver instead of on the right.\n\niftrue: show the infobox in the splashscreen floating over the entire UI; hide the title bar\niffalse: show the infobox in a sidebar on the right\nsuggestions: return [{if: \"value=title\", then: \"Show in a floatover and show the title bar\"}]\ngroup: advanced\ndefault: sidebar", + "type": [ + "string", + "boolean" + ] }, "titleIcons": { "description": "Small icons shown next to the title.\nIf not specified, the OsmLink and wikipedia links will be used by default.\nUse an empty array to hide them.\nNote that \"defaults\" will insert all the default titleIcons (which are added automatically)\n\nUse `auto:` to automatically create an icon based on a tagRendering which has icons\n\nType: icon[]\ngroup: infobox", @@ -1640,7 +1659,7 @@ } }, "filter": { - "description": "All the extra questions for filtering.\nIf a string is given, mapComplete will search in 'filters.json' for the appropriate filter or will try to parse it as `layername.filterid` and us that one\n\ngroup: filters", + "description": "All the extra questions for filtering.\nIf a string is given, mapComplete will search in\n1. The tagrenderings for a match on ID and use the mappings as options\n2. search 'filters.json' for the appropriate filter or\n3. will try to parse it as `layername.filterid` and us that one.\n\n\ngroup: filters", "anyOf": [ { "type": "array", @@ -1781,11 +1800,45 @@ "type": "boolean" }, "units": { + "description": "Either a list with [{\"key\": \"unitname\", \"key2\": {\"quantity\": \"unitname\", \"denominations\": [\"denom\", \"denom\"]}}]", "type": "array", "items": { "anyOf": [ { - "$ref": "#/definitions/default_2" + "description": "In some cases, a value is represented in a certain unit (such as meters for heigt/distance/..., km/h for speed, ...)\n\nSometimes, multiple denominations are possible (e.g. km/h vs mile/h; megawatt vs kilowatt vs gigawatt for power generators, ...)\n\nThis brings in some troubles, as there are multiple ways to write it (no denomitation, 'm' vs 'meter' 'metre', ...)\n\nNot only do we want to write consistent data to OSM, we also want to present this consistently to the user.\nThis is handled by defining units.\n\n# Rendering\n\nTo render a value with long (human) denomination, use {canonical(key)}\n\n# Usage\n\nFirst of all, you define which keys have units applied, for example:\n\n```\nunits: [\n appliesTo: [\"maxspeed\", \"maxspeed:hgv\", \"maxspeed:bus\"]\n applicableUnits: [\n ...\n ]\n]\n```\n\nApplicableUnits defines which is the canonical extension, how it is presented to the user, ...:\n\n```\napplicableUnits: [\n{\n canonicalDenomination: \"km/h\",\n alternativeDenomination: [\"km/u\", \"kmh\", \"kph\"]\n default: true,\n human: {\n en: \"kilometer/hour\",\n nl: \"kilometer/uur\"\n },\n humanShort: {\n en: \"km/h\",\n nl: \"km/u\"\n }\n},\n{\n canoncialDenomination: \"mph\",\n ... similar for miles an hour ...\n}\n]\n```\n\n\nIf this is defined, then every key which the denominations apply to (`maxspeed`, `maxspeed:hgv` and `maxspeed:bus`) will be rewritten at the metatagging stage:\nevery value will be parsed and the canonical extension will be added add presented to the other parts of the code.\n\nAlso, if a freeform text field is used, an extra dropdown with applicable denominations will be given", + "type": "object", + "properties": { + "quantity": { + "description": "What is quantified? E.g. 'speed', 'length' (including width, diameter, ...), 'electric tension', 'electric current', 'duration'", + "type": "string" + }, + "appliesToKey": { + "description": "Every key from this list will be normalized.\n\nTo render the value properly (with a human readable denomination), use `{canonical()}`", + "type": "array", + "items": { + "type": "string" + } + }, + "eraseInvalidValues": { + "description": "If set, invalid values will be erased in the MC application (but not in OSM of course!)\nBe careful with setting this", + "type": "boolean" + }, + "applicableUnits": { + "description": "The possible denominations for this unit.\nFor length, denominations could be \"meter\", \"kilometer\", \"miles\", \"foot\"", + "type": "array", + "items": { + "$ref": "#/definitions/DenominationConfigJson" + } + }, + "defaultInput": { + "description": "In some cases, the default denomination is not the most user friendly to input.\nE.g., when measuring kerb heights, it is illogical to ask contributors to input an amount in meters.\n\nWhen a default input method should be used, this can be specified by setting the canonical denomination here, e.g.\n`defaultInput: \"cm\"`. This must be a denomination which appears in the applicableUnits", + "type": "string" + } + }, + "required": [ + "applicableUnits" + ], + "additionalProperties": false }, { "$ref": "#/definitions/Record" @@ -1932,10 +1985,6 @@ "osmTags": { "$ref": "#/definitions/TagConfigJson", "description": "question: Which tags must be present on the feature to show it in this layer?\nEvery source must set which tags have to be present in order to load the given layer." - }, - "maxCacheAge": { - "description": "question: How long (in seconds) is the data allowed to remain cached until it must be refreshed?\nThe maximum amount of seconds that a tile is allowed to linger in the cache\n\ntype: nat\ndefault: 30 days\ngroup: expert", - "type": "number" } }, "required": [ @@ -1953,10 +2002,6 @@ "description": "To load a tiled geojson layer, set the zoomlevel of the tiles\n\nquestion: If using a tiled geojson, what is the zoomlevel of the tiles?\nifunset: This is not a tiled geojson", "type": "number" }, - "isOsmCache": { - "description": "Indicates that the upstream geojson data is OSM-derived.\nUseful for e.g. merging or for scripts generating this cache.\nThis also indicates that making changes on this data is possible\n\nquestion: Is this geojson a cache of OpenStreetMap data?\nifunset: This is not an OpenStreetMap cache\niftrue: this is based on OpenStreetMap and can thus be edited\ngroup: expert", - "type": "boolean" - }, "mercatorCrs": { "description": "Some API's use a mercator-projection (EPSG:900913) instead of WGS84. Set the flag `mercatorCrs: true` in the source for this\n\nquestion: Does this geojson use EPSG:900913 instead of WGS84 as projection?\niftrue: This geojson uses EPSG:900913 instead of WGS84\nifunset: This geojson uses WGS84 just like most geojson (default)", "type": "boolean" @@ -2015,22 +2060,6 @@ ], "description": "Every source must set which tags have to be present in order to load the given layer." }, - { - "path": [ - "layers", - "source", - "maxCacheAge" - ], - "required": false, - "hints": { - "typehint": "nat", - "group": "expert", - "default": "30 days", - "question": "How long (in seconds) is the data allowed to remain cached until it must be refreshed?" - }, - "type": "number", - "description": "The maximum amount of seconds that a tile is allowed to linger in the cache" - }, { "path": [ "layers", @@ -2059,22 +2088,6 @@ "type": "number", "description": "To load a tiled geojson layer, set the zoomlevel of the tiles" }, - { - "path": [ - "layers", - "source", - "isOsmCache" - ], - "required": false, - "hints": { - "group": "expert", - "question": "Is this geojson a cache of OpenStreetMap data?", - "iftrue": "this is based on OpenStreetMap and can thus be edited", - "ifunset": "This is not an OpenStreetMap cache" - }, - "type": "boolean", - "description": "Indicates that the upstream geojson data is OSM-derived.\nUseful for e.g. merging or for scripts generating this cache.\nThis also indicates that making changes on this data is possible" - }, { "path": [ "layers", @@ -2210,6 +2223,21 @@ ], "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" }, + { + "path": [ + "layers", + "isCounted" + ], + "required": false, + "hints": { + "question": "should this layer be included in the summary counts?", + "iftrue": "Do include the count", + "iffalse": "Do not include the counts", + "ifunset": "Do count" + }, + "type": "boolean", + "description": "The layer server can give summary counts for a tile.\nThis should however be disabled for some layers, e.g. because there are too many features (walls_and_buildings) or because the count is irrelevant." + }, { "path": [ "layers", @@ -2951,10 +2979,19 @@ "group": "advanced", "default": "sidebar", "question": "Should the information for this layer be shown in the sidebar or in a splash screen?", - "iftrue": "show the infobox in the splashscreen floating over the entire UI", - "iffalse": "show the infobox in a sidebar on the right" + "iftrue": "show the infobox in the splashscreen floating over the entire UI; hide the title bar", + "iffalse": "show the infobox in a sidebar on the right", + "suggestions": [ + { + "if": "value=title", + "then": "Show in a floatover and show the title bar" + } + ] }, - "type": "boolean", + "type": [ + "string", + "boolean" + ], "description": "If set, open the selectedElementView in a floatOver instead of on the right." }, { @@ -3558,7 +3595,12 @@ }, { "if": "value=projected_centerpoint", - "then": "Show an icon for line (way) object near the centroid location, but moved onto the line" + "then": "Show an icon for line (way) object near the centroid location, but moved onto the line. Does not show an item on polygons" + }, + null, + { + "if": "value=polygon_centroid", + "then": "Show an icon at a polygon centroid (but not if it is a way)" } ], "multianswer": "true" @@ -11611,7 +11653,7 @@ "ifunset": "Ways are rendered with a full line" }, "type": "string", - "description": "The dasharray defines 'pixels of line, pixels of gap, pixels of line, pixels of gap, ...'. For example, `5 6` will be 5 pixels of line followed by a 6 pixel gap.\nCannot be a dynamic property due to a mapbox limitation" + "description": "The dasharray defines 'pixels of line, pixels of gap, pixels of line, pixels of gap, ...'. For example, `5 6` will be 5 pixels of line followed by a 6 pixel gap.\nCannot be a dynamic property due to a MapLibre limitation (see https://github.com/maplibre/maplibre-gl-js/issues/1235)" }, { "path": [ @@ -12537,6 +12579,10 @@ "if": "value=information_board", "then": "information_board - A layer showing touristical, road side information boards (e.g. giving information about the landscape, a building, a feature, a map, …)" }, + { + "if": "value=item_with_image", + "then": "item_with_image - All items with an image. All alone, not a layer which is relevant for any MapComplete theme, as it is a random collection of items. However, when put into the databank, this allows to quickly fetch (the URL of) pictures nearby a different object, to quickly link this" + }, { "if": "value=kerbs", "then": "kerbs - A layer showing kerbs." @@ -12547,7 +12593,7 @@ }, { "if": "value=last_click", - "then": "last_click - This layer defines how to render the 'last click'-location. By default, it will show a marker with the possibility to add a new point (if there are some presets) and/or to add a new note (if the 'note' layer attribute is set). If none are possible, this layer won't show up" + "then": "last_click - This 'layer' is not really a layer, but contains part of the code how the popup to 'add a new marker' is displayed" }, { "if": "value=love_hotel", @@ -12637,6 +12683,10 @@ "if": "value=playground", "then": "playground - Playgrounds" }, + { + "if": "value=playground_equipment", + "then": "playground_equipment - Layer showing playground equipment" + }, { "if": "value=postboxes", "then": "postboxes - The layer showing postboxes." @@ -12705,6 +12755,14 @@ "if": "value=slow_roads", "then": "slow_roads - All carfree roads" }, + { + "if": "value=souvenir_coin", + "then": "souvenir_coin - Layer showing machines selling souvenir coins" + }, + { + "if": "value=souvenir_note", + "then": "souvenir_note - Layer showing machines selling souvenir banknotes" + }, { "if": "value=speed_camera", "then": "speed_camera - Layer showing speed cameras" @@ -12741,6 +12799,10 @@ "if": "value=stripclub", "then": "stripclub - A venue where erotic dance, striptease, or lap dances are performed commercially. " }, + { + "if": "value=summary", + "then": "summary - Special layer which shows `count`" + }, { "if": "value=surveillance_camera", "then": "surveillance_camera - This layer shows surveillance cameras and allows a contributor to update information and add new cameras" @@ -21304,7 +21366,7 @@ ] } ], - "description": "All the extra questions for filtering.\nIf a string is given, mapComplete will search in 'filters.json' for the appropriate filter or will try to parse it as `layername.filterid` and us that one" + "description": "All the extra questions for filtering.\nIf a string is given, mapComplete will search in\n1. The tagrenderings for a match on ID and use the mappings as options\n2. search 'filters.json' for the appropriate filter or\n3. will try to parse it as `layername.filterid` and us that one." }, { "path": [ @@ -21861,6 +21923,56 @@ "type": "boolean", "description": "If set, a 'split this way' button is shown on objects rendered as LineStrings, e.g. highways.\nIf the way is part of a relation, MapComplete will attempt to update this relation as well" }, + { + "path": [ + "layers", + "units" + ], + "required": false, + "hints": {}, + "type": [ + { + "description": "In some cases, a value is represented in a certain unit (such as meters for heigt/distance/..., km/h for speed, ...)\n\nSometimes, multiple denominations are possible (e.g. km/h vs mile/h; megawatt vs kilowatt vs gigawatt for power generators, ...)\n\nThis brings in some troubles, as there are multiple ways to write it (no denomitation, 'm' vs 'meter' 'metre', ...)\n\nNot only do we want to write consistent data to OSM, we also want to present this consistently to the user.\nThis is handled by defining units.\n\n# Rendering\n\nTo render a value with long (human) denomination, use {canonical(key)}\n\n# Usage\n\nFirst of all, you define which keys have units applied, for example:\n\n```\nunits: [\n appliesTo: [\"maxspeed\", \"maxspeed:hgv\", \"maxspeed:bus\"]\n applicableUnits: [\n ...\n ]\n]\n```\n\nApplicableUnits defines which is the canonical extension, how it is presented to the user, ...:\n\n```\napplicableUnits: [\n{\n canonicalDenomination: \"km/h\",\n alternativeDenomination: [\"km/u\", \"kmh\", \"kph\"]\n default: true,\n human: {\n en: \"kilometer/hour\",\n nl: \"kilometer/uur\"\n },\n humanShort: {\n en: \"km/h\",\n nl: \"km/u\"\n }\n},\n{\n canoncialDenomination: \"mph\",\n ... similar for miles an hour ...\n}\n]\n```\n\n\nIf this is defined, then every key which the denominations apply to (`maxspeed`, `maxspeed:hgv` and `maxspeed:bus`) will be rewritten at the metatagging stage:\nevery value will be parsed and the canonical extension will be added add presented to the other parts of the code.\n\nAlso, if a freeform text field is used, an extra dropdown with applicable denominations will be given", + "type": "object", + "properties": { + "quantity": { + "description": "What is quantified? E.g. 'speed', 'length' (including width, diameter, ...), 'electric tension', 'electric current', 'duration'", + "type": "string" + }, + "appliesToKey": { + "description": "Every key from this list will be normalized.\n\nTo render the value properly (with a human readable denomination), use `{canonical()}`", + "type": "array", + "items": { + "type": "string" + } + }, + "eraseInvalidValues": { + "description": "If set, invalid values will be erased in the MC application (but not in OSM of course!)\nBe careful with setting this", + "type": "boolean" + }, + "applicableUnits": { + "description": "The possible denominations for this unit.\nFor length, denominations could be \"meter\", \"kilometer\", \"miles\", \"foot\"", + "type": "array", + "items": { + "$ref": "#/definitions/DenominationConfigJson" + } + }, + "defaultInput": { + "description": "In some cases, the default denomination is not the most user friendly to input.\nE.g., when measuring kerb heights, it is illogical to ask contributors to input an amount in meters.\n\nWhen a default input method should be used, this can be specified by setting the canonical denomination here, e.g.\n`defaultInput: \"cm\"`. This must be a denomination which appears in the applicableUnits", + "type": "string" + } + }, + "required": [ + "applicableUnits" + ], + "additionalProperties": false + }, + { + "$ref": "#/definitions/Record" + } + ], + "description": "Either a list with [{\"key\": \"unitname\", \"key2\": {\"quantity\": \"unitname\", \"denominations\": [\"denom\", \"denom\"]}}]" + }, { "path": [ "layers", @@ -22170,10 +22282,6 @@ "osmTags": { "$ref": "#/definitions/TagConfigJson", "description": "question: Which tags must be present on the feature to show it in this layer?\nEvery source must set which tags have to be present in order to load the given layer." - }, - "maxCacheAge": { - "description": "question: How long (in seconds) is the data allowed to remain cached until it must be refreshed?\nThe maximum amount of seconds that a tile is allowed to linger in the cache\n\ntype: nat\ndefault: 30 days\ngroup: expert", - "type": "number" } }, "required": [ @@ -22191,10 +22299,6 @@ "description": "To load a tiled geojson layer, set the zoomlevel of the tiles\n\nquestion: If using a tiled geojson, what is the zoomlevel of the tiles?\nifunset: This is not a tiled geojson", "type": "number" }, - "isOsmCache": { - "description": "Indicates that the upstream geojson data is OSM-derived.\nUseful for e.g. merging or for scripts generating this cache.\nThis also indicates that making changes on this data is possible\n\nquestion: Is this geojson a cache of OpenStreetMap data?\nifunset: This is not an OpenStreetMap cache\niftrue: this is based on OpenStreetMap and can thus be edited\ngroup: expert", - "type": "boolean" - }, "mercatorCrs": { "description": "Some API's use a mercator-projection (EPSG:900913) instead of WGS84. Set the flag `mercatorCrs: true` in the source for this\n\nquestion: Does this geojson use EPSG:900913 instead of WGS84 as projection?\niftrue: This geojson uses EPSG:900913 instead of WGS84\nifunset: This geojson uses WGS84 just like most geojson (default)", "type": "boolean" @@ -22254,23 +22358,6 @@ ], "description": "Every source must set which tags have to be present in order to load the given layer." }, - { - "path": [ - "layers", - "override", - "source", - "maxCacheAge" - ], - "required": false, - "hints": { - "typehint": "nat", - "group": "expert", - "default": "30 days", - "question": "How long (in seconds) is the data allowed to remain cached until it must be refreshed?" - }, - "type": "number", - "description": "The maximum amount of seconds that a tile is allowed to linger in the cache" - }, { "path": [ "layers", @@ -22301,23 +22388,6 @@ "type": "number", "description": "To load a tiled geojson layer, set the zoomlevel of the tiles" }, - { - "path": [ - "layers", - "override", - "source", - "isOsmCache" - ], - "required": false, - "hints": { - "group": "expert", - "question": "Is this geojson a cache of OpenStreetMap data?", - "iftrue": "this is based on OpenStreetMap and can thus be edited", - "ifunset": "This is not an OpenStreetMap cache" - }, - "type": "boolean", - "description": "Indicates that the upstream geojson data is OSM-derived.\nUseful for e.g. merging or for scripts generating this cache.\nThis also indicates that making changes on this data is possible" - }, { "path": [ "layers", @@ -22459,6 +22529,22 @@ ], "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" }, + { + "path": [ + "layers", + "override", + "isCounted" + ], + "required": false, + "hints": { + "question": "should this layer be included in the summary counts?", + "iftrue": "Do include the count", + "iffalse": "Do not include the counts", + "ifunset": "Do count" + }, + "type": "boolean", + "description": "The layer server can give summary counts for a tile.\nThis should however be disabled for some layers, e.g. because there are too many features (walls_and_buildings) or because the count is irrelevant." + }, { "path": [ "layers", @@ -23225,10 +23311,19 @@ "group": "advanced", "default": "sidebar", "question": "Should the information for this layer be shown in the sidebar or in a splash screen?", - "iftrue": "show the infobox in the splashscreen floating over the entire UI", - "iffalse": "show the infobox in a sidebar on the right" + "iftrue": "show the infobox in the splashscreen floating over the entire UI; hide the title bar", + "iffalse": "show the infobox in a sidebar on the right", + "suggestions": [ + { + "if": "value=title", + "then": "Show in a floatover and show the title bar" + } + ] }, - "type": "boolean", + "type": [ + "string", + "boolean" + ], "description": "If set, open the selectedElementView in a floatOver instead of on the right." }, { @@ -23857,7 +23952,12 @@ }, { "if": "value=projected_centerpoint", - "then": "Show an icon for line (way) object near the centroid location, but moved onto the line" + "then": "Show an icon for line (way) object near the centroid location, but moved onto the line. Does not show an item on polygons" + }, + null, + { + "if": "value=polygon_centroid", + "then": "Show an icon at a polygon centroid (but not if it is a way)" } ], "multianswer": "true" @@ -32151,7 +32251,7 @@ "ifunset": "Ways are rendered with a full line" }, "type": "string", - "description": "The dasharray defines 'pixels of line, pixels of gap, pixels of line, pixels of gap, ...'. For example, `5 6` will be 5 pixels of line followed by a 6 pixel gap.\nCannot be a dynamic property due to a mapbox limitation" + "description": "The dasharray defines 'pixels of line, pixels of gap, pixels of line, pixels of gap, ...'. For example, `5 6` will be 5 pixels of line followed by a 6 pixel gap.\nCannot be a dynamic property due to a MapLibre limitation (see https://github.com/maplibre/maplibre-gl-js/issues/1235)" }, { "path": [ @@ -33103,6 +33203,10 @@ "if": "value=information_board", "then": "information_board - A layer showing touristical, road side information boards (e.g. giving information about the landscape, a building, a feature, a map, …)" }, + { + "if": "value=item_with_image", + "then": "item_with_image - All items with an image. All alone, not a layer which is relevant for any MapComplete theme, as it is a random collection of items. However, when put into the databank, this allows to quickly fetch (the URL of) pictures nearby a different object, to quickly link this" + }, { "if": "value=kerbs", "then": "kerbs - A layer showing kerbs." @@ -33113,7 +33217,7 @@ }, { "if": "value=last_click", - "then": "last_click - This layer defines how to render the 'last click'-location. By default, it will show a marker with the possibility to add a new point (if there are some presets) and/or to add a new note (if the 'note' layer attribute is set). If none are possible, this layer won't show up" + "then": "last_click - This 'layer' is not really a layer, but contains part of the code how the popup to 'add a new marker' is displayed" }, { "if": "value=love_hotel", @@ -33203,6 +33307,10 @@ "if": "value=playground", "then": "playground - Playgrounds" }, + { + "if": "value=playground_equipment", + "then": "playground_equipment - Layer showing playground equipment" + }, { "if": "value=postboxes", "then": "postboxes - The layer showing postboxes." @@ -33271,6 +33379,14 @@ "if": "value=slow_roads", "then": "slow_roads - All carfree roads" }, + { + "if": "value=souvenir_coin", + "then": "souvenir_coin - Layer showing machines selling souvenir coins" + }, + { + "if": "value=souvenir_note", + "then": "souvenir_note - Layer showing machines selling souvenir banknotes" + }, { "if": "value=speed_camera", "then": "speed_camera - Layer showing speed cameras" @@ -33307,6 +33423,10 @@ "if": "value=stripclub", "then": "stripclub - A venue where erotic dance, striptease, or lap dances are performed commercially. " }, + { + "if": "value=summary", + "then": "summary - Special layer which shows `count`" + }, { "if": "value=surveillance_camera", "then": "surveillance_camera - This layer shows surveillance cameras and allows a contributor to update information and add new cameras" @@ -42168,7 +42288,7 @@ ] } ], - "description": "All the extra questions for filtering.\nIf a string is given, mapComplete will search in 'filters.json' for the appropriate filter or will try to parse it as `layername.filterid` and us that one" + "description": "All the extra questions for filtering.\nIf a string is given, mapComplete will search in\n1. The tagrenderings for a match on ID and use the mappings as options\n2. search 'filters.json' for the appropriate filter or\n3. will try to parse it as `layername.filterid` and us that one." }, { "path": [ @@ -42748,6 +42868,57 @@ "type": "boolean", "description": "If set, a 'split this way' button is shown on objects rendered as LineStrings, e.g. highways.\nIf the way is part of a relation, MapComplete will attempt to update this relation as well" }, + { + "path": [ + "layers", + "override", + "units" + ], + "required": false, + "hints": {}, + "type": [ + { + "description": "In some cases, a value is represented in a certain unit (such as meters for heigt/distance/..., km/h for speed, ...)\n\nSometimes, multiple denominations are possible (e.g. km/h vs mile/h; megawatt vs kilowatt vs gigawatt for power generators, ...)\n\nThis brings in some troubles, as there are multiple ways to write it (no denomitation, 'm' vs 'meter' 'metre', ...)\n\nNot only do we want to write consistent data to OSM, we also want to present this consistently to the user.\nThis is handled by defining units.\n\n# Rendering\n\nTo render a value with long (human) denomination, use {canonical(key)}\n\n# Usage\n\nFirst of all, you define which keys have units applied, for example:\n\n```\nunits: [\n appliesTo: [\"maxspeed\", \"maxspeed:hgv\", \"maxspeed:bus\"]\n applicableUnits: [\n ...\n ]\n]\n```\n\nApplicableUnits defines which is the canonical extension, how it is presented to the user, ...:\n\n```\napplicableUnits: [\n{\n canonicalDenomination: \"km/h\",\n alternativeDenomination: [\"km/u\", \"kmh\", \"kph\"]\n default: true,\n human: {\n en: \"kilometer/hour\",\n nl: \"kilometer/uur\"\n },\n humanShort: {\n en: \"km/h\",\n nl: \"km/u\"\n }\n},\n{\n canoncialDenomination: \"mph\",\n ... similar for miles an hour ...\n}\n]\n```\n\n\nIf this is defined, then every key which the denominations apply to (`maxspeed`, `maxspeed:hgv` and `maxspeed:bus`) will be rewritten at the metatagging stage:\nevery value will be parsed and the canonical extension will be added add presented to the other parts of the code.\n\nAlso, if a freeform text field is used, an extra dropdown with applicable denominations will be given", + "type": "object", + "properties": { + "quantity": { + "description": "What is quantified? E.g. 'speed', 'length' (including width, diameter, ...), 'electric tension', 'electric current', 'duration'", + "type": "string" + }, + "appliesToKey": { + "description": "Every key from this list will be normalized.\n\nTo render the value properly (with a human readable denomination), use `{canonical()}`", + "type": "array", + "items": { + "type": "string" + } + }, + "eraseInvalidValues": { + "description": "If set, invalid values will be erased in the MC application (but not in OSM of course!)\nBe careful with setting this", + "type": "boolean" + }, + "applicableUnits": { + "description": "The possible denominations for this unit.\nFor length, denominations could be \"meter\", \"kilometer\", \"miles\", \"foot\"", + "type": "array", + "items": { + "$ref": "#/definitions/DenominationConfigJson" + } + }, + "defaultInput": { + "description": "In some cases, the default denomination is not the most user friendly to input.\nE.g., when measuring kerb heights, it is illogical to ask contributors to input an amount in meters.\n\nWhen a default input method should be used, this can be specified by setting the canonical denomination here, e.g.\n`defaultInput: \"cm\"`. This must be a denomination which appears in the applicableUnits", + "type": "string" + } + }, + "required": [ + "applicableUnits" + ], + "additionalProperties": false + }, + { + "$ref": "#/definitions/Record" + } + ], + "description": "Either a list with [{\"key\": \"unitname\", \"key2\": {\"quantity\": \"unitname\", \"denominations\": [\"denom\", \"denom\"]}}]" + }, { "path": [ "layers", @@ -43022,6 +43193,15 @@ "type": "array", "description": "Define some (overlay) slippy map tilesources" }, + { + "path": [ + "tileLayerSources" + ], + "required": false, + "hints": {}, + "type": "object", + "description": "This class has grown beyond the point of only containing Raster Layers" + }, { "path": [ "tileLayerSources", @@ -43291,10 +43471,11 @@ "group": "advanced", "question": "Should the map use elevation data to give a 3D-feel?", "iftrue": "Use elevation and render 3D", - "iffalse": "Do not use terrain" + "iffalse": "Do not use terrain", + "ifunset": "MapComplete default: don't use terrain" }, "type": "boolean", - "description": "This is especially useful for hiking maps, skiing maps etc...\nfunset: MapComplete default: don't use terrain" + "description": "This is especially useful for hiking maps, skiing maps etc..." }, { "path": [ @@ -43323,15 +43504,6 @@ "type": "number", "description": "If a query takes too long, the overpass-server will abort.\nOnce can set the amount of time before overpass gives up here." }, - { - "path": [ - "widenFactor" - ], - "required": false, - "hints": {}, - "type": "number", - "description": "When a query is run, the data within bounds of the visible map is loaded.\nHowever, users tend to pan and zoom a lot. It is pretty annoying if every single pan means a reloading of the data.\nFor this, the bounds are widened in order to make a small pan still within bounds of the loaded data.\nIF widenfactor is 1, this feature is disabled. A recommended value is between 1 and 3" - }, { "path": [ "overpassMaxZoom" diff --git a/src/index.ts b/src/index.ts index e63c66d01..3a9084c2e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -61,7 +61,7 @@ async function main() { console.error("Error while initializing: ", err, err.stack) const customDefinition = DetermineLayout.getCustomDefinition() new Combine([ - new FixedUiElement(err).SetClass("block alert"), + new FixedUiElement(err.toString().split("\n").join("
")).SetClass("block alert"), customDefinition?.length > 0 ? new SubtleButton(new SvelteUIElement(Download), "Download the raw file").onClick( diff --git a/studio.html b/studio.html index 2e5da87d3..9041b50bb 100644 --- a/studio.html +++ b/studio.html @@ -12,7 +12,7 @@ -
Initing studio...
+
Loading studio...