Studio: studio now supports loading self-made layers in themes
This commit is contained in:
parent
9716bc5425
commit
28bf8cca9f
24 changed files with 826 additions and 464 deletions
|
@ -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:<tagrenderingId>` 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": {
|
||||
|
|
|
@ -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:<tagrenderingId>` 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": {
|
||||
|
|
|
@ -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: \"<b>\"+key+\"</b> (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:<tagrenderingId>` 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:<tagrenderingId>` 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": {
|
||||
|
|
|
@ -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: \"<b>\"+key+\"</b> (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:<tagrenderingId>` 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:<tagrenderingId>` 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": {
|
||||
|
|
|
@ -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": {
|
||||
|
|
|
@ -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": {
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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<LayoutConfigJson> {
|
||||
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 = <LayerConfigJson>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<string>): LayoutConfig | null {
|
||||
public static async LoadLayoutFromHash(userLayoutParam: UIEventSource<string>): Promise<LayoutConfig | null> {
|
||||
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: "<div class='alert'>Layer only mode.</div> The loaded custom theme actually isn't a custom theme, but only contains a layer.",
|
||||
en: "<div class='alert'>Layer only mode.</div> 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<string>(),
|
||||
publicLayers: new Set<string>()
|
||||
}
|
||||
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 = <LayoutConfigJson>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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -834,6 +834,10 @@ export class UIEventSource<T> extends Store<T> implements Writable<T> {
|
|||
)
|
||||
}
|
||||
|
||||
public mapAsyncD<J>(f: (t: T) => Promise<J>): Store<J> {
|
||||
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<T> extends Store<T> implements Writable<T> {
|
|||
update(f: Updater<T> & ((value: T) => T)): void {
|
||||
this.setData(f(this.data))
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -39,7 +39,7 @@ export abstract class Conversion<TIn, TOut> {
|
|||
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
|
||||
}
|
||||
|
|
|
@ -186,7 +186,7 @@ class AddDefaultLayers extends DesugaringStep<LayoutConfigJson> {
|
|||
|
||||
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) {
|
||||
|
|
|
@ -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: "<b>"+key+"</b> (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.
|
||||
*
|
||||
|
|
|
@ -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) =>
|
||||
|
|
|
@ -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<T> {
|
|||
this.category = category
|
||||
this.expertMode = options?.expertMode ?? new UIEventSource<boolean>(false)
|
||||
|
||||
this.messages = this.setupErrorsForLayers()
|
||||
|
||||
const layerId = this.getId()
|
||||
this.configuration
|
||||
|
@ -84,6 +83,8 @@ export abstract class EditJsonState<T> {
|
|||
}
|
||||
await this.server.update(id, config, this.category)
|
||||
})
|
||||
this.messages = this.createMessagesStore()
|
||||
|
||||
}
|
||||
|
||||
public startSavingUpdates(enabled = true) {
|
||||
|
@ -152,10 +153,10 @@ export abstract class EditJsonState<T> {
|
|||
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<T> {
|
|||
|
||||
protected abstract getId(): Store<string>
|
||||
|
||||
private setupErrorsForLayers(): Store<ConversionMessage[]> {
|
||||
const layers = AllSharedLayers.getSharedLayersConfigs()
|
||||
const questions = layers.get("questions")
|
||||
const sharedQuestions = new Map<string, QuestionableTagRenderingConfigJson>()
|
||||
for (const question of questions.tagRenderings) {
|
||||
sharedQuestions.set(question["id"], <QuestionableTagRenderingConfigJson>question)
|
||||
protected abstract validate(configuration: Partial<T>): Promise<ConversionMessage[]>;
|
||||
|
||||
/**
|
||||
* Creates a store that validates the configuration and which contains all relevant (error)-messages
|
||||
* @private
|
||||
*/
|
||||
private createMessagesStore(): Store<ConversionMessage[]> {
|
||||
return this.configuration.mapAsyncD(async (config) => {
|
||||
if(!this.validate){
|
||||
return []
|
||||
}
|
||||
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(<T>config, context)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
context.err(e)
|
||||
}
|
||||
return context.messages
|
||||
})
|
||||
return await this.validate(config)
|
||||
}).map(messages => messages ?? [])
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -314,7 +306,7 @@ export default class EditLayerState extends EditJsonState<LayerConfigJson> {
|
|||
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<LayerConfigJson> {
|
|||
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<LayerConfigJson> {
|
|||
super(schema, server, "layers", options)
|
||||
this.osmConnection = osmConnection
|
||||
this.layout = {
|
||||
getMatchingLayer: (_) => {
|
||||
getMatchingLayer: () => {
|
||||
try {
|
||||
return new LayerConfig(<LayerConfigJson>this.configuration.data, "dynamic")
|
||||
} catch (e) {
|
||||
return undefined
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
this.featureSwitches = {
|
||||
featureSwitchIsDebugging: new UIEventSource<boolean>(true),
|
||||
featureSwitchIsDebugging: new UIEventSource<boolean>(true)
|
||||
}
|
||||
|
||||
this.addMissingTagRenderingIds()
|
||||
|
@ -428,6 +420,30 @@ export default class EditLayerState extends EditJsonState<LayerConfigJson> {
|
|||
}
|
||||
})
|
||||
}
|
||||
|
||||
protected async validate(configuration: Partial<LayerConfigJson>): Promise<ConversionMessage[]> {
|
||||
|
||||
const layers = AllSharedLayers.getSharedLayersConfigs()
|
||||
|
||||
const questions = layers.get("questions")
|
||||
const sharedQuestions = new Map<string, QuestionableTagRenderingConfigJson>()
|
||||
for (const question of questions.tagRenderings) {
|
||||
sharedQuestions.set(question["id"], <QuestionableTagRenderingConfigJson>question)
|
||||
}
|
||||
const state: DesugaringContext = {
|
||||
tagRenderings: sharedQuestions,
|
||||
sharedLayers: layers
|
||||
}
|
||||
const prepare = this.buildValidation(state)
|
||||
const context = ConversionContext.construct([], ["prepare"])
|
||||
try {
|
||||
prepare.convert(<LayerConfigJson>configuration, context)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
context.err(e)
|
||||
}
|
||||
return context.messages
|
||||
}
|
||||
}
|
||||
|
||||
export class EditThemeState extends EditJsonState<LayoutConfigJson> {
|
||||
|
@ -437,6 +453,7 @@ export class EditThemeState extends EditJsonState<LayoutConfigJson> {
|
|||
options: { expertMode: UIEventSource<boolean> }
|
||||
) {
|
||||
super(schema, server, "themes", options)
|
||||
this.setupFixers()
|
||||
}
|
||||
|
||||
protected buildValidation(state: DesugaringContext): Conversion<LayoutConfigJson, any> {
|
||||
|
@ -449,4 +466,55 @@ export class EditThemeState extends EditJsonState<LayoutConfigJson> {
|
|||
protected getId(): Store<string> {
|
||||
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<LayoutConfigJson>) {
|
||||
|
||||
const layers = AllSharedLayers.getSharedLayersConfigs()
|
||||
|
||||
for (const l of configuration.layers ?? []) {
|
||||
if(typeof l !== "string"){
|
||||
continue
|
||||
}
|
||||
if (!l.startsWith("https://")) {
|
||||
continue
|
||||
}
|
||||
const config = <LayerConfigJson> await Utils.downloadJsonCached(l, 1000*60*10)
|
||||
layers.set(l, config)
|
||||
}
|
||||
|
||||
const questions = layers.get("questions")
|
||||
const sharedQuestions = new Map<string, QuestionableTagRenderingConfigJson>()
|
||||
for (const question of questions.tagRenderings) {
|
||||
sharedQuestions.set(question["id"], <QuestionableTagRenderingConfigJson>question)
|
||||
}
|
||||
const state: DesugaringContext = {
|
||||
tagRenderings: sharedQuestions,
|
||||
sharedLayers: layers
|
||||
}
|
||||
const prepare = this.buildValidation(state)
|
||||
const context = ConversionContext.construct([], ["prepare"])
|
||||
try {
|
||||
prepare.convert(<LayoutConfigJson>configuration, context)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
context.err(e)
|
||||
}
|
||||
return context.messages
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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: `<b>${l.id}</b> (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: `<b>${l.id}</b> (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 = `<b>${l.id}</b> (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
|
||||
|
|
|
@ -64,16 +64,6 @@
|
|||
}
|
||||
}
|
||||
newPath.push(...toAdd)
|
||||
console.log(
|
||||
"Fused path ",
|
||||
path.join("."),
|
||||
"+",
|
||||
i,
|
||||
"+",
|
||||
subpartPath.join("."),
|
||||
"into",
|
||||
newPath.join(".")
|
||||
)
|
||||
return newPath
|
||||
}
|
||||
|
||||
|
|
|
@ -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<number>
|
||||
|
@ -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<LayerConfigJson | LayoutConfigJson> {
|
||||
try {
|
||||
return await Utils.downloadJson(this.urlFor(layerId, category, uid))
|
||||
return <any> await Utils.downloadJson(this.urlFor(layerId, category, uid))
|
||||
} catch (e) {
|
||||
return undefined
|
||||
}
|
||||
|
|
|
@ -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 @@
|
|||
</BackButton>
|
||||
</EditLayer>
|
||||
{:else if state === "editing_theme"}
|
||||
<EditTheme state={editThemeState}>
|
||||
<EditTheme state={editThemeState} selfLayers={$selfLayers} otherLayers={$otherLayers} {osmConnection}>
|
||||
<BackButton
|
||||
clss="small p-1"
|
||||
imageClass="w-8 h-8"
|
||||
|
|
|
@ -77,10 +77,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": [
|
||||
|
@ -98,10 +94,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"
|
||||
|
@ -159,21 +151,6 @@
|
|||
],
|
||||
"description": "Every source must set which tags have to be present in order to load the given layer."
|
||||
},
|
||||
{
|
||||
"path": [
|
||||
"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": [
|
||||
"source",
|
||||
|
@ -200,21 +177,6 @@
|
|||
"type": "number",
|
||||
"description": "To load a tiled geojson layer, set the zoomlevel of the tiles"
|
||||
},
|
||||
{
|
||||
"path": [
|
||||
"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": [
|
||||
"source",
|
||||
|
@ -344,6 +306,20 @@
|
|||
],
|
||||
"description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation"
|
||||
},
|
||||
{
|
||||
"path": [
|
||||
"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": [
|
||||
"minzoom"
|
||||
|
@ -1060,10 +1036,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."
|
||||
},
|
||||
{
|
||||
|
@ -1642,7 +1627,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"
|
||||
|
@ -9454,7 +9444,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": [
|
||||
|
@ -10354,6 +10344,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."
|
||||
|
@ -10364,7 +10358,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",
|
||||
|
@ -10454,6 +10448,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."
|
||||
|
@ -10522,6 +10520,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"
|
||||
|
@ -10558,6 +10564,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"
|
||||
|
@ -18823,7 +18833,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": [
|
||||
|
@ -19357,6 +19367,55 @@
|
|||
"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": [
|
||||
"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(<key>)}`",
|
||||
"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<string,string|{quantity:string;denominations:string[];canonical?:string|undefined;}>"
|
||||
}
|
||||
],
|
||||
"description": "Either a list with [{\"key\": \"unitname\", \"key2\": {\"quantity\": \"unitname\", \"denominations\": [\"denom\", \"denom\"]}}]"
|
||||
},
|
||||
{
|
||||
"path": [
|
||||
"units",
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -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("<br/>")).SetClass("block alert"),
|
||||
|
||||
customDefinition?.length > 0
|
||||
? new SubtleButton(new SvelteUIElement(Download), "Download the raw file").onClick(
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
<link href="./css/wikipedia.css" rel="stylesheet"/>
|
||||
</head>
|
||||
<body>
|
||||
<div id="main" class="h-full">Initing studio...</div>
|
||||
<div id="main" class="h-full">Loading studio...</div>
|
||||
<script src="./src/UI/StudioGui.ts" type="module"></script>
|
||||
<script async data-goatcounter="https://pietervdvn.goatcounter.com/count" src="https://gc.zgo.at/count.js" crossorigin="anonymous" integrity="sha384-nx5O+otcqJoqMhdDt8jUzmia6ng81Z5zZozYr69TzPkOLjVhLKMxu5zHCV9/0MPn"></script>
|
||||
|
||||
|
|
Loading…
Reference in a new issue