diff --git a/Docs/Schemas/LayerConfigJson.schema.json b/Docs/Schemas/LayerConfigJson.schema.json index 52f9f1e69..72a8a69e0 100644 --- a/Docs/Schemas/LayerConfigJson.schema.json +++ b/Docs/Schemas/LayerConfigJson.schema.json @@ -426,6 +426,16 @@ "items": { "$ref": "#/definitions/default_2" } + }, + "syncSelection": { + "description": "If set, synchronizes wether or not this layer is selected.\n\nno: Do not sync at all, always revert to default\nlocal: keep selection on local storage\ntheme-only: sync via OSM, but this layer will only be toggled in this theme\nglobal: all layers with this ID will be synced accross all themes", + "enum": [ + "global", + "local", + "no", + "theme-only" + ], + "type": "string" } }, "required": [ @@ -602,11 +612,32 @@ ] }, "then": { - "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\ntype: rendered" + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" }, "icon": { "description": "An icon supporting this mapping; typically shown pretty small\nType: icon", - "type": "string" + "anyOf": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "class", + "path" + ] + }, + { + "type": "string" + } + ] }, "hideInAnswer": { "description": "In some cases, multiple taggings exist (e.g. a default assumption, or a commonly mapped abbreviation and a fully written variation).\n\nIn the latter case, a correct text should be shown, but only a single, canonical tagging should be selectable by the user.\nIn this case, one of the mappings can be hiden by setting this flag.\n\nTo demonstrate an example making a default assumption:\n\nmappings: [\n {\n if: \"access=\", -- no access tag present, we assume accessible\n then: \"Accessible to the general public\",\n hideInAnswer: true\n },\n {\n if: \"access=yes\",\n then: \"Accessible to the general public\", -- the user selected this, we add that to OSM\n },\n {\n if: \"access=no\",\n then: \"Not accessible to the public\"\n }\n]\n\n\nFor example, for an operator, we have `operator=Agentschap Natuur en Bos`, which is often abbreviated to `operator=ANB`.\nThen, we would add two mappings:\n{\n if: \"operator=Agentschap Natuur en Bos\" -- the non-abbreviated version which should be uploaded\n then: \"Maintained by Agentschap Natuur en Bos\"\n},\n{\n if: \"operator=ANB\", -- we don't want to upload abbreviations\n then: \"Maintained by Agentschap Natuur en Bos\"\n hideInAnswer: true\n}\n\nHide in answer can also be a tagsfilter, e.g. to make sure an option is only shown when appropriate.\nKeep in mind that this is reverse logic: it will be hidden in the answer if the condition is true, it will thus only show in the case of a mismatch\n\ne.g., for toilets: if \"wheelchair=no\", we know there is no wheelchair dedicated room.\nFor the location of the changing table, the option \"in the wheelchair accessible toilet is weird\", so we write:\n\n{\n \"question\": \"Where is the changing table located?\"\n \"mappings\": [\n {\"if\":\"changing_table:location=female\",\"then\":\"In the female restroom\"},\n {\"if\":\"changing_table:location=male\",\"then\":\"In the male restroom\"},\n {\"if\":\"changing_table:location=wheelchair\",\"then\":\"In the wheelchair accessible restroom\", \"hideInAnswer\": \"wheelchair=no\"},\n \n ]\n}\n\nAlso have a look for the meta-tags\n{\n if: \"operator=Agentschap Natuur en Bos\",\n then: \"Maintained by Agentschap Natuur en Bos\",\n hideInAnswer: \"_country!=be\"\n}", diff --git a/Docs/Schemas/LayerConfigJsonJSC.ts b/Docs/Schemas/LayerConfigJsonJSC.ts index 35dc011ac..481e12736 100644 --- a/Docs/Schemas/LayerConfigJsonJSC.ts +++ b/Docs/Schemas/LayerConfigJsonJSC.ts @@ -426,6 +426,16 @@ export default { "items": { "$ref": "#/definitions/default_2" } + }, + "syncSelection": { + "description": "If set, synchronizes wether or not this layer is selected.\n\nno: Do not sync at all, always revert to default\nlocal: keep selection on local storage\ntheme-only: sync via OSM, but this layer will only be toggled in this theme\nglobal: all layers with this ID will be synced accross all themes", + "enum": [ + "global", + "local", + "no", + "theme-only" + ], + "type": "string" } }, "required": [ @@ -600,11 +610,32 @@ export default { ] }, "then": { - "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\ntype: rendered" + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" }, "icon": { "description": "An icon supporting this mapping; typically shown pretty small\nType: icon", - "type": "string" + "anyOf": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "class", + "path" + ] + }, + { + "type": "string" + } + ] }, "hideInAnswer": { "description": "In some cases, multiple taggings exist (e.g. a default assumption, or a commonly mapped abbreviation and a fully written variation).\n\nIn the latter case, a correct text should be shown, but only a single, canonical tagging should be selectable by the user.\nIn this case, one of the mappings can be hiden by setting this flag.\n\nTo demonstrate an example making a default assumption:\n\nmappings: [\n {\n if: \"access=\", -- no access tag present, we assume accessible\n then: \"Accessible to the general public\",\n hideInAnswer: true\n },\n {\n if: \"access=yes\",\n then: \"Accessible to the general public\", -- the user selected this, we add that to OSM\n },\n {\n if: \"access=no\",\n then: \"Not accessible to the public\"\n }\n]\n\n\nFor example, for an operator, we have `operator=Agentschap Natuur en Bos`, which is often abbreviated to `operator=ANB`.\nThen, we would add two mappings:\n{\n if: \"operator=Agentschap Natuur en Bos\" -- the non-abbreviated version which should be uploaded\n then: \"Maintained by Agentschap Natuur en Bos\"\n},\n{\n if: \"operator=ANB\", -- we don't want to upload abbreviations\n then: \"Maintained by Agentschap Natuur en Bos\"\n hideInAnswer: true\n}\n\nHide in answer can also be a tagsfilter, e.g. to make sure an option is only shown when appropriate.\nKeep in mind that this is reverse logic: it will be hidden in the answer if the condition is true, it will thus only show in the case of a mismatch\n\ne.g., for toilets: if \"wheelchair=no\", we know there is no wheelchair dedicated room.\nFor the location of the changing table, the option \"in the wheelchair accessible toilet is weird\", so we write:\n\n{\n \"question\": \"Where is the changing table located?\"\n \"mappings\": [\n {\"if\":\"changing_table:location=female\",\"then\":\"In the female restroom\"},\n {\"if\":\"changing_table:location=male\",\"then\":\"In the male restroom\"},\n {\"if\":\"changing_table:location=wheelchair\",\"then\":\"In the wheelchair accessible restroom\", \"hideInAnswer\": \"wheelchair=no\"},\n \n ]\n}\n\nAlso have a look for the meta-tags\n{\n if: \"operator=Agentschap Natuur en Bos\",\n then: \"Maintained by Agentschap Natuur en Bos\",\n hideInAnswer: \"_country!=be\"\n}", diff --git a/Docs/Schemas/LayoutConfigJson.schema.json b/Docs/Schemas/LayoutConfigJson.schema.json index dcfda9918..362a59e45 100644 --- a/Docs/Schemas/LayoutConfigJson.schema.json +++ b/Docs/Schemas/LayoutConfigJson.schema.json @@ -456,11 +456,32 @@ ] }, "then": { - "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\ntype: rendered" + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" }, "icon": { "description": "An icon supporting this mapping; typically shown pretty small\nType: icon", - "type": "string" + "anyOf": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "class", + "path" + ] + }, + { + "type": "string" + } + ] }, "hideInAnswer": { "description": "In some cases, multiple taggings exist (e.g. a default assumption, or a commonly mapped abbreviation and a fully written variation).\n\nIn the latter case, a correct text should be shown, but only a single, canonical tagging should be selectable by the user.\nIn this case, one of the mappings can be hiden by setting this flag.\n\nTo demonstrate an example making a default assumption:\n\nmappings: [\n {\n if: \"access=\", -- no access tag present, we assume accessible\n then: \"Accessible to the general public\",\n hideInAnswer: true\n },\n {\n if: \"access=yes\",\n then: \"Accessible to the general public\", -- the user selected this, we add that to OSM\n },\n {\n if: \"access=no\",\n then: \"Not accessible to the public\"\n }\n]\n\n\nFor example, for an operator, we have `operator=Agentschap Natuur en Bos`, which is often abbreviated to `operator=ANB`.\nThen, we would add two mappings:\n{\n if: \"operator=Agentschap Natuur en Bos\" -- the non-abbreviated version which should be uploaded\n then: \"Maintained by Agentschap Natuur en Bos\"\n},\n{\n if: \"operator=ANB\", -- we don't want to upload abbreviations\n then: \"Maintained by Agentschap Natuur en Bos\"\n hideInAnswer: true\n}\n\nHide in answer can also be a tagsfilter, e.g. to make sure an option is only shown when appropriate.\nKeep in mind that this is reverse logic: it will be hidden in the answer if the condition is true, it will thus only show in the case of a mismatch\n\ne.g., for toilets: if \"wheelchair=no\", we know there is no wheelchair dedicated room.\nFor the location of the changing table, the option \"in the wheelchair accessible toilet is weird\", so we write:\n\n{\n \"question\": \"Where is the changing table located?\"\n \"mappings\": [\n {\"if\":\"changing_table:location=female\",\"then\":\"In the female restroom\"},\n {\"if\":\"changing_table:location=male\",\"then\":\"In the male restroom\"},\n {\"if\":\"changing_table:location=wheelchair\",\"then\":\"In the wheelchair accessible restroom\", \"hideInAnswer\": \"wheelchair=no\"},\n \n ]\n}\n\nAlso have a look for the meta-tags\n{\n if: \"operator=Agentschap Natuur en Bos\",\n then: \"Maintained by Agentschap Natuur en Bos\",\n hideInAnswer: \"_country!=be\"\n}", @@ -1314,6 +1335,16 @@ "items": { "$ref": "#/definitions/default_2" } + }, + "syncSelection": { + "description": "If set, synchronizes wether or not this layer is selected.\n\nno: Do not sync at all, always revert to default\nlocal: keep selection on local storage\ntheme-only: sync via OSM, but this layer will only be toggled in this theme\nglobal: all layers with this ID will be synced accross all themes", + "enum": [ + "global", + "local", + "no", + "theme-only" + ], + "type": "string" } }, "required": [ diff --git a/Docs/Schemas/LayoutConfigJsonJSC.ts b/Docs/Schemas/LayoutConfigJsonJSC.ts index 14f441f16..92e8e24fb 100644 --- a/Docs/Schemas/LayoutConfigJsonJSC.ts +++ b/Docs/Schemas/LayoutConfigJsonJSC.ts @@ -454,11 +454,32 @@ export default { ] }, "then": { - "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\ntype: rendered" + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" }, "icon": { "description": "An icon supporting this mapping; typically shown pretty small\nType: icon", - "type": "string" + "anyOf": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "class", + "path" + ] + }, + { + "type": "string" + } + ] }, "hideInAnswer": { "description": "In some cases, multiple taggings exist (e.g. a default assumption, or a commonly mapped abbreviation and a fully written variation).\n\nIn the latter case, a correct text should be shown, but only a single, canonical tagging should be selectable by the user.\nIn this case, one of the mappings can be hiden by setting this flag.\n\nTo demonstrate an example making a default assumption:\n\nmappings: [\n {\n if: \"access=\", -- no access tag present, we assume accessible\n then: \"Accessible to the general public\",\n hideInAnswer: true\n },\n {\n if: \"access=yes\",\n then: \"Accessible to the general public\", -- the user selected this, we add that to OSM\n },\n {\n if: \"access=no\",\n then: \"Not accessible to the public\"\n }\n]\n\n\nFor example, for an operator, we have `operator=Agentschap Natuur en Bos`, which is often abbreviated to `operator=ANB`.\nThen, we would add two mappings:\n{\n if: \"operator=Agentschap Natuur en Bos\" -- the non-abbreviated version which should be uploaded\n then: \"Maintained by Agentschap Natuur en Bos\"\n},\n{\n if: \"operator=ANB\", -- we don't want to upload abbreviations\n then: \"Maintained by Agentschap Natuur en Bos\"\n hideInAnswer: true\n}\n\nHide in answer can also be a tagsfilter, e.g. to make sure an option is only shown when appropriate.\nKeep in mind that this is reverse logic: it will be hidden in the answer if the condition is true, it will thus only show in the case of a mismatch\n\ne.g., for toilets: if \"wheelchair=no\", we know there is no wheelchair dedicated room.\nFor the location of the changing table, the option \"in the wheelchair accessible toilet is weird\", so we write:\n\n{\n \"question\": \"Where is the changing table located?\"\n \"mappings\": [\n {\"if\":\"changing_table:location=female\",\"then\":\"In the female restroom\"},\n {\"if\":\"changing_table:location=male\",\"then\":\"In the male restroom\"},\n {\"if\":\"changing_table:location=wheelchair\",\"then\":\"In the wheelchair accessible restroom\", \"hideInAnswer\": \"wheelchair=no\"},\n \n ]\n}\n\nAlso have a look for the meta-tags\n{\n if: \"operator=Agentschap Natuur en Bos\",\n then: \"Maintained by Agentschap Natuur en Bos\",\n hideInAnswer: \"_country!=be\"\n}", @@ -1304,6 +1325,16 @@ export default { "items": { "$ref": "#/definitions/default_2" } + }, + "syncSelection": { + "description": "If set, synchronizes wether or not this layer is selected.\n\nno: Do not sync at all, always revert to default\nlocal: keep selection on local storage\ntheme-only: sync via OSM, but this layer will only be toggled in this theme\nglobal: all layers with this ID will be synced accross all themes", + "enum": [ + "global", + "local", + "no", + "theme-only" + ], + "type": "string" } }, "required": [ diff --git a/Docs/Schemas/LineRenderingConfigJson.schema.json b/Docs/Schemas/LineRenderingConfigJson.schema.json index a410d218d..2b8cac100 100644 --- a/Docs/Schemas/LineRenderingConfigJson.schema.json +++ b/Docs/Schemas/LineRenderingConfigJson.schema.json @@ -256,11 +256,32 @@ ] }, "then": { - "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\ntype: rendered" + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" }, "icon": { "description": "An icon supporting this mapping; typically shown pretty small\nType: icon", - "type": "string" + "anyOf": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "class", + "path" + ] + }, + { + "type": "string" + } + ] }, "hideInAnswer": { "description": "In some cases, multiple taggings exist (e.g. a default assumption, or a commonly mapped abbreviation and a fully written variation).\n\nIn the latter case, a correct text should be shown, but only a single, canonical tagging should be selectable by the user.\nIn this case, one of the mappings can be hiden by setting this flag.\n\nTo demonstrate an example making a default assumption:\n\nmappings: [\n {\n if: \"access=\", -- no access tag present, we assume accessible\n then: \"Accessible to the general public\",\n hideInAnswer: true\n },\n {\n if: \"access=yes\",\n then: \"Accessible to the general public\", -- the user selected this, we add that to OSM\n },\n {\n if: \"access=no\",\n then: \"Not accessible to the public\"\n }\n]\n\n\nFor example, for an operator, we have `operator=Agentschap Natuur en Bos`, which is often abbreviated to `operator=ANB`.\nThen, we would add two mappings:\n{\n if: \"operator=Agentschap Natuur en Bos\" -- the non-abbreviated version which should be uploaded\n then: \"Maintained by Agentschap Natuur en Bos\"\n},\n{\n if: \"operator=ANB\", -- we don't want to upload abbreviations\n then: \"Maintained by Agentschap Natuur en Bos\"\n hideInAnswer: true\n}\n\nHide in answer can also be a tagsfilter, e.g. to make sure an option is only shown when appropriate.\nKeep in mind that this is reverse logic: it will be hidden in the answer if the condition is true, it will thus only show in the case of a mismatch\n\ne.g., for toilets: if \"wheelchair=no\", we know there is no wheelchair dedicated room.\nFor the location of the changing table, the option \"in the wheelchair accessible toilet is weird\", so we write:\n\n{\n \"question\": \"Where is the changing table located?\"\n \"mappings\": [\n {\"if\":\"changing_table:location=female\",\"then\":\"In the female restroom\"},\n {\"if\":\"changing_table:location=male\",\"then\":\"In the male restroom\"},\n {\"if\":\"changing_table:location=wheelchair\",\"then\":\"In the wheelchair accessible restroom\", \"hideInAnswer\": \"wheelchair=no\"},\n \n ]\n}\n\nAlso have a look for the meta-tags\n{\n if: \"operator=Agentschap Natuur en Bos\",\n then: \"Maintained by Agentschap Natuur en Bos\",\n hideInAnswer: \"_country!=be\"\n}", diff --git a/Docs/Schemas/LineRenderingConfigJsonJSC.ts b/Docs/Schemas/LineRenderingConfigJsonJSC.ts index 918f1b4c8..b0037110a 100644 --- a/Docs/Schemas/LineRenderingConfigJsonJSC.ts +++ b/Docs/Schemas/LineRenderingConfigJsonJSC.ts @@ -254,11 +254,32 @@ export default { ] }, "then": { - "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\ntype: rendered" + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" }, "icon": { "description": "An icon supporting this mapping; typically shown pretty small\nType: icon", - "type": "string" + "anyOf": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "class", + "path" + ] + }, + { + "type": "string" + } + ] }, "hideInAnswer": { "description": "In some cases, multiple taggings exist (e.g. a default assumption, or a commonly mapped abbreviation and a fully written variation).\n\nIn the latter case, a correct text should be shown, but only a single, canonical tagging should be selectable by the user.\nIn this case, one of the mappings can be hiden by setting this flag.\n\nTo demonstrate an example making a default assumption:\n\nmappings: [\n {\n if: \"access=\", -- no access tag present, we assume accessible\n then: \"Accessible to the general public\",\n hideInAnswer: true\n },\n {\n if: \"access=yes\",\n then: \"Accessible to the general public\", -- the user selected this, we add that to OSM\n },\n {\n if: \"access=no\",\n then: \"Not accessible to the public\"\n }\n]\n\n\nFor example, for an operator, we have `operator=Agentschap Natuur en Bos`, which is often abbreviated to `operator=ANB`.\nThen, we would add two mappings:\n{\n if: \"operator=Agentschap Natuur en Bos\" -- the non-abbreviated version which should be uploaded\n then: \"Maintained by Agentschap Natuur en Bos\"\n},\n{\n if: \"operator=ANB\", -- we don't want to upload abbreviations\n then: \"Maintained by Agentschap Natuur en Bos\"\n hideInAnswer: true\n}\n\nHide in answer can also be a tagsfilter, e.g. to make sure an option is only shown when appropriate.\nKeep in mind that this is reverse logic: it will be hidden in the answer if the condition is true, it will thus only show in the case of a mismatch\n\ne.g., for toilets: if \"wheelchair=no\", we know there is no wheelchair dedicated room.\nFor the location of the changing table, the option \"in the wheelchair accessible toilet is weird\", so we write:\n\n{\n \"question\": \"Where is the changing table located?\"\n \"mappings\": [\n {\"if\":\"changing_table:location=female\",\"then\":\"In the female restroom\"},\n {\"if\":\"changing_table:location=male\",\"then\":\"In the male restroom\"},\n {\"if\":\"changing_table:location=wheelchair\",\"then\":\"In the wheelchair accessible restroom\", \"hideInAnswer\": \"wheelchair=no\"},\n \n ]\n}\n\nAlso have a look for the meta-tags\n{\n if: \"operator=Agentschap Natuur en Bos\",\n then: \"Maintained by Agentschap Natuur en Bos\",\n hideInAnswer: \"_country!=be\"\n}", diff --git a/Docs/Schemas/PointRenderingConfigJson.schema.json b/Docs/Schemas/PointRenderingConfigJson.schema.json index 5f14ee615..89d2c3c41 100644 --- a/Docs/Schemas/PointRenderingConfigJson.schema.json +++ b/Docs/Schemas/PointRenderingConfigJson.schema.json @@ -260,11 +260,32 @@ ] }, "then": { - "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\ntype: rendered" + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" }, "icon": { "description": "An icon supporting this mapping; typically shown pretty small\nType: icon", - "type": "string" + "anyOf": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "class", + "path" + ] + }, + { + "type": "string" + } + ] }, "hideInAnswer": { "description": "In some cases, multiple taggings exist (e.g. a default assumption, or a commonly mapped abbreviation and a fully written variation).\n\nIn the latter case, a correct text should be shown, but only a single, canonical tagging should be selectable by the user.\nIn this case, one of the mappings can be hiden by setting this flag.\n\nTo demonstrate an example making a default assumption:\n\nmappings: [\n {\n if: \"access=\", -- no access tag present, we assume accessible\n then: \"Accessible to the general public\",\n hideInAnswer: true\n },\n {\n if: \"access=yes\",\n then: \"Accessible to the general public\", -- the user selected this, we add that to OSM\n },\n {\n if: \"access=no\",\n then: \"Not accessible to the public\"\n }\n]\n\n\nFor example, for an operator, we have `operator=Agentschap Natuur en Bos`, which is often abbreviated to `operator=ANB`.\nThen, we would add two mappings:\n{\n if: \"operator=Agentschap Natuur en Bos\" -- the non-abbreviated version which should be uploaded\n then: \"Maintained by Agentschap Natuur en Bos\"\n},\n{\n if: \"operator=ANB\", -- we don't want to upload abbreviations\n then: \"Maintained by Agentschap Natuur en Bos\"\n hideInAnswer: true\n}\n\nHide in answer can also be a tagsfilter, e.g. to make sure an option is only shown when appropriate.\nKeep in mind that this is reverse logic: it will be hidden in the answer if the condition is true, it will thus only show in the case of a mismatch\n\ne.g., for toilets: if \"wheelchair=no\", we know there is no wheelchair dedicated room.\nFor the location of the changing table, the option \"in the wheelchair accessible toilet is weird\", so we write:\n\n{\n \"question\": \"Where is the changing table located?\"\n \"mappings\": [\n {\"if\":\"changing_table:location=female\",\"then\":\"In the female restroom\"},\n {\"if\":\"changing_table:location=male\",\"then\":\"In the male restroom\"},\n {\"if\":\"changing_table:location=wheelchair\",\"then\":\"In the wheelchair accessible restroom\", \"hideInAnswer\": \"wheelchair=no\"},\n \n ]\n}\n\nAlso have a look for the meta-tags\n{\n if: \"operator=Agentschap Natuur en Bos\",\n then: \"Maintained by Agentschap Natuur en Bos\",\n hideInAnswer: \"_country!=be\"\n}", diff --git a/Docs/Schemas/PointRenderingConfigJsonJSC.ts b/Docs/Schemas/PointRenderingConfigJsonJSC.ts index 30827308f..599e6c5c1 100644 --- a/Docs/Schemas/PointRenderingConfigJsonJSC.ts +++ b/Docs/Schemas/PointRenderingConfigJsonJSC.ts @@ -258,11 +258,32 @@ export default { ] }, "then": { - "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\ntype: rendered" + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" }, "icon": { "description": "An icon supporting this mapping; typically shown pretty small\nType: icon", - "type": "string" + "anyOf": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "class", + "path" + ] + }, + { + "type": "string" + } + ] }, "hideInAnswer": { "description": "In some cases, multiple taggings exist (e.g. a default assumption, or a commonly mapped abbreviation and a fully written variation).\n\nIn the latter case, a correct text should be shown, but only a single, canonical tagging should be selectable by the user.\nIn this case, one of the mappings can be hiden by setting this flag.\n\nTo demonstrate an example making a default assumption:\n\nmappings: [\n {\n if: \"access=\", -- no access tag present, we assume accessible\n then: \"Accessible to the general public\",\n hideInAnswer: true\n },\n {\n if: \"access=yes\",\n then: \"Accessible to the general public\", -- the user selected this, we add that to OSM\n },\n {\n if: \"access=no\",\n then: \"Not accessible to the public\"\n }\n]\n\n\nFor example, for an operator, we have `operator=Agentschap Natuur en Bos`, which is often abbreviated to `operator=ANB`.\nThen, we would add two mappings:\n{\n if: \"operator=Agentschap Natuur en Bos\" -- the non-abbreviated version which should be uploaded\n then: \"Maintained by Agentschap Natuur en Bos\"\n},\n{\n if: \"operator=ANB\", -- we don't want to upload abbreviations\n then: \"Maintained by Agentschap Natuur en Bos\"\n hideInAnswer: true\n}\n\nHide in answer can also be a tagsfilter, e.g. to make sure an option is only shown when appropriate.\nKeep in mind that this is reverse logic: it will be hidden in the answer if the condition is true, it will thus only show in the case of a mismatch\n\ne.g., for toilets: if \"wheelchair=no\", we know there is no wheelchair dedicated room.\nFor the location of the changing table, the option \"in the wheelchair accessible toilet is weird\", so we write:\n\n{\n \"question\": \"Where is the changing table located?\"\n \"mappings\": [\n {\"if\":\"changing_table:location=female\",\"then\":\"In the female restroom\"},\n {\"if\":\"changing_table:location=male\",\"then\":\"In the male restroom\"},\n {\"if\":\"changing_table:location=wheelchair\",\"then\":\"In the wheelchair accessible restroom\", \"hideInAnswer\": \"wheelchair=no\"},\n \n ]\n}\n\nAlso have a look for the meta-tags\n{\n if: \"operator=Agentschap Natuur en Bos\",\n then: \"Maintained by Agentschap Natuur en Bos\",\n hideInAnswer: \"_country!=be\"\n}", diff --git a/Docs/Schemas/TagRenderingConfigJson.schema.json b/Docs/Schemas/TagRenderingConfigJson.schema.json index 9b74df032..21ee45e1e 100644 --- a/Docs/Schemas/TagRenderingConfigJson.schema.json +++ b/Docs/Schemas/TagRenderingConfigJson.schema.json @@ -96,11 +96,32 @@ ] }, "then": { - "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\ntype: rendered" + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" }, "icon": { "description": "An icon supporting this mapping; typically shown pretty small\nType: icon", - "type": "string" + "anyOf": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "class", + "path" + ] + }, + { + "type": "string" + } + ] }, "hideInAnswer": { "description": "In some cases, multiple taggings exist (e.g. a default assumption, or a commonly mapped abbreviation and a fully written variation).\n\nIn the latter case, a correct text should be shown, but only a single, canonical tagging should be selectable by the user.\nIn this case, one of the mappings can be hiden by setting this flag.\n\nTo demonstrate an example making a default assumption:\n\nmappings: [\n {\n if: \"access=\", -- no access tag present, we assume accessible\n then: \"Accessible to the general public\",\n hideInAnswer: true\n },\n {\n if: \"access=yes\",\n then: \"Accessible to the general public\", -- the user selected this, we add that to OSM\n },\n {\n if: \"access=no\",\n then: \"Not accessible to the public\"\n }\n]\n\n\nFor example, for an operator, we have `operator=Agentschap Natuur en Bos`, which is often abbreviated to `operator=ANB`.\nThen, we would add two mappings:\n{\n if: \"operator=Agentschap Natuur en Bos\" -- the non-abbreviated version which should be uploaded\n then: \"Maintained by Agentschap Natuur en Bos\"\n},\n{\n if: \"operator=ANB\", -- we don't want to upload abbreviations\n then: \"Maintained by Agentschap Natuur en Bos\"\n hideInAnswer: true\n}\n\nHide in answer can also be a tagsfilter, e.g. to make sure an option is only shown when appropriate.\nKeep in mind that this is reverse logic: it will be hidden in the answer if the condition is true, it will thus only show in the case of a mismatch\n\ne.g., for toilets: if \"wheelchair=no\", we know there is no wheelchair dedicated room.\nFor the location of the changing table, the option \"in the wheelchair accessible toilet is weird\", so we write:\n\n{\n \"question\": \"Where is the changing table located?\"\n \"mappings\": [\n {\"if\":\"changing_table:location=female\",\"then\":\"In the female restroom\"},\n {\"if\":\"changing_table:location=male\",\"then\":\"In the male restroom\"},\n {\"if\":\"changing_table:location=wheelchair\",\"then\":\"In the wheelchair accessible restroom\", \"hideInAnswer\": \"wheelchair=no\"},\n \n ]\n}\n\nAlso have a look for the meta-tags\n{\n if: \"operator=Agentschap Natuur en Bos\",\n then: \"Maintained by Agentschap Natuur en Bos\",\n hideInAnswer: \"_country!=be\"\n}", diff --git a/Docs/Schemas/TagRenderingConfigJsonJSC.ts b/Docs/Schemas/TagRenderingConfigJsonJSC.ts index 2bc849c6e..cf2358570 100644 --- a/Docs/Schemas/TagRenderingConfigJsonJSC.ts +++ b/Docs/Schemas/TagRenderingConfigJsonJSC.ts @@ -96,11 +96,32 @@ export default { ] }, "then": { - "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\ntype: rendered" + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" }, "icon": { "description": "An icon supporting this mapping; typically shown pretty small\nType: icon", - "type": "string" + "anyOf": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "class", + "path" + ] + }, + { + "type": "string" + } + ] }, "hideInAnswer": { "description": "In some cases, multiple taggings exist (e.g. a default assumption, or a commonly mapped abbreviation and a fully written variation).\n\nIn the latter case, a correct text should be shown, but only a single, canonical tagging should be selectable by the user.\nIn this case, one of the mappings can be hiden by setting this flag.\n\nTo demonstrate an example making a default assumption:\n\nmappings: [\n {\n if: \"access=\", -- no access tag present, we assume accessible\n then: \"Accessible to the general public\",\n hideInAnswer: true\n },\n {\n if: \"access=yes\",\n then: \"Accessible to the general public\", -- the user selected this, we add that to OSM\n },\n {\n if: \"access=no\",\n then: \"Not accessible to the public\"\n }\n]\n\n\nFor example, for an operator, we have `operator=Agentschap Natuur en Bos`, which is often abbreviated to `operator=ANB`.\nThen, we would add two mappings:\n{\n if: \"operator=Agentschap Natuur en Bos\" -- the non-abbreviated version which should be uploaded\n then: \"Maintained by Agentschap Natuur en Bos\"\n},\n{\n if: \"operator=ANB\", -- we don't want to upload abbreviations\n then: \"Maintained by Agentschap Natuur en Bos\"\n hideInAnswer: true\n}\n\nHide in answer can also be a tagsfilter, e.g. to make sure an option is only shown when appropriate.\nKeep in mind that this is reverse logic: it will be hidden in the answer if the condition is true, it will thus only show in the case of a mismatch\n\ne.g., for toilets: if \"wheelchair=no\", we know there is no wheelchair dedicated room.\nFor the location of the changing table, the option \"in the wheelchair accessible toilet is weird\", so we write:\n\n{\n \"question\": \"Where is the changing table located?\"\n \"mappings\": [\n {\"if\":\"changing_table:location=female\",\"then\":\"In the female restroom\"},\n {\"if\":\"changing_table:location=male\",\"then\":\"In the male restroom\"},\n {\"if\":\"changing_table:location=wheelchair\",\"then\":\"In the wheelchair accessible restroom\", \"hideInAnswer\": \"wheelchair=no\"},\n \n ]\n}\n\nAlso have a look for the meta-tags\n{\n if: \"operator=Agentschap Natuur en Bos\",\n then: \"Maintained by Agentschap Natuur en Bos\",\n hideInAnswer: \"_country!=be\"\n}", diff --git a/Docs/Schemas/TilesourceConfigJson.schema.json b/Docs/Schemas/TilesourceConfigJson.schema.json index cef3d5386..ebac53292 100644 --- a/Docs/Schemas/TilesourceConfigJson.schema.json +++ b/Docs/Schemas/TilesourceConfigJson.schema.json @@ -204,11 +204,32 @@ ] }, "then": { - "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\ntype: rendered" + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" }, "icon": { "description": "An icon supporting this mapping; typically shown pretty small\nType: icon", - "type": "string" + "anyOf": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "class", + "path" + ] + }, + { + "type": "string" + } + ] }, "hideInAnswer": { "description": "In some cases, multiple taggings exist (e.g. a default assumption, or a commonly mapped abbreviation and a fully written variation).\n\nIn the latter case, a correct text should be shown, but only a single, canonical tagging should be selectable by the user.\nIn this case, one of the mappings can be hiden by setting this flag.\n\nTo demonstrate an example making a default assumption:\n\nmappings: [\n {\n if: \"access=\", -- no access tag present, we assume accessible\n then: \"Accessible to the general public\",\n hideInAnswer: true\n },\n {\n if: \"access=yes\",\n then: \"Accessible to the general public\", -- the user selected this, we add that to OSM\n },\n {\n if: \"access=no\",\n then: \"Not accessible to the public\"\n }\n]\n\n\nFor example, for an operator, we have `operator=Agentschap Natuur en Bos`, which is often abbreviated to `operator=ANB`.\nThen, we would add two mappings:\n{\n if: \"operator=Agentschap Natuur en Bos\" -- the non-abbreviated version which should be uploaded\n then: \"Maintained by Agentschap Natuur en Bos\"\n},\n{\n if: \"operator=ANB\", -- we don't want to upload abbreviations\n then: \"Maintained by Agentschap Natuur en Bos\"\n hideInAnswer: true\n}\n\nHide in answer can also be a tagsfilter, e.g. to make sure an option is only shown when appropriate.\nKeep in mind that this is reverse logic: it will be hidden in the answer if the condition is true, it will thus only show in the case of a mismatch\n\ne.g., for toilets: if \"wheelchair=no\", we know there is no wheelchair dedicated room.\nFor the location of the changing table, the option \"in the wheelchair accessible toilet is weird\", so we write:\n\n{\n \"question\": \"Where is the changing table located?\"\n \"mappings\": [\n {\"if\":\"changing_table:location=female\",\"then\":\"In the female restroom\"},\n {\"if\":\"changing_table:location=male\",\"then\":\"In the male restroom\"},\n {\"if\":\"changing_table:location=wheelchair\",\"then\":\"In the wheelchair accessible restroom\", \"hideInAnswer\": \"wheelchair=no\"},\n \n ]\n}\n\nAlso have a look for the meta-tags\n{\n if: \"operator=Agentschap Natuur en Bos\",\n then: \"Maintained by Agentschap Natuur en Bos\",\n hideInAnswer: \"_country!=be\"\n}", diff --git a/Docs/Schemas/TilesourceConfigJsonJSC.ts b/Docs/Schemas/TilesourceConfigJsonJSC.ts index 15178b758..bc487ece2 100644 --- a/Docs/Schemas/TilesourceConfigJsonJSC.ts +++ b/Docs/Schemas/TilesourceConfigJsonJSC.ts @@ -202,11 +202,32 @@ export default { ] }, "then": { - "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\ntype: rendered" + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" }, "icon": { "description": "An icon supporting this mapping; typically shown pretty small\nType: icon", - "type": "string" + "anyOf": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "class", + "path" + ] + }, + { + "type": "string" + } + ] }, "hideInAnswer": { "description": "In some cases, multiple taggings exist (e.g. a default assumption, or a commonly mapped abbreviation and a fully written variation).\n\nIn the latter case, a correct text should be shown, but only a single, canonical tagging should be selectable by the user.\nIn this case, one of the mappings can be hiden by setting this flag.\n\nTo demonstrate an example making a default assumption:\n\nmappings: [\n {\n if: \"access=\", -- no access tag present, we assume accessible\n then: \"Accessible to the general public\",\n hideInAnswer: true\n },\n {\n if: \"access=yes\",\n then: \"Accessible to the general public\", -- the user selected this, we add that to OSM\n },\n {\n if: \"access=no\",\n then: \"Not accessible to the public\"\n }\n]\n\n\nFor example, for an operator, we have `operator=Agentschap Natuur en Bos`, which is often abbreviated to `operator=ANB`.\nThen, we would add two mappings:\n{\n if: \"operator=Agentschap Natuur en Bos\" -- the non-abbreviated version which should be uploaded\n then: \"Maintained by Agentschap Natuur en Bos\"\n},\n{\n if: \"operator=ANB\", -- we don't want to upload abbreviations\n then: \"Maintained by Agentschap Natuur en Bos\"\n hideInAnswer: true\n}\n\nHide in answer can also be a tagsfilter, e.g. to make sure an option is only shown when appropriate.\nKeep in mind that this is reverse logic: it will be hidden in the answer if the condition is true, it will thus only show in the case of a mismatch\n\ne.g., for toilets: if \"wheelchair=no\", we know there is no wheelchair dedicated room.\nFor the location of the changing table, the option \"in the wheelchair accessible toilet is weird\", so we write:\n\n{\n \"question\": \"Where is the changing table located?\"\n \"mappings\": [\n {\"if\":\"changing_table:location=female\",\"then\":\"In the female restroom\"},\n {\"if\":\"changing_table:location=male\",\"then\":\"In the male restroom\"},\n {\"if\":\"changing_table:location=wheelchair\",\"then\":\"In the wheelchair accessible restroom\", \"hideInAnswer\": \"wheelchair=no\"},\n \n ]\n}\n\nAlso have a look for the meta-tags\n{\n if: \"operator=Agentschap Natuur en Bos\",\n then: \"Maintained by Agentschap Natuur en Bos\",\n hideInAnswer: \"_country!=be\"\n}", diff --git a/Docs/Tools/GenerateSeries.ts b/Docs/Tools/GenerateSeries.ts index 9f135c54d..b97d7399b 100644 --- a/Docs/Tools/GenerateSeries.ts +++ b/Docs/Tools/GenerateSeries.ts @@ -528,7 +528,7 @@ function stackHists(hists: [V, Histogram][]): [V, Histogram][] { runningTotals.bumpHist(hist) result.push([vhist[0], clone]) }) - result.reverse() + result.reverse(/* Changes in place, safe copy*/) return result } diff --git a/Logic/Actors/AvailableBaseLayersImplementation.ts b/Logic/Actors/AvailableBaseLayersImplementation.ts index 2ac7aa8d4..6b12013fe 100644 --- a/Logic/Actors/AvailableBaseLayersImplementation.ts +++ b/Logic/Actors/AvailableBaseLayersImplementation.ts @@ -239,7 +239,7 @@ export default class AvailableBaseLayersImplementation implements AvailableBaseL prefered = preferedCategory.data; } - prefered.reverse(); + prefered.reverse(/*New list, inplace reverse is fine*/); for (const category of prefered) { //Then sort all 'photo'-layers to the top. Stability of the sorting will force a 'best' photo layer on top available.sort((a, b) => { diff --git a/Logic/FeatureSource/FeaturePipeline.ts b/Logic/FeatureSource/FeaturePipeline.ts index bb2382aff..87d4aedda 100644 --- a/Logic/FeatureSource/FeaturePipeline.ts +++ b/Logic/FeatureSource/FeaturePipeline.ts @@ -75,7 +75,7 @@ export default class FeaturePipeline { this.state = state; const self = this - const expiryInSeconds = Math.min(...state.layoutToUse.layers.map(l => l.maxAgeOfCache)) + const expiryInSeconds = Math.min(...state.layoutToUse?.layers?.map(l => l.maxAgeOfCache) ?? []) this.oldestAllowedDate = new Date(new Date().getTime() - expiryInSeconds); this.osmSourceZoomLevel = state.osmApiTileSize.data; const useOsmApi = state.locationControl.map(l => l.zoom > (state.overpassMaxZoom.data ?? 12)) diff --git a/Logic/FeatureSource/Sources/ChangeGeometryApplicator.ts b/Logic/FeatureSource/Sources/ChangeGeometryApplicator.ts index 83ab174bd..d7d6c8b7a 100644 --- a/Logic/FeatureSource/Sources/ChangeGeometryApplicator.ts +++ b/Logic/FeatureSource/Sources/ChangeGeometryApplicator.ts @@ -74,7 +74,7 @@ export default class ChangeGeometryApplicator implements FeatureSourceForLayer { // We only apply the last change as that one'll have the latest geometry const change = changesForFeature[changesForFeature.length - 1] copy.feature.geometry = ChangeDescriptionTools.getGeojsonGeometry(change) - console.log("Applying a geometry change onto ", feature, change, copy) + console.log("Applying a geometry change onto:", feature,"The change is:", change,"which becomes:", copy) newFeatures.push(copy) } this.features.setData(newFeatures) diff --git a/Logic/FeatureSource/TiledFeatureSource/OsmFeatureSource.ts b/Logic/FeatureSource/TiledFeatureSource/OsmFeatureSource.ts index 68190d909..a9bd149b5 100644 --- a/Logic/FeatureSource/TiledFeatureSource/OsmFeatureSource.ts +++ b/Logic/FeatureSource/TiledFeatureSource/OsmFeatureSource.ts @@ -79,7 +79,7 @@ export default class OsmFeatureSource { }) - const neededLayers = options.state.layoutToUse.layers + const neededLayers = (options.state.layoutToUse?.layers ?? []) .filter(layer => !layer.doNotDownload) .filter(layer => layer.source.geojsonSource === undefined || layer.source.isOsmCacheLayer) this.allowedTags = new Or(neededLayers.map(l => l.source.osmTags)) diff --git a/Logic/Osm/Actions/ChangeDescription.ts b/Logic/Osm/Actions/ChangeDescription.ts index 0f03caf0b..42df3f4a0 100644 --- a/Logic/Osm/Actions/ChangeDescription.ts +++ b/Logic/Osm/Actions/ChangeDescription.ts @@ -81,7 +81,7 @@ export class ChangeDescriptionTools { case "way": const w = new OsmWay(change.id) w.nodes = change.changes["nodes"] - w.coordinates = change.changes["coordinates"].map(coor => coor.reverse()) + w.coordinates = change.changes["coordinates"].map(([lon, lat]) => [lat, lon]) return w.asGeoJson().geometry case "relation": const r = new OsmRelation(change.id) diff --git a/Logic/Osm/Actions/CreateMultiPolygonWithPointReuseAction.ts b/Logic/Osm/Actions/CreateMultiPolygonWithPointReuseAction.ts index d5e3f53ec..73eb1f3a1 100644 --- a/Logic/Osm/Actions/CreateMultiPolygonWithPointReuseAction.ts +++ b/Logic/Osm/Actions/CreateMultiPolygonWithPointReuseAction.ts @@ -33,12 +33,12 @@ export default class CreateMultiPolygonWithPointReuseAction extends OsmCreateAct super(null, true); this._tags = [...tags, new Tag("type", "multipolygon")]; this.changeType = changeType; - this.theme = state.layoutToUse.id + this.theme = state?.layoutToUse?.id ?? "" this.createOuterWay = new CreateWayWithPointReuseAction([], outerRingCoordinates, state, config) this.createInnerWays = innerRingsCoordinates.map(ringCoordinates => new CreateNewWayAction([], ringCoordinates.map(([lon, lat]) => ({lat, lon})), - {theme: state.layoutToUse.id})) + {theme: state?.layoutToUse?.id})) this.geojsonPreview = { type: "Feature", diff --git a/Logic/Osm/Actions/CreateNewNodeAction.ts b/Logic/Osm/Actions/CreateNewNodeAction.ts index 1c7d37989..844463ec1 100644 --- a/Logic/Osm/Actions/CreateNewNodeAction.ts +++ b/Logic/Osm/Actions/CreateNewNodeAction.ts @@ -112,16 +112,25 @@ export default class CreateNewNodeAction extends OsmCreateAction { const geojson = this._snapOnto.asGeoJson() const projected = GeoOperations.nearestPoint(geojson, [this._lon, this._lat]) + const projectedCoor= <[number, number]>projected.geometry.coordinates const index = projected.properties.index // We check that it isn't close to an already existing point let reusedPointId = undefined; - const prev = <[number, number]>geojson.geometry.coordinates[index] - if (GeoOperations.distanceBetween(prev, <[number, number]>projected.geometry.coordinates) < this._reusePointDistance) { + let outerring : [number,number][]; + + if(geojson.geometry.type === "LineString"){ + outerring = <[number, number][]> geojson.geometry.coordinates + }else if(geojson.geometry.type === "Polygon"){ + outerring =<[number, number][]> geojson.geometry.coordinates[0] + } + + const prev= outerring[index] + if (GeoOperations.distanceBetween(prev, projectedCoor) < this._reusePointDistance) { // We reuse this point instead! reusedPointId = this._snapOnto.nodes[index] } - const next = <[number, number]>geojson.geometry.coordinates[index + 1] - if (GeoOperations.distanceBetween(next, <[number, number]>projected.geometry.coordinates) < this._reusePointDistance) { + const next = outerring[index + 1] + if (GeoOperations.distanceBetween(next, projectedCoor) < this._reusePointDistance) { // We reuse this point instead! reusedPointId = this._snapOnto.nodes[index + 1] } @@ -135,8 +144,7 @@ export default class CreateNewNodeAction extends OsmCreateAction { }] } - const locations = [...this._snapOnto.coordinates] - locations.forEach(coor => coor.reverse()) + const locations = [...this._snapOnto.coordinates.map(([lat, lon]) =><[number,number]> [lon, lat])] const ids = [...this._snapOnto.nodes] locations.splice(index + 1, 0, [this._lon, this._lat]) diff --git a/Logic/Osm/Actions/CreateNewWayAction.ts b/Logic/Osm/Actions/CreateNewWayAction.ts index 68c521abc..edc6dd0bf 100644 --- a/Logic/Osm/Actions/CreateNewWayAction.ts +++ b/Logic/Osm/Actions/CreateNewWayAction.ts @@ -33,7 +33,7 @@ export default class CreateNewWayAction extends OsmCreateAction { We filter those here, as the CreateWayWithPointReuseAction delegates the actual creation to here. Filtering here also prevents similar bugs in other actions */ - if(this.coordinates.length > 0 && this.coordinates[this.coordinates.length - 1].nodeId === coordinate.nodeId){ + if(this.coordinates.length > 0 && coordinate.nodeId !== undefined && this.coordinates[this.coordinates.length - 1].nodeId === coordinate.nodeId){ // This is a duplicate id console.warn("Skipping a node in createWay to avoid a duplicate node:", coordinate,"\nThe previous coordinates are: ", this.coordinates) continue diff --git a/Logic/Osm/Actions/CreateWayWithPointReuseAction.ts b/Logic/Osm/Actions/CreateWayWithPointReuseAction.ts index afcdab4a1..95911f432 100644 --- a/Logic/Osm/Actions/CreateWayWithPointReuseAction.ts +++ b/Logic/Osm/Actions/CreateWayWithPointReuseAction.ts @@ -186,7 +186,7 @@ export default class CreateWayWithPointReuseAction extends OsmCreateAction { } public async CreateChangeDescriptions(changes: Changes): Promise { - const theme = this._state.layoutToUse.id + const theme = this._state?.layoutToUse?.id const allChanges: ChangeDescription[] = [] const nodeIdsToUse: { lat: number, lon: number, nodeId?: number }[] = [] for (let i = 0; i < this._coordinateInfo.length; i++) { @@ -251,7 +251,7 @@ export default class CreateWayWithPointReuseAction extends OsmCreateAction { const bbox = new BBox(coordinates) const state = this._state - const allNodes = [].concat(...state.featurePipeline.GetFeaturesWithin("type_node", bbox.pad(1.2))) + const allNodes = [].concat(...state?.featurePipeline?.GetFeaturesWithin("type_node", bbox.pad(1.2))??[]) const maxDistance = Math.max(...this._config.map(c => c.withinRangeOfM)) // Init coordianteinfo with undefined but the same length as coordinates diff --git a/Logic/Osm/Actions/ReplaceGeometryAction.ts b/Logic/Osm/Actions/ReplaceGeometryAction.ts index 5146e5574..f642b2d04 100644 --- a/Logic/Osm/Actions/ReplaceGeometryAction.ts +++ b/Logic/Osm/Actions/ReplaceGeometryAction.ts @@ -28,6 +28,7 @@ export default class ReplaceGeometryAction extends OsmChangeAction { /** * The target coordinates that should end up in OpenStreetMap. * This is identical to either this.feature.geometry.coordinates or -in case of a polygon- feature.geometry.coordinates[0] + * Format: [lon, lat] */ private readonly targetCoordinates: [number, number][]; /** @@ -540,8 +541,6 @@ export default class ReplaceGeometryAction extends OsmChangeAction { id: nodeId, }) }) - - } return allChanges diff --git a/Logic/Osm/Changes.ts b/Logic/Osm/Changes.ts index 10640991f..c8cfb83bc 100644 --- a/Logic/Osm/Changes.ts +++ b/Logic/Osm/Changes.ts @@ -55,8 +55,8 @@ export class Changes { // This doesn't matter however, as the '-1' is per piecewise upload, not global per changeset } - private static createChangesetFor(csId: string, - allChanges: { + static createChangesetFor(csId: string, + allChanges: { modifiedObjects: OsmObject[], newObjects: OsmObject[], deletedObjects: OsmObject[] diff --git a/Logic/Osm/OsmObject.ts b/Logic/Osm/OsmObject.ts index c73cf4e72..bb5a8e997 100644 --- a/Logic/Osm/OsmObject.ts +++ b/Logic/Osm/OsmObject.ts @@ -207,27 +207,36 @@ export abstract class OsmObject { return objects; } + /** + * Uses the list of polygon features to determine if the given tags are a polygon or not. + * */ protected static isPolygon(tags: any): boolean { for (const tagsKey in tags) { if (!tags.hasOwnProperty(tagsKey)) { continue } - const polyGuide = OsmObject.polygonFeatures.get(tagsKey) + const polyGuide : { values: Set; blacklist: boolean } = OsmObject.polygonFeatures.get(tagsKey) if (polyGuide === undefined) { continue } if ((polyGuide.values === null)) { - // We match all + // .values is null, thus merely _having_ this key is enough to be a polygon (or if blacklist, being a line) return !polyGuide.blacklist } - // is the key contained? - return polyGuide.values.has(tags[tagsKey]) + // is the key contained? Then we have a match if the value is contained + const doesMatch = polyGuide.values.has(tags[tagsKey]) + if(polyGuide.blacklist){ + return !doesMatch + } + return doesMatch } + + return false; } private static constructPolygonFeatures(): Map, blacklist: boolean }> { const result = new Map, blacklist: boolean }>(); - for (const polygonFeature of polygon_features) { + for (const polygonFeature of (polygon_features["default"] ?? polygon_features)) { const key = polygonFeature.key; if (polygonFeature.polygon === "all") { @@ -381,7 +390,7 @@ export class OsmWay extends OsmObject { } if (element.nodes === undefined) { - console.log("PANIC") + console.error("PANIC: no nodes!") } for (const nodeId of element.nodes) { @@ -417,7 +426,9 @@ export class OsmWay extends OsmObject { } private isPolygon(): boolean { - if (this.coordinates[0] !== this.coordinates[this.coordinates.length - 1]) { + // Compare lat and lon seperately, as the coordinate array might not be a reference to the same object + if (this.coordinates[0][0] !== this.coordinates[this.coordinates.length - 1][0] || + this.coordinates[0][1] !== this.coordinates[this.coordinates.length - 1][1] ) { return false; // Not closed } return OsmObject.isPolygon(this.tags) diff --git a/Logic/State/FeaturePipelineState.ts b/Logic/State/FeaturePipelineState.ts index 2c2c2a81f..62f461605 100644 --- a/Logic/State/FeaturePipelineState.ts +++ b/Logic/State/FeaturePipelineState.ts @@ -25,7 +25,7 @@ export default class FeaturePipelineState extends MapState { constructor(layoutToUse: LayoutConfig) { super(layoutToUse); - const clustering = layoutToUse.clustering + const clustering = layoutToUse?.clustering this.featureAggregator = TileHierarchyAggregator.createHierarchy(this); const clusterCounter = this.featureAggregator const self = this; diff --git a/Logic/State/MapState.ts b/Logic/State/MapState.ts index 387885a9c..32aba550d 100644 --- a/Logic/State/MapState.ts +++ b/Logic/State/MapState.ts @@ -117,10 +117,12 @@ export default class MapState extends UserRelatedState { }) - this.overlayToggles = this.layoutToUse.tileLayerSources.filter(c => c.name !== undefined).map(c => ({ + this.overlayToggles = this.layoutToUse?.tileLayerSources + ?.filter(c => c.name !== undefined) + ?.map(c => ({ config: c, isDisplayed: QueryParameters.GetBooleanQueryParameter("overlay-" + c.id, c.defaultState, "Wether or not the overlay " + c.id + " is shown") - })) + })) ?? [] this.filteredLayers = this.InitializeFilteredLayers() @@ -142,7 +144,7 @@ export default class MapState extends UserRelatedState { initialized.add(overlayToggle.config) } - for (const tileLayerSource of this.layoutToUse.tileLayerSources) { + for (const tileLayerSource of this.layoutToUse?.tileLayerSources ?? []) { if (initialized.has(tileLayerSource)) { continue } @@ -153,28 +155,14 @@ export default class MapState extends UserRelatedState { private lockBounds() { const layout = this.layoutToUse; - if (layout.lockLocation) { - if (layout.lockLocation === true) { - const tile = Tiles.embedded_tile( - layout.startLat, - layout.startLon, - layout.startZoom - 1 - ); - const bounds = Tiles.tile_bounds(tile.z, tile.x, tile.y); - // We use the bounds to get a sense of distance for this zoom level - const latDiff = bounds[0][0] - bounds[1][0]; - const lonDiff = bounds[0][1] - bounds[1][1]; - layout.lockLocation = [ - [layout.startLat - latDiff, layout.startLon - lonDiff], - [layout.startLat + latDiff, layout.startLon + lonDiff], - ]; - } - console.warn("Locking the bounds to ", layout.lockLocation); - this.mainMapObject.installBounds( - new BBox(layout.lockLocation), - this.featureSwitchIsTesting.data - ) + if (!layout?.lockLocation) { + return; } + console.warn("Locking the bounds to ", layout.lockLocation); + this.mainMapObject.installBounds( + new BBox(layout.lockLocation), + this.featureSwitchIsTesting.data + ) } private initCurrentView() { @@ -364,8 +352,10 @@ export default class MapState extends UserRelatedState { } private InitializeFilteredLayers() { - const layoutToUse = this.layoutToUse; + if(layoutToUse === undefined){ + return new UIEventSource([]) + } const flayers: FilteredLayer[] = []; for (const layer of layoutToUse.layers) { let isDisplayed: UIEventSource diff --git a/Models/Constants.ts b/Models/Constants.ts index c76a77f17..a8bec17f0 100644 --- a/Models/Constants.ts +++ b/Models/Constants.ts @@ -61,7 +61,7 @@ export default class Constants { * For every bin, the totals are uploaded as metadata */ static distanceToChangeObjectBins = [25, 50, 100, 500, 1000, 5000, Number.MAX_VALUE] - static themeOrder = ["personal", "cyclofix", "hailhydrant", "bookcases", "toilets", "aed"]; + static themeOrder = ["personal", "cyclofix", "waste" , "etymology", "food","cafes_and_pubs", "playgrounds", "hailhydrant", "toilets", "aed", "bookcases"]; private static isRetina(): boolean { if (Utils.runningFromConsole) { diff --git a/Models/ThemeConfig/Conversion/FixImages.ts b/Models/ThemeConfig/Conversion/FixImages.ts index ef315fd95..0b8bb1de0 100644 --- a/Models/ThemeConfig/Conversion/FixImages.ts +++ b/Models/ThemeConfig/Conversion/FixImages.ts @@ -6,51 +6,70 @@ import * as tagrenderingmetapaths from "../../../assets/tagrenderingconfigmeta.j export class ExtractImages extends Conversion { private _isOfficial: boolean; - constructor(isOfficial: boolean) { + private _sharedTagRenderings: Map; + + private static readonly layoutMetaPaths = (metapaths["default"] ?? metapaths).filter(mp => mp.typeHint !== undefined && (mp.typeHint === "image" || mp.typeHint === "icon")) + private static readonly tagRenderingMetaPaths = (tagrenderingmetapaths["default"] ?? tagrenderingmetapaths).filter(trpath => trpath.typeHint === "rendered") + + + constructor(isOfficial: boolean, sharedTagRenderings: Map) { super("Extract all images from a layoutConfig using the meta paths",[],"ExctractImages"); this._isOfficial = isOfficial; + this._sharedTagRenderings = sharedTagRenderings; } convert(json: LayoutConfigJson, context: string): { result: string[], errors: string[], warnings: string[] } { - const paths = metapaths["default"] ?? metapaths - const trpaths = tagrenderingmetapaths["default"] ?? tagrenderingmetapaths - const allFoundImages = [] + const allFoundImages : string[] = [] const errors = [] const warnings = [] - for (const metapath of paths) { - if (metapath.typeHint === undefined) { - continue - } - if (metapath.typeHint !== "image" && metapath.typeHint !== "icon") { - continue - } - + for (const metapath of ExtractImages.layoutMetaPaths) { const mightBeTr = Array.isArray(metapath.type) && metapath.type.some(t => t["$ref"] == "#/definitions/TagRenderingConfigJson") const found = Utils.CollectPath(metapath.path, json) if (mightBeTr) { // We might have tagRenderingConfigs containing icons here - for (const foundImage of found) { + for (const el of found) { + const path = el.path + const foundImage = el.leaf; if (typeof foundImage === "string") { + + if(foundImage == ""){ + warnings.push(context+"."+path.join(".")+" Found an empty image") + } + + if(this._sharedTagRenderings?.has(foundImage)){ + // This is not an image, but a shared tag rendering + continue + } + allFoundImages.push(foundImage) } else{ // This is a tagRendering where every rendered value might be an icon! - for (const trpath of trpaths) { - if (trpath.typeHint !== "rendered") { - continue - } + for (const trpath of ExtractImages.tagRenderingMetaPaths) { const fromPath = Utils.CollectPath(trpath.path, foundImage) for (const img of fromPath) { - if (typeof img !== "string") { - (this._isOfficial ? errors: warnings).push(context+": found an image path that is not a path at " + context + "." + metapath.path.join(".") + ": " + JSON.stringify(img)) + if (typeof img.leaf !== "string") { + (this._isOfficial ? errors: warnings).push(context+"."+img.path.join(".")+": found an image path that is not a string: " + JSON.stringify(img.leaf)) + } + } + allFoundImages.push(...fromPath.map(i => i.leaf).filter(i => typeof i=== "string")) + for (const pathAndImg of fromPath) { + if(pathAndImg.leaf === "" || pathAndImg.leaf["path"] == ""){ + warnings.push(context+[...path,...pathAndImg.path].join(".")+": Found an empty image at ") } } - allFoundImages.push(...fromPath.filter(i => typeof i === "string")) } } } } else { - allFoundImages.push(...found) + for (const foundElement of found) { + if(foundElement.leaf === ""){ + warnings.push(context+"."+foundElement.path.join(".")+" Found an empty image") + continue + } + allFoundImages.push(foundElement.leaf) + } + } } @@ -58,6 +77,7 @@ export class ExtractImages extends Conversion { .map(img => img["path"] ?? img) .map(img => img.split(";"))) .map(img => img.split(":")[0]) + .filter(img => img !== "") return {result: Utils.Dedup(splitParts), errors, warnings}; } @@ -108,7 +128,7 @@ export class FixImages extends DesugaringStep { continue } const mightBeTr = Array.isArray(metapath.type) && metapath.type.some(t => t["$ref"] == "#/definitions/TagRenderingConfigJson") - Utils.WalkPath(metapath.path, json, leaf => { + Utils.WalkPath(metapath.path, json, (leaf, path) => { if (typeof leaf === "string") { return replaceString(leaf) } diff --git a/Models/ThemeConfig/Conversion/LegacyJsonConvert.ts b/Models/ThemeConfig/Conversion/LegacyJsonConvert.ts index f5a5294fb..c1bbcec37 100644 --- a/Models/ThemeConfig/Conversion/LegacyJsonConvert.ts +++ b/Models/ThemeConfig/Conversion/LegacyJsonConvert.ts @@ -126,6 +126,11 @@ class UpdateLegacyTheme extends DesugaringStep { convert(json: LayoutConfigJson, context: string): { result: LayoutConfigJson; errors: string[]; warnings: string[] } { const oldThemeConfig = {...json} + + if(oldThemeConfig.socialImage === ""){ + delete oldThemeConfig.socialImage + } + if (oldThemeConfig["roamingRenderings"] !== undefined) { if (oldThemeConfig["roamingRenderings"].length == 0) { diff --git a/Models/ThemeConfig/Conversion/PrepareLayer.ts b/Models/ThemeConfig/Conversion/PrepareLayer.ts index 70b9815d7..88fc0276e 100644 --- a/Models/ThemeConfig/Conversion/PrepareLayer.ts +++ b/Models/ThemeConfig/Conversion/PrepareLayer.ts @@ -75,14 +75,14 @@ class ExpandTagRendering extends Conversion { new PreparePersonalTheme(state), new OnEveryConcat("layers", new SubstituteLayer(state)), new SetDefault("socialImage", "assets/SocialImage.png", true), + // We expand all tagrenderings first... new OnEvery("layers", new PrepareLayer(state)), + // Then we apply the override all new ApplyOverrideAll(), + // And then we prepare all the layers _again_ in case that an override all contained unexpanded tagrenderings! + new OnEvery("layers", new PrepareLayer(state)), new AddDefaultLayers(state), new AddDependencyLayersToTheme(state), new AddImportLayers(), diff --git a/Models/ThemeConfig/Conversion/Validation.ts b/Models/ThemeConfig/Conversion/Validation.ts index 2e87d706c..f8174c5b0 100644 --- a/Models/ThemeConfig/Conversion/Validation.ts +++ b/Models/ThemeConfig/Conversion/Validation.ts @@ -12,6 +12,7 @@ import {ExtractImages} from "./FixImages"; import ScriptUtils from "../../../scripts/ScriptUtils"; import {And} from "../../../Logic/Tags/And"; import Translations from "../../../UI/i18n/Translations"; +import Svg from "../../../Svg"; class ValidateLanguageCompleteness extends DesugaringStep { @@ -50,12 +51,14 @@ class ValidateTheme extends DesugaringStep { private readonly _path?: string; private readonly knownImagePaths: Set; private readonly _isBuiltin: boolean; + private _sharedTagRenderings: Map; - constructor(knownImagePaths: Set, path: string, isBuiltin: boolean) { + constructor(knownImagePaths: Set, path: string, isBuiltin: boolean, sharedTagRenderings: Map) { super("Doesn't change anything, but emits warnings and errors", [], "ValidateTheme"); this.knownImagePaths = knownImagePaths; this._path = path; this._isBuiltin = isBuiltin; + this._sharedTagRenderings = sharedTagRenderings; } convert(json: LayoutConfigJson, context: string): { result: LayoutConfigJson; errors: string[], warnings: string[], information: string[] } { @@ -78,7 +81,7 @@ class ValidateTheme extends DesugaringStep { } { // Check images: are they local, are the licenses there, is the theme icon square, ... - const images = new ExtractImages(this._isBuiltin).convertStrict(json, "validation") + const images = new ExtractImages(this._isBuiltin, this._sharedTagRenderings).convertStrict(json, "validation") const remoteImages = images.filter(img => img.indexOf("http") == 0) for (const remoteImage of remoteImages) { errors.push("Found a remote image: " + remoteImage + " in theme " + json.id + ", please download it.") @@ -93,8 +96,11 @@ class ValidateTheme extends DesugaringStep { continue } if (image.match(/[a-z]*/)) { - // This is a builtin img, e.g. 'checkmark' or 'crosshair' - continue; + + if(Svg.All[image + ".svg"] !== undefined){ + // This is a builtin img, e.g. 'checkmark' or 'crosshair' + continue; + } } if (this.knownImagePaths !== undefined && !this.knownImagePaths.has(image)) { @@ -163,10 +169,10 @@ class ValidateTheme extends DesugaringStep { } export class ValidateThemeAndLayers extends Fuse { - constructor(knownImagePaths: Set, path: string, isBuiltin: boolean) { + constructor(knownImagePaths: Set, path: string, isBuiltin: boolean, sharedTagRenderings: Map) { super("Validates a theme and the contained layers", - new ValidateTheme(knownImagePaths, path, isBuiltin), - new OnEvery("layers", new ValidateLayer(knownImagePaths, undefined, false)) + new ValidateTheme(knownImagePaths, path, isBuiltin, sharedTagRenderings), + new OnEvery("layers", new ValidateLayer(undefined, false)) ); } } @@ -202,11 +208,29 @@ class OverrideShadowingCheck extends DesugaringStep { } +class MiscThemeChecks extends DesugaringStep{ + constructor() { + super("Miscelleanous checks on the theme", [],"MiscThemesChecks"); + } + + convert(json: LayoutConfigJson, context: string): { result: LayoutConfigJson; errors?: string[]; warnings?: string[]; information?: string[] } { + const warnings = [] + if(json.socialImage === ""){ + warnings.push("Social image for theme "+json.id+" is the emtpy string") + } + return { + result :json, + warnings + }; + } +} + export class PrevalidateTheme extends Fuse { constructor() { super("Various consistency checks on the raw JSON", - new OverrideShadowingCheck() + new OverrideShadowingCheck(), + new MiscThemeChecks() ); } @@ -224,25 +248,32 @@ export class DetectShadowedMappings extends DesugaringStep TagUtils.Tag(m.if)) + const parsedConditions = json.mappings.map(m => { + const ifTags = TagUtils.Tag(m.if); + if(m.hideInAnswer !== undefined && m.hideInAnswer !== false && m.hideInAnswer !== true){ + let conditionTags = TagUtils.Tag( m.hideInAnswer) + // Merge the condition too! + return new And([conditionTags, ifTags]) + } + return ifTags + }) for (let i = 0; i < json.mappings.length; i++) { - if(json.mappings[i].hideInAnswer === true){ + if(!parsedConditions[i].isUsableAsAnswer()){ + // There is no straightforward way to convert this mapping.if into a properties-object, so we simply skip this one + // Yes, it might be shadowed, but running this check is to difficult right now continue } const keyValues = parsedConditions[i].asChange({}); - const properties = [] + const properties = {} keyValues.forEach(({k, v}) => { properties[k] = v }) for (let j = 0; j < i; j++) { - if(json.mappings[j].hideInAnswer === true){ - continue - } const doesMatch = parsedConditions[j].matchesProperties(properties) if (doesMatch) { // The current mapping is shadowed! errors.push(`At ${context}: Mapping ${i} is shadowed by mapping ${j} and will thus never be shown: - The mapping ${parsedConditions[i].asHumanString(false, false, {})} is fully matched by a previous mapping, which matches: + The mapping ${parsedConditions[i].asHumanString(false, false, {})} is fully matched by a previous mapping (namely ${j}), which matches: ${parsedConditions[j].asHumanString(false, false, {})}. Move the mapping up to fix this problem @@ -252,6 +283,10 @@ export class DetectShadowedMappings extends DesugaringStep=0 const images = Utils.Dedup(Translations.T(mapping.then).ExtractImages()) + const ctx = `${context}.mappings[${i}]` if (images.length > 0) { - warnings.push(context + ".mappings[" + i + "]: A mapping has an image in the 'then'-clause. Remove the image there and use `\"icon\": ` instead. The images found are "+images.join(", ")) + if(!ignore){ + errors.push(`${ctx}: A mapping has an image in the 'then'-clause. Remove the image there and use \`"icon": \` instead. The images found are ${images.join(", ")}. (This check can be turned of by adding "#": "${ignoreToken}" in the mapping, but this is discouraged`) + }else{ + information.push(`${ctx}: Ignored image ${images.join(", ")} in 'then'-clause of a mapping as this check has been disabled`) + } + }else if (ignore){ + warnings.push(`${ctx}: unused '${ignoreToken}' - please remove this`) } } - return { + return { + errors, warnings, + information, result: json }; } @@ -289,8 +337,7 @@ export class DetectMappingsWithImages extends DesugaringStep { constructor() { super("Various validation on tagRenderingConfigs", - // TODO enable these checks again - // new DetectShadowedMappings(), + new DetectShadowedMappings(), new DetectMappingsWithImages() ); } @@ -302,12 +349,10 @@ export class ValidateLayer extends DesugaringStep { * @private */ private readonly _path?: string; - private readonly knownImagePaths?: Set; private readonly _isBuiltin: boolean; - constructor(knownImagePaths: Set, path: string, isBuiltin: boolean) { + constructor(path: string, isBuiltin: boolean) { super("Doesn't change anything, but emits warnings and errors", [], "ValidateLayer"); - this.knownImagePaths = knownImagePaths; this._path = path; this._isBuiltin = isBuiltin; } diff --git a/Models/ThemeConfig/Json/TagRenderingConfigJson.ts b/Models/ThemeConfig/Json/TagRenderingConfigJson.ts index 36ccbf3cc..0b08218b5 100644 --- a/Models/ThemeConfig/Json/TagRenderingConfigJson.ts +++ b/Models/ThemeConfig/Json/TagRenderingConfigJson.ts @@ -115,7 +115,7 @@ export interface TagRenderingConfigJson { /** * If the condition `if` is met, the text `then` will be rendered. * If not known yet, the user will be presented with `then` as an option - * type: rendered + * Type: rendered */ then: string | any, /** diff --git a/Models/ThemeConfig/LayoutConfig.ts b/Models/ThemeConfig/LayoutConfig.ts index 1875b8d99..eb22b8443 100644 --- a/Models/ThemeConfig/LayoutConfig.ts +++ b/Models/ThemeConfig/LayoutConfig.ts @@ -71,7 +71,7 @@ export default class LayoutConfig { this.credits = json.credits; this.version = json.version; this.language = json.mustHaveLanguage ?? Array.from(Object.keys(json.title)); - this.usedImages = Array.from(new ExtractImages(official).convertStrict(json, "while extracting the images of " + json.id + " " + context ?? "")).sort() + this.usedImages = Array.from(new ExtractImages(official, undefined).convertStrict(json, "while extracting the images of " + json.id + " " + context ?? "")).sort() { if (typeof json.title === "string") { throw `The title of a theme should always be a translation, as it sets the corresponding languages (${context}.title). The themenID is ${this.id}; the offending object is ${JSON.stringify(json.title)} which is a ${typeof json.title})` diff --git a/Models/ThemeConfig/TagRenderingConfig.ts b/Models/ThemeConfig/TagRenderingConfig.ts index 5c1c9b28c..f0e797aae 100644 --- a/Models/ThemeConfig/TagRenderingConfig.ts +++ b/Models/ThemeConfig/TagRenderingConfig.ts @@ -338,7 +338,8 @@ export default class TagRenderingConfig { const free = this.freeform?.key if (free !== undefined) { - return tags[free] !== undefined + const value = tags[free] + return value !== undefined && value !== "" } return false diff --git a/UI/BigComponents/Histogram.ts b/UI/BigComponents/Histogram.ts index 81daeef55..d786fbf5d 100644 --- a/UI/BigComponents/Histogram.ts +++ b/UI/BigComponents/Histogram.ts @@ -93,7 +93,7 @@ export default class Histogram extends VariableUiElement { keys.sort() break; case "name-rev": - keys.sort().reverse() + keys.sort().reverse(/*Copy of array, inplace reverse if fine*/) break; case "count": keys.sort((k0, k1) => counts.get(k0) - counts.get(k1)) diff --git a/UI/Input/ValidatedTextField.ts b/UI/Input/ValidatedTextField.ts index 75b561319..9359289d4 100644 --- a/UI/Input/ValidatedTextField.ts +++ b/UI/Input/ValidatedTextField.ts @@ -543,9 +543,9 @@ class LengthTextField extends TextFieldDef { // Bit of a hack: we project the centerpoint to the closes point on the road - if available if (options?.feature !== undefined && options.feature.geometry.type !== "Point") { const lonlat = <[number, number]>[...options.location] - lonlat.reverse() + lonlat.reverse(/*Changes a clone, this is safe */) options.location = <[number, number]>GeoOperations.nearestPoint(options.feature, lonlat).geometry.coordinates - options.location.reverse() + options.location.reverse(/*Changes a clone, this is safe */) } diff --git a/UI/Popup/ImportButton.ts b/UI/Popup/ImportButton.ts index 7ad8842e8..3f956c3f0 100644 --- a/UI/Popup/ImportButton.ts +++ b/UI/Popup/ImportButton.ts @@ -373,7 +373,7 @@ export class ImportWayButton extends AbstractImportButton implements AutoAction { name: "max_snap_distance", doc: "If the imported object is a LineString or (Multi)Polygon, already existing OSM-points will be reused to construct the geometry of the newly imported way", - defaultValue: "5" + defaultValue: "0.05" }, { name: "move_osm_point_if", @@ -381,7 +381,7 @@ export class ImportWayButton extends AbstractImportButton implements AutoAction }, { name: "max_move_distance", doc: "If an OSM-point is moved, the maximum amount of meters it is moved. Capped on 20m", - defaultValue: "1" + defaultValue: "0.05" }, { name: "snap_onto_layers", doc: "If no existing nearby point exists, but a line of a specified layer is closeby, snap to this layer instead", @@ -406,24 +406,12 @@ export class ImportWayButton extends AbstractImportButton implements AutoAction AbstractImportButton.importedIds.add(originalFeatureTags.data.id) const args = this.parseArgs(argument, originalFeatureTags) const feature = state.allElements.ContainingFeatures.get(id) - console.log("Geometry to auto-import is:", feature) - const geom = feature.geometry - let coordinates: [number, number][] - if (geom.type === "LineString") { - coordinates = geom.coordinates - } else if (geom.type === "Polygon") { - coordinates = geom.coordinates[0] - } - - const mergeConfigs = this.GetMergeConfig(args); - - const action = this.CreateAction( + const action = ImportWayButton.CreateAction( feature, args, state, - mergeConfigs, - coordinates + mergeConfigs ) await state.changes.applyAction(action) } @@ -455,18 +443,8 @@ export class ImportWayButton extends AbstractImportButton implements AutoAction // Upload the way to OSM - const geom = feature.geometry - let coordinates: [number, number][] - if (geom.type === "LineString") { - coordinates = geom.coordinates - } else if (geom.type === "Polygon") { - coordinates = geom.coordinates[0] - } const mergeConfigs = this.GetMergeConfig(args); - - - let action = this.CreateAction(feature, args, state, mergeConfigs, coordinates); - + let action = ImportWayButton.CreateAction(feature, args, state, mergeConfigs); return this.createConfirmPanelForWay( state, args, @@ -508,14 +486,12 @@ export class ImportWayButton extends AbstractImportButton implements AutoAction return mergeConfigs; } - private CreateAction(feature, + private static CreateAction(feature, args: { max_snap_distance: string; snap_onto_layers: string; icon: string; text: string; tags: string; newTags: UIEventSource; targetLayer: string }, state: FeaturePipelineState, - mergeConfigs: any[], - coordinates: [number, number][]) { - + mergeConfigs: any[]) { const coors = feature.geometry.coordinates - if (feature.geometry.type === "Polygon" && coors.length > 1) { + if ((feature.geometry.type === "Polygon" ) && coors.length > 1) { const outer = coors[0] const inner = [...coors] inner.splice(0, 1) @@ -531,7 +507,7 @@ export class ImportWayButton extends AbstractImportButton implements AutoAction return new CreateWayWithPointReuseAction( args.newTags.data, - coordinates, + coors, state, mergeConfigs ) diff --git a/UI/Popup/LoginButton.ts b/UI/Popup/LoginButton.ts index dd16d6411..ecf859daf 100644 --- a/UI/Popup/LoginButton.ts +++ b/UI/Popup/LoginButton.ts @@ -28,7 +28,6 @@ export class LoginToggle extends VariableUiElement { const login = new LoginButton(text, state) super( state.osmConnection.loadingStatus.map(osmConnectionState => { - console.trace("Current osm state is ", osmConnectionState) if(osmConnectionState === "loading"){ return loading } diff --git a/UI/Popup/SplitRoadWizard.ts b/UI/Popup/SplitRoadWizard.ts index 5bdd70667..8ae8b0158 100644 --- a/UI/Popup/SplitRoadWizard.ts +++ b/UI/Popup/SplitRoadWizard.ts @@ -107,7 +107,7 @@ export default class SplitRoadWizard extends Toggle { .filter(p => GeoOperations.distanceBetween(p[0].geometry.coordinates, coordinates) < 5) .map(p => p[1]) .sort((a, b) => a - b) - .reverse() + .reverse(/*Copy/derived list, inplace reverse is fine*/) if (points.length > 0) { for (const point of points) { splitPoints.data.splice(point, 1) diff --git a/UI/SpecialVisualizations.ts b/UI/SpecialVisualizations.ts index 8a4d6a2cb..97b594375 100644 --- a/UI/SpecialVisualizations.ts +++ b/UI/SpecialVisualizations.ts @@ -45,6 +45,7 @@ import ImgurUploader from "../Logic/ImageProviders/ImgurUploader"; import FileSelectorButton from "./Input/FileSelectorButton"; import {LoginToggle} from "./Popup/LoginButton"; import {start} from "repl"; +import {SubstitutedTranslation} from "./SubstitutedTranslation"; export interface SpecialVisualization { funcName: string, @@ -865,13 +866,14 @@ export default class SpecialVisualizations { args: [], docs:"Shows the title of the popup. Useful for some cases, e.g. 'What is phone number of {title()}?'", example:"`What is the phone number of {title()}`, which might automatically become `What is the phone number of XYZ`.", - constr: (state, tags, args, guistate) => - new VariableUiElement(tags.map(tags => { + constr: (state, tagsSource, args, guistate) => + new VariableUiElement(tagsSource.map(tags => { const layer = state.layoutToUse.getMatchingLayer(tags) - console.log("Layer for tags", tags,"is", layer.id) const title = layer?.title?.GetRenderValue(tags) - console.log("Title became: ", title) - return title + if(title === undefined){ + return undefined + } + return new SubstitutedTranslation(title, tagsSource, state) })) } ] diff --git a/Utils.ts b/Utils.ts index cdf0cb26b..a39f34e88 100644 --- a/Utils.ts +++ b/Utils.ts @@ -304,7 +304,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be if (target === null) { return source } - + for (const key in source) { if (!source.hasOwnProperty(key)) { continue @@ -358,16 +358,16 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be * * The leaf objects are replaced by the function */ - public static WalkPath(path: string[], object: any, replaceLeaf: ((leaf: any) => any)) { + public static WalkPath(path: string[], object: any, replaceLeaf: ((leaf: any, travelledPath: string[]) => any), travelledPath: string[] = []) { const head = path[0] if (path.length === 1) { // We have reached the leaf const leaf = object[head]; if (leaf !== undefined) { - if(Array.isArray(leaf)){ - object[head] = leaf.map(replaceLeaf) - }else{ - object[head] = replaceLeaf(leaf) + if (Array.isArray(leaf)) { + object[head] = leaf.map(o => replaceLeaf(o, travelledPath)) + } else { + object[head] = replaceLeaf(leaf, travelledPath) } } return @@ -381,10 +381,10 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be return; } if (Array.isArray(sub)) { - sub.forEach(el => Utils.WalkPath(path.slice(1), el, replaceLeaf)) + sub.forEach((el, i) => Utils.WalkPath(path.slice(1), el, replaceLeaf, [...travelledPath, head, "" + i])) return; } - Utils.WalkPath(path.slice(1), sub, replaceLeaf) + Utils.WalkPath(path.slice(1), sub, replaceLeaf, [...travelledPath, head]) } /** @@ -393,22 +393,26 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be * * The leaf objects are collected in the list */ - public static CollectPath(path: string[], object: any, collectedList = []): any[] { + public static CollectPath(path: string[], object: any, collectedList: { leaf: any, path: string[] }[] = [], travelledPath: string[] = []): { leaf: any, path: string[] }[] { if (object === undefined || object === null) { return collectedList; } const head = path[0] + travelledPath = [...travelledPath, head] if (path.length === 1) { // We have reached the leaf const leaf = object[head]; if (leaf === undefined || leaf === null) { return collectedList - } - if (Array.isArray(leaf)) { - collectedList.push(...leaf) - } else { - collectedList.push(leaf) + } + if (Array.isArray(leaf)) { + for (let i = 0; i < (leaf).length; i++){ + const l = (leaf)[i]; + collectedList.push({leaf: l, path: [...travelledPath, ""+i]}) } + } else { + collectedList.push({leaf, path: travelledPath}) + } return collectedList } const sub = object[head] @@ -417,13 +421,13 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be } if (Array.isArray(sub)) { - sub.forEach(el => Utils.CollectPath(path.slice(1), el, collectedList)) + sub.forEach((el, i) => Utils.CollectPath(path.slice(1), el, collectedList, [...travelledPath, "" + i])) return collectedList; } if (typeof sub !== "object") { return collectedList; } - return Utils.CollectPath(path.slice(1), sub, collectedList) + return Utils.CollectPath(path.slice(1), sub, collectedList, travelledPath) } /** @@ -725,6 +729,28 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be return new Date(str) } + public static levenshteinDistance(str1: string, str2: string) { + const track = Array(str2.length + 1).fill(null).map(() => + Array(str1.length + 1).fill(null)); + for (let i = 0; i <= str1.length; i += 1) { + track[0][i] = i; + } + for (let j = 0; j <= str2.length; j += 1) { + track[j][0] = j; + } + for (let j = 1; j <= str2.length; j += 1) { + for (let i = 1; i <= str1.length; i += 1) { + const indicator = str1[i - 1] === str2[j - 1] ? 0 : 1; + track[j][i] = Math.min( + track[j][i - 1] + 1, // deletion + track[j - 1][i] + 1, // insertion + track[j - 1][i - 1] + indicator, // substitution + ); + } + } + return track[str2.length][str1.length]; + } + private static colorDiff(c0: { r: number, g: number, b: number }, c1: { r: number, g: number, b: number }) { return Math.abs(c0.r - c1.r) + Math.abs(c0.g - c1.g) + Math.abs(c0.b - c1.b); } @@ -751,27 +777,6 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be b: parseInt(hex.substr(5, 2), 16), } } - - public static levenshteinDistance (str1: string, str2: string) { - const track = Array(str2.length + 1).fill(null).map(() => - Array(str1.length + 1).fill(null)); - for (let i = 0; i <= str1.length; i += 1) { - track[0][i] = i; - } - for (let j = 0; j <= str2.length; j += 1) { - track[j][0] = j; - } - for (let j = 1; j <= str2.length; j += 1) { - for (let i = 1; i <= str1.length; i += 1) { - const indicator = str1[i - 1] === str2[j - 1] ? 0 : 1; - track[j][i] = Math.min( - track[j][i - 1] + 1, // deletion - track[j - 1][i] + 1, // insertion - track[j - 1][i - 1] + indicator, // substitution - ); - } - } - return track[str2.length][str1.length]; - } + } diff --git a/assets/layers/bike_repair_station/license_info.json b/assets/layers/bike_repair_station/license_info.json index 4ad68baaf..fb56c0978 100644 --- a/assets/layers/bike_repair_station/license_info.json +++ b/assets/layers/bike_repair_station/license_info.json @@ -84,6 +84,20 @@ "https://osoc.be/editions/2020/cyclofix" ] }, + { + "path": "repair_station_broken_pump.svg", + "license": "CC-BY-SA", + "authors": [ + "Pieter Fiers", + "Thibault Declercq", + "Pierre Barban", + "Joost Schouppe", + "Pieter Vander Vennet" + ], + "sources": [ + "https://osoc.be/editions/2020/cyclofix" + ] + }, { "path": "repair_station_example.jpg", "license": "CC-BY-SA 4.0", diff --git a/assets/layers/charging_station/charging_station.json b/assets/layers/charging_station/charging_station.json index 613ad070f..edcbd3669 100644 --- a/assets/layers/charging_station/charging_station.json +++ b/assets/layers/charging_station/charging_station.json @@ -241,7 +241,7 @@ }, "hideInAnswer": true, "icon": { - "path": "CEE7_4F.svg", + "path": "./assets/layers/charging_station/CEE7_4F.svg", "class": "medium" } }, @@ -270,7 +270,7 @@ }, "hideInAnswer": true, "icon": { - "path": "TypeE.svg", + "path": "./assets/layers/charging_station/TypeE.svg", "class": "medium" } }, @@ -325,7 +325,7 @@ }, "hideInAnswer": true, "icon": { - "path": "Chademo_type4.svg", + "path": "./assets/layers/charging_station/Chademo_type4.svg", "class": "medium" } }, @@ -380,7 +380,7 @@ }, "hideInAnswer": true, "icon": { - "path": "Type1_J1772.svg", + "path": "./assets/layers/charging_station/Type1_J1772.svg", "class": "medium" } }, @@ -435,7 +435,7 @@ }, "hideInAnswer": true, "icon": { - "path": "Type1_J1772.svg", + "path": "./assets/layers/charging_station/Type1_J1772.svg", "class": "medium" } }, @@ -490,7 +490,7 @@ }, "hideInAnswer": true, "icon": { - "path": "Type1-ccs.svg", + "path": "./assets/layers/charging_station/Type1-ccs.svg", "class": "medium" } }, @@ -545,7 +545,7 @@ }, "hideInAnswer": true, "icon": { - "path": "Tesla-hpwc-model-s.svg", + "path": "./assets/layers/charging_station/Tesla-hpwc-model-s.svg", "class": "medium" } }, @@ -600,7 +600,7 @@ }, "hideInAnswer": true, "icon": { - "path": "Type2_socket.svg", + "path": "./assets/layers/charging_station/Type2_socket.svg", "class": "medium" } }, @@ -655,7 +655,7 @@ }, "hideInAnswer": true, "icon": { - "path": "Type2_CCS.svg", + "path": "./assets/layers/charging_station/Type2_CCS.svg", "class": "medium" } }, @@ -710,7 +710,7 @@ }, "hideInAnswer": true, "icon": { - "path": "Type2_tethered.svg", + "path": "./assets/layers/charging_station/Type2_tethered.svg", "class": "medium" } }, @@ -765,7 +765,7 @@ }, "hideInAnswer": true, "icon": { - "path": "Type2_CCS.svg", + "path": "./assets/layers/charging_station/Type2_CCS.svg", "class": "medium" } }, @@ -826,7 +826,7 @@ }, "hideInAnswer": true, "icon": { - "path": "Tesla-hpwc-model-s.svg", + "path": "./assets/layers/charging_station/Tesla-hpwc-model-s.svg", "class": "medium" } }, @@ -887,7 +887,7 @@ }, "hideInAnswer": true, "icon": { - "path": "Type2_tethered.svg", + "path": "./assets/layers/charging_station/Type2_tethered.svg", "class": "medium" } }, @@ -916,7 +916,7 @@ }, "hideInAnswer": true, "icon": { - "path": "usb_port.svg", + "path": "./assets/layers/charging_station/usb_port.svg", "class": "medium" } }, @@ -967,7 +967,7 @@ }, "hideInAnswer": true, "icon": { - "path": "bosch-3pin.svg", + "path": "./assets/layers/charging_station/bosch-3pin.svg", "class": "medium" } }, @@ -1018,7 +1018,7 @@ }, "hideInAnswer": true, "icon": { - "path": "bosch-5pin.svg", + "path": "./assets/layers/charging_station/bosch-5pin.svg", "class": "medium" } } @@ -3953,7 +3953,7 @@ "operational_status=broken" ] }, - "then": "cross:#c22;" + "then": "close:#c22;" }, { "if": { diff --git a/assets/layers/charging_station/charging_station.protojson b/assets/layers/charging_station/charging_station.protojson index 72fb64d1d..35a2fc40a 100644 --- a/assets/layers/charging_station/charging_station.protojson +++ b/assets/layers/charging_station/charging_station.protojson @@ -764,7 +764,7 @@ "operational_status=broken" ] }, - "then": "cross:#c22;" + "then": "close:#c22;" }, { "if": { diff --git a/assets/layers/charging_station/csvToJson.ts b/assets/layers/charging_station/csvToJson.ts index e138243f7..ea1d55ac9 100644 --- a/assets/layers/charging_station/csvToJson.ts +++ b/assets/layers/charging_station/csvToJson.ts @@ -131,7 +131,7 @@ function run(file, protojson) { then: txt, hideInAnswer: true, icon:{ - path: e.image, + path: `./assets/layers/charging_station/${e.image}`, class:"medium" } } diff --git a/assets/layers/crossings/crossings.json b/assets/layers/crossings/crossings.json index 2c1fb4b6a..dd6a4f7b5 100644 --- a/assets/layers/crossings/crossings.json +++ b/assets/layers/crossings/crossings.json @@ -306,11 +306,15 @@ { "if": "red_turn:right:bicycle=yes", "then": { - "en": "A cyclist can turn right if the light is red ", - "nl": "Een fietser mag wel rechtsaf slaan als het licht rood is ", - "de": "Ein Radfahrer kann bei roter Ampel rechts abbiegen " + "en": "A cyclist can turn right if the light is red", + "nl": "Een fietser mag wel rechtsaf slaan als het licht rood is", + "de": "Ein Radfahrer kann bei roter Ampel rechts abbiegen" }, - "hideInAnswer": "_country!=be" + "hideInAnswer": "_country!=be", + "icon": { + "path": "./assets/layers/crossings/Belgian_road_sign_B22.svg", + "class": "medium" + } }, { "if": "red_turn:right:bicycle=yes", @@ -343,11 +347,15 @@ { "if": "red_turn:straight:bicycle=yes", "then": { - "en": "A cyclist can go straight on if the light is red ", - "nl": "Een fietser mag wel rechtdoor gaan als het licht rood is ", - "de": "Ein Radfahrer kann bei roter Ampel geradeaus fahren " + "en": "A cyclist can go straight on if the light is red", + "nl": "Een fietser mag wel rechtdoor gaan als het licht rood is", + "de": "Ein Radfahrer kann bei roter Ampel geradeaus fahren" }, - "hideInAnswer": "_country!=be" + "hideInAnswer": "_country!=be", + "icon": { + "path": "./assets/layers/crossings/Belgian_road_sign_B23.svg", + "class": "medium" + } }, { "if": "red_turn:straight:bicycle=yes", diff --git a/assets/layers/direction/direction.json b/assets/layers/direction/direction.json index c60322d11..81dc20931 100644 --- a/assets/layers/direction/direction.json +++ b/assets/layers/direction/direction.json @@ -32,16 +32,7 @@ "presets": [], "mapRendering": [ { - "icon": { - "render": "direction_gradient:var(--catch-detail-color)", - "#": "For some weird reason, showing the icon in the layer control panel breaks the svg-gradient (because the svg gradient has a global color or smthng) - so we use a different icon without gradient", - "mappings": [ - { - "if": "id=node/-1", - "then": "direction:var(--catch-detail-color)" - } - ] - }, + "icon": "direction_gradient:var(--catch-detail-color)", "iconSize": "200,200,center", "location": [ "point", diff --git a/assets/layers/recycling/batteries.svg b/assets/layers/recycling/batteries.svg new file mode 100644 index 000000000..2ba547473 --- /dev/null +++ b/assets/layers/recycling/batteries.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/assets/layers/recycling/beverage_cartons.svg b/assets/layers/recycling/beverage_cartons.svg new file mode 100644 index 000000000..05909ef8f --- /dev/null +++ b/assets/layers/recycling/beverage_cartons.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/assets/layers/recycling/cans.svg b/assets/layers/recycling/cans.svg new file mode 100644 index 000000000..2aaff3f9a --- /dev/null +++ b/assets/layers/recycling/cans.svg @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/layers/recycling/clothes.svg b/assets/layers/recycling/clothes.svg new file mode 100644 index 000000000..f99861248 --- /dev/null +++ b/assets/layers/recycling/clothes.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/assets/layers/recycling/cooking_oil.svg b/assets/layers/recycling/cooking_oil.svg new file mode 100644 index 000000000..7a742beab --- /dev/null +++ b/assets/layers/recycling/cooking_oil.svg @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/layers/recycling/engine_oil.svg b/assets/layers/recycling/engine_oil.svg new file mode 100644 index 000000000..f92afd355 --- /dev/null +++ b/assets/layers/recycling/engine_oil.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/assets/layers/recycling/garden_waste.svg b/assets/layers/recycling/garden_waste.svg new file mode 100644 index 000000000..8788c1147 --- /dev/null +++ b/assets/layers/recycling/garden_waste.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/layers/recycling/glass.svg b/assets/layers/recycling/glass.svg new file mode 100644 index 000000000..46488037e --- /dev/null +++ b/assets/layers/recycling/glass.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/layers/recycling/glass_bottles.svg b/assets/layers/recycling/glass_bottles.svg new file mode 100644 index 000000000..7d234e991 --- /dev/null +++ b/assets/layers/recycling/glass_bottles.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/assets/layers/recycling/license_info.json b/assets/layers/recycling/license_info.json new file mode 100644 index 000000000..ec788ff43 --- /dev/null +++ b/assets/layers/recycling/license_info.json @@ -0,0 +1,186 @@ +[ + { + "path": "batteries.svg", + "license": "CC-BY-SA", + "authors": [ + "Tobias Zwick" + ], + "sources": [ + "https://github.com/streetcomplete/StreetComplete/blob/master/res/graphics/recycling%20icons/batteries.svg" + ] + }, + { + "path": "beverage_cartons.svg", + "license": "CC-BY-SA", + "authors": [ + "Tobias Zwick" + ], + "sources": [ + "https://github.com/streetcomplete/StreetComplete/blob/master/res/graphics/recycling%20icons/beverage_cartons.svg" + ] + }, + { + "path": "cans.svg", + "license": "CC-BY-SA", + "authors": [ + "Tobias Zwick" + ], + "sources": [ + "https://github.com/streetcomplete/StreetComplete/blob/master/res/graphics/recycling%20icons/cans.svg" + ] + }, + { + "path": "clothes.svg", + "license": "CC-BY-SA", + "authors": [ + "Adrien Pavie", + "modified from EmojiOne 2: U+1F456, U+1F45" + ], + "sources": [ + "https://github.com/streetcomplete/StreetComplete/blob/master/res/graphics/recycling%20icons/clothes.svg" + ] + }, + { + "path": "cooking_oil.svg", + "license": "CC-BY-SA", + "authors": [ + "Tobias Zwick" + ], + "sources": [ + "https://github.com/streetcomplete/StreetComplete/blob/master/res/graphics/recycling%20icons/cooking_oil.svg" + ] + }, + { + "path": "engine_oil.svg", + "license": "CC-BY-SA", + "authors": [ + "Tobias Zwick" + ], + "sources": [ + "https://github.com/streetcomplete/StreetComplete/blob/master/res/graphics/recycling%20icons/engine_oil.svg" + ] + }, + { + "path": "garden_waste.svg", + "license": "CC-BY-SA", + "authors": [ + "Tobias Zwick", + "modified from Twemoji: U+1F33F" + ], + "sources": [ + "https://github.com/streetcomplete/StreetComplete/blob/master/res/graphics/recycling%20icons/garden_waste.svg" + ] + }, + { + "path": "glass.svg", + "license": "CC-BY-SA", + "authors": [ + "Tobias Zwick" + ], + "sources": [ + "https://github.com/streetcomplete/StreetComplete/blob/master/res/graphics/recycling%20icons/glass.svg" + ] + }, + { + "path": "glass_bottles.svg", + "license": "CC-BY-SA", + "authors": [ + "Tobias Zwick" + ], + "sources": [ + "https://github.com/streetcomplete/StreetComplete/blob/master/res/graphics/recycling%20icons/glass_bottles.svg" + ] + }, + { + "path": "newspaper.svg", + "license": "CC-BY-SA", + "authors": [ + "Tobias Zwick", + "modified from EmojiOne 2: U+1F4F0" + ], + "sources": [ + "https://github.com/streetcomplete/StreetComplete/blob/master/res/graphics/recycling%20icons/newspaper.svg" + ] + }, + { + "path": "paper.svg", + "license": "CC-BY-SA", + "authors": [ + "Tobias Zwick" + ], + "sources": [ + "https://github.com/streetcomplete/StreetComplete/blob/master/res/graphics/recycling%20icons/paper.svg" + ] + }, + { + "path": "plastic.svg", + "license": "CC-BY-SA", + "authors": [ + "Tobias Zwick" + ], + "sources": [ + "https://github.com/streetcomplete/StreetComplete/blob/master/res/graphics/recycling%20icons/plastic.svg" + ] + }, + { + "path": "plastic_bottles.svg", + "license": "CC-BY-SA", + "authors": [ + "Tobias Zwick" + ], + "sources": [ + "https://github.com/streetcomplete/StreetComplete/blob/master/res/graphics/recycling%20icons/plastic_bottles.svg" + ] + }, + { + "path": "plastic_packaging.svg", + "license": "CC-BY-SA", + "authors": [ + "Tobias Zwick" + ], + "sources": [ + "https://github.com/streetcomplete/StreetComplete/blob/master/res/graphics/recycling%20icons/plastic_packaging.svg" + ] + }, + { + "path": "recycling-14.svg", + "license": "CC0", + "authors": [ + "Michael Glanznig" + ], + "sources": [ + "https://github.com/gmgeo/osmic/blob/master/amenity/recycling-14.svg" + ] + }, + { + "path": "scrap_metal.svg", + "license": "CC-BY-SA", + "authors": [ + "Tobias Zwick" + ], + "sources": [ + "https://github.com/streetcomplete/StreetComplete/blob/master/res/graphics/recycling%20icons/scrap_metal.svg" + ] + }, + { + "path": "shoes.svg", + "license": "CC-BY-SA", + "authors": [ + "Tobias Zwick", + "modified from EmojiOne 2: U+1F45F" + ], + "sources": [ + "https://github.com/streetcomplete/StreetComplete/blob/master/res/graphics/recycling%20icons/shoes.svg" + ] + }, + { + "path": "small_electrical_appliances.svg", + "license": "CC-BY-SA", + "authors": [ + "EmojiOne 2: U+1F50C" + ], + "sources": [ + "https://github.com/streetcomplete/StreetComplete/blob/master/res/graphics/recycling%20icons/small_electrical_appliances.svg" + ] + } +] \ No newline at end of file diff --git a/assets/layers/recycling/newspaper.svg b/assets/layers/recycling/newspaper.svg new file mode 100644 index 000000000..19ffa9d54 --- /dev/null +++ b/assets/layers/recycling/newspaper.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/assets/layers/recycling/paper.svg b/assets/layers/recycling/paper.svg new file mode 100644 index 000000000..76cb42217 --- /dev/null +++ b/assets/layers/recycling/paper.svg @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/layers/recycling/plastic.svg b/assets/layers/recycling/plastic.svg new file mode 100644 index 000000000..755c2e865 --- /dev/null +++ b/assets/layers/recycling/plastic.svg @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/layers/recycling/plastic_bottles.svg b/assets/layers/recycling/plastic_bottles.svg new file mode 100644 index 000000000..669312972 --- /dev/null +++ b/assets/layers/recycling/plastic_bottles.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/layers/recycling/plastic_packaging.svg b/assets/layers/recycling/plastic_packaging.svg new file mode 100644 index 000000000..7ae9d650f --- /dev/null +++ b/assets/layers/recycling/plastic_packaging.svg @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/layers/recycling/recycling-14.svg b/assets/layers/recycling/recycling-14.svg new file mode 100644 index 000000000..6244f4124 --- /dev/null +++ b/assets/layers/recycling/recycling-14.svg @@ -0,0 +1,40 @@ + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/assets/layers/recycling/recycling.json b/assets/layers/recycling/recycling.json new file mode 100644 index 000000000..bab282396 --- /dev/null +++ b/assets/layers/recycling/recycling.json @@ -0,0 +1,999 @@ +{ + "id": "recycling", + "name": { + "en": "Recycling", + "nl": "Recycling" + }, + "description": { + "en": "A layer with recycling containers and centres", + "nl": "Een laag met recyclagingcontainers en -centrums" + }, + "source": { + "osmTags": "amenity=recycling" + }, + "calculatedTags": [ + "_waste_amount=Object.values(Object.keys(feat.properties).filter((key) => key.startsWith('recycling:')).reduce((cur, key) => { return Object.assign(cur, { [key]: feat.properties[key] })}, {})).reduce((n, x) => n + (x == \"yes\"), 0);" + ], + "minzoom": 12, + "title": { + "render": { + "en": "Recycling facility", + "nl": "Recyclingfaciliteit" + }, + "mappings": [ + { + "if": "name~*", + "then": { + "*": "{name}", + "en": "Recycling centre", + "nl": "Recyclingcentrum" + } + }, + { + "if": "recycling_type=centre", + "then": { + "en": "Recycling container", + "nl": "Recyclingcontainer" + } + }, + { + "if": "recycling_type=container", + "then": { + "en": "Recycling container", + "nl": "Recyclingcontainer" + } + } + ] + }, + "mapRendering": [ + { + "location": [ + "point", + "centroid" + ], + "icon": { + "render": "circle:white;./assets/layers/recycling/recycling-14.svg", + "mappings": [ + { + "if": { + "and": [ + "_waste_amount=1", + "recycling:batteries=yes" + ] + }, + "then": "circle:white;./assets/layers/recycling/batteries.svg" + }, + { + "if": { + "and": [ + "_waste_amount=1", + "recycling:beverage_cartons=yes" + ] + }, + "then": "circle:white;./assets/layers/recycling/beverage_cartons.svg" + }, + { + "if": { + "and": [ + "_waste_amount=1", + "recycling:cans=yes" + ] + }, + "then": "circle:white;./assets/layers/recycling/cans.svg" + }, + { + "if": { + "and": [ + "_waste_amount=1", + "recycling:clothes=yes" + ] + }, + "then": "circle:white;./assets/layers/recycling/clothes.svg" + }, + { + "if": { + "and": [ + "_waste_amount=1", + "recycling:cooking_oil=yes" + ] + }, + "then": "circle:white;./assets/layers/recycling/cooking_oil.svg" + }, + { + "if": { + "and": [ + "_waste_amount=1", + "recycling:engine_oil=yes" + ] + }, + "then": "circle:white;./assets/layers/recycling/engine_oil.svg" + }, + { + "if": { + "and": [ + "_waste_amount=1", + "recycling:glass=yes" + ] + }, + "then": "circle:white;./assets/layers/recycling/glass.svg" + }, + { + "if": { + "and": [ + "_waste_amount=1", + "recycling:glass_bottles=yes" + ] + }, + "then": "circle:white;./assets/layers/recycling/glass_bottles.svg" + }, + { + "if": { + "and": [ + "_waste_amount=1", + { + "or": [ + "recycling:green_waste=yes", + "recycling:organic=yes" + ] + } + ] + }, + "then": "circle:white;./assets/layers/recycling/garden_waste.svg" + }, + { + "if": { + "and": [ + "_waste_amount=1", + "recycling:newspaper=yes" + ] + }, + "then": "circle:white;./assets/layers/recycling/newspaper.svg" + }, + { + "if": { + "and": [ + "_waste_amount=1", + "recycling:paper=yes" + ] + }, + "then": "circle:white;./assets/layers/recycling/paper.svg" + }, + { + "if": { + "and": [ + "_waste_amount=1", + "recycling:plastic_bottles=yes" + ] + }, + "then": "circle:white;./assets/layers/recycling/plastic_bottles.svg" + }, + { + "if": { + "and": [ + "_waste_amount=1", + "recycling:plastic_packaging=yes" + ] + }, + "then": "circle:white;./assets/layers/recycling/plastic_packaging.svg" + }, + { + "if": { + "and": [ + "_waste_amount=1", + "recycling:plastic=yes" + ] + }, + "then": "circle:white;./assets/layers/recycling/plastic.svg" + }, + { + "if": { + "and": [ + "_waste_amount=1", + "recycling:scrap_metal=yes" + ] + }, + "then": "circle:white;./assets/layers/recycling/scrap_metal.svg" + }, + { + "if": { + "and": [ + "_waste_amount=1", + "recycling:shoes=yes" + ] + }, + "then": "circle:white;./assets/layers/recycling/shoes.svg" + }, + { + "if": { + "and": [ + "_waste_amount=1", + { + "or": [ + "recycling:small_appliances=yes", + "recycling:small_electric_appliances=yes" + ] + } + ] + }, + "then": "circle:white;./assets/layers/recycling/small_electrical_appliances.svg" + }, + { + "if": { + "and": [ + "_waste_amount=1", + "recycling:waste=yes" + ] + }, + "then": "circle:white;./assets/layers/waste_disposal/waste_disposal.svg" + } + ] + }, + "iconBadges": [ + { + "if": { + "and": [ + "_waste_amount>1", + "recycling:batteries=yes" + ] + }, + "then": "circle:white;./assets/layers/recycling/batteries.svg" + }, + { + "if": { + "and": [ + "_waste_amount>1", + "recycling:beverage_cartons=yes" + ] + }, + "then": "circle:white;./assets/layers/recycling/beverage_cartons.svg" + }, + { + "if": { + "and": [ + "_waste_amount>1", + "recycling:cans=yes" + ] + }, + "then": "circle:white;./assets/layers/recycling/cans.svg" + }, + { + "if": { + "and": [ + "_waste_amount>1", + "recycling:clothes=yes" + ] + }, + "then": "circle:white;./assets/layers/recycling/clothes.svg" + }, + { + "if": { + "and": [ + "_waste_amount>1", + "recycling:cooking_oil=yes" + ] + }, + "then": "circle:white;./assets/layers/recycling/cooking_oil.svg" + }, + { + "if": { + "and": [ + "_waste_amount>1", + "recycling:engine_oil=yes" + ] + }, + "then": "circle:white;./assets/layers/recycling/engine_oil.svg" + }, + { + "if": { + "and": [ + "_waste_amount>1", + "recycling:glass=yes" + ] + }, + "then": "circle:white;./assets/layers/recycling/glass.svg" + }, + { + "if": { + "and": [ + "_waste_amount>1", + "recycling:glass_bottles=yes" + ] + }, + "then": "circle:white;./assets/layers/recycling/glass_bottles.svg" + }, + { + "if": { + "and": [ + "_waste_amount>1", + { + "or": [ + "recycling:green_waste=yes", + "recycling:organic=yes" + ] + } + ] + }, + "then": "circle:white;./assets/layers/recycling/garden_waste.svg" + }, + { + "if": { + "and": [ + "_waste_amount>1", + "recycling:newspaper=yes" + ] + }, + "then": "circle:white;./assets/layers/recycling/newspaper.svg" + }, + { + "if": { + "and": [ + "_waste_amount>1", + "recycling:paper=yes" + ] + }, + "then": "circle:white;./assets/layers/recycling/paper.svg" + }, + { + "if": { + "and": [ + "_waste_amount>1", + "recycling:plastic_bottles=yes" + ] + }, + "then": "circle:white;./assets/layers/recycling/plastic_bottles.svg" + }, + { + "if": { + "and": [ + "_waste_amount>1", + "recycling:plastic_packaging=yes" + ] + }, + "then": "circle:white;./assets/layers/recycling/plastic_packaging.svg" + }, + { + "if": { + "and": [ + "_waste_amount>1", + "recycling:plastic=yes" + ] + }, + "then": "circle:white;./assets/layers/recycling/plastic.svg" + }, + { + "if": { + "and": [ + "_waste_amount>1", + "recycling:scrap_metal=yes" + ] + }, + "then": "circle:white;./assets/layers/recycling/scrap_metal.svg" + }, + { + "if": { + "and": [ + "_waste_amount>1", + "recycling:shoes=yes" + ] + }, + "then": "circle:white;./assets/layers/recycling/shoes.svg" + }, + { + "if": { + "and": [ + "_waste_amount>1", + { + "or": [ + "recycling:small_appliances=yes", + "recycling:small_electrical_appliances=yes" + ] + } + ] + }, + "then": "circle:white;./assets/layers/recycling/small_electrical_appliances.svg" + }, + { + "if": { + "and": [ + "_waste_amount>1", + "recycling:waste=yes" + ] + }, + "then": "circle:white;./assets/layers/waste_disposal/waste_disposal.svg" + } + ] + } + ], + "presets": [ + { + "title": { + "en": "recycling container", + "nl": "recycling container" + }, + "tags": [ + "amenity=recycling", + "recycling_type=container" + ] + }, + { + "title": { + "en": "recycling centre", + "nl": "recycling centre" + }, + "tags": [ + "amenity=recycling", + "recycling_type=centre" + ] + } + ], + "tagRenderings": [ + "images", + { + "id": "recycling-type", + "question": { + "en": "What type of recycling is this?", + "nl": "Wat voor soort recycling is dit?" + }, + "mappings": [ + { + "if": "recycling_type=container", + "then": { + "en": "This is a recycling container", + "nl": "Dit is een recyclingcontainer" + } + }, + { + "if": "recycling_type=centre", + "then": { + "en": "This is a recycling centre", + "nl": "Dit is een recyclingcentrum" + } + }, + { + "if": "amenity=waste_disposal", + "then": { + "en": "Waste disposal container for residual waste", + "nl": "Afvalcontainer voor restafval" + }, + "addExtraTags": [ + "recycling:batteries=", + "recycling:beverage_cartons=", + "recycling:cans=", + "recycling:clothes=", + "recycling:cooking_oil=", + "recycling:engine_oil=", + "recycling:green_waste=", + "recycling:organic=", + "recycling:glass_bottles=", + "recycling:glass=", + "recycling:newspaper=", + "recycling:paper=", + "recycling:plastic_bottles=", + "recycling:plastic_packaging=", + "recycling:plastic=", + "recycling:scrap_metal=", + "recycling:shoes=", + "recycling:small_appliances=", + "recycling:small_electrical_appliances=", + "recycling:waste=" + ] + } + ] + }, + { + "id": "recycling-centre-name", + "question": { + "en": "What is the name of this recycling centre?", + "nl": "Wat is de naam van dit recyclagecentrum?" + }, + "render": { + "en": "This recycling centre is named {name}", + "nl": "Dit recyclagecentrum heet {name}" + }, + "freeform": { + "key": "name" + }, + "mappings": [ + { + "if": "noname=yes", + "then": { + "en": "This recycling centre doesn't have a specific name", + "nl": "Dit recyclagecentrum heeft geen specifieke naam" + } + } + ], + "condition": "recycling_type=centre" + }, + { + "id": "container-location", + "question": { + "en": "Where is this container located?", + "nl": "Waar bevindt deze container zich?" + }, + "condition": "recycling_type=container", + "mappings": [ + { + "if": "location=underground", + "then": { + "en": "This is an underground container", + "nl": "Dit is een ondergrondse container" + } + }, + { + "if": "location=indoor", + "then": { + "en": "This container is located indoors", + "nl": "Deze container bevindt zich binnen" + } + }, + { + "if": "location=", + "then": { + "en": "This container is located outdoors", + "nl": "Deze container is buiten" + } + } + ] + }, + { + "id": "recycling-accepts", + "question": { + "en": "What can be recycled here?", + "nl": "Wat kan hier gerecycled worden?" + }, + "multiAnswer": true, + "mappings": [ + { + "if": "recycling:batteries=yes", + "ifnot": "recycling:batteries=", + "then": { + "en": "Batteries can be recycled here", + "nl": "Batterijen kunnen hier gerecycled worden" + }, + "icon": { + "path": "./assets/layers/recycling/batteries.svg", + "class": "medium" + } + }, + { + "if": "recycling:beverage_cartons=yes", + "ifnot": "recycling:beverage_cartons=", + "then": { + "en": "Beverage cartons can be recycled here", + "nl": "Drankpakken kunnen hier gerecycled worden" + }, + "icon": { + "path": "./assets/layers/recycling/beverage_cartons.svg", + "class": "medium" + } + }, + { + "if": "recycling:cans=yes", + "ifnot": "recycling:cans=", + "then": { + "en": "Cans can be recycled here", + "nl": "Blikken kunnen hier gerecycled worden" + }, + "icon": { + "path": "./assets/layers/recycling/cans.svg", + "class": "medium" + } + }, + { + "if": "recycling:clothes=yes", + "ifnot": "recycling:clothes=", + "then": { + "en": "Clothes can be recycled here", + "nl": "Kleren kunnen hier gerecycled worden" + }, + "icon": { + "path": "./assets/layers/recycling/clothes.svg", + "class": "medium" + } + }, + { + "if": "recycling:cooking_oil=yes", + "ifnot": "recycling:cooking_oil=", + "then": { + "en": "Cooking oil can be recycled here", + "nl": "Frituurvet kan hier gerecycled worden" + }, + "icon": { + "path": "./assets/layers/recycling/cooking_oil.svg", + "class": "medium" + } + }, + { + "if": "recycling:engine_oil=yes", + "ifnot": "recycling:engine_oil=", + "then": { + "en": "Engine oil can be recycled here", + "nl": "Motorolie kan hier gerecycled worden" + }, + "icon": { + "path": "./assets/layers/recycling/engine_oil.svg", + "class": "medium" + } + }, + { + "if": "recycling:green_waste=yes", + "ifnot": "recycling:green_waste=", + "then": { + "en": "Green waste can be recycled here", + "nl": "Groen afval kan hier gerecycled worden" + }, + "icon": { + "path": "./assets/layers/recycling/garden_waste.svg", + "class": "medium" + } + }, + { + "if": "recycling:organic=yes", + "ifnot": "recycling:organic=", + "then": { + "en": "Organic waste can be recycled here", + "nl": "Organisch afval kan hier gerecycled worden" + }, + "hideInAnswer": true, + "icon": { + "path": "./assets/layers/recycling/garden_waste.svg", + "class": "medium" + } + }, + { + "if": "recycling:glass_bottles=yes", + "ifnot": "recycling:glass_bottles=", + "then": { + "en": "Glass bottles can be recycled here", + "nl": "Glazen flessen kunnen hier gerecycled worden" + }, + "icon": { + "path": "./assets/layers/recycling/glass_bottles.svg", + "class": "medium" + } + }, + { + "if": "recycling:glass=yes", + "ifnot": "recycling:glass=", + "then": { + "en": "Glass can be recycled here", + "nl": "Glas kan hier gerecycled worden" + }, + "icon": { + "path": "./assets/layers/recycling/glass.svg", + "class": "medium" + } + }, + { + "if": "recycling:newspaper=yes", + "ifnot": "recycling:newspaper=", + "then": { + "en": "Newspapers can be recycled here", + "nl": "Kranten kunnen hier gerecycled worden" + }, + "icon": { + "path": "./assets/layers/recycling/newspaper.svg", + "class": "medium" + } + }, + { + "if": "recycling:paper=yes", + "ifnot": "recycling:paper=", + "then": { + "en": "Paper can be recycled here", + "nl": "Papier kan hier gerecycled worden" + }, + "icon": { + "path": "./assets/layers/recycling/paper.svg", + "class": "medium" + } + }, + { + "if": "recycling:plastic_bottles=yes", + "ifnot": "recycling:plastic_bottles=", + "then": { + "en": "Plastic bottles can be recycled here", + "nl": "Plastic flessen kunnen hier gerecycled worden" + }, + "icon": { + "path": "./assets/layers/recycling/plastic_bottles.svg", + "class": "medium" + } + }, + { + "if": "recycling:plastic_packaging=yes", + "ifnot": "recycling:plastic_packaging=", + "then": { + "en": "Plastic packaging can be recycled here", + "nl": "Plastic verpakking kan hier gerecycled worden" + }, + "icon": { + "path": "./assets/layers/recycling/plastic_packaging.svg", + "class": "medium" + } + }, + { + "if": "recycling:plastic=yes", + "ifnot": "recycling:plastic=", + "then": { + "en": "Plastic can be recycled here", + "nl": "Plastic kan hier gerecycled worden" + }, + "icon": { + "path": "./assets/layers/recycling/plastic.svg", + "class": "medium" + } + }, + { + "if": "recycling:scrap_metal=yes", + "ifnot": "recycling:scrap_metal=", + "then": { + "en": "Scrap metal can be recycled here", + "nl": "Oud metaal kan hier gerecycled worden" + }, + "icon": { + "path": "./assets/layers/recycling/scrap_metal.svg", + "class": "medium" + } + }, + { + "if": "recycling:shoes=yes", + "ifnot": "recycling:shoes=", + "then": { + "en": "Shoes can be recycled here", + "nl": "Schoenen kunnen hier gerecycled worden" + }, + "icon": { + "path": "./assets/layers/recycling/shoes.svg", + "class": "medium" + } + }, + { + "if": "recycling:small_appliances=yes", + "ifnot": "recycling:small_appliances=", + "then": { + "en": "Small electrical appliances can be recycled here", + "nl": "Kleine elektrische apparaten kunnen hier gerecycled worden" + }, + "icon": { + "path": "./assets/layers/recycling/small_electrical_appliances.svg", + "class": "medium" + } + }, + { + "if": "recycling:small_electrical_appliances=yes", + "ifnot": "recycling:small_electrical_appliances=", + "then": { + "en": "Small electrical appliances can be recycled here", + "nl": "Kleine elektrische apparaten kunnen hier gerecycled worden" + }, + "hideInAnswer": true, + "icon": { + "path": "./assets/layers/recycling/small_electrical_appliances.svg", + "class": "medium" + } + }, + { + "if": "recycling:waste=yes", + "ifnot": "recycling:waste=", + "then": { + "en": "Residual waste can be recycled here", + "nl": "Restafval kan hier gerecycled worden" + }, + "icon": { + "path": "./assets/layers/waste_disposal/waste_disposal.svg", + "class": "medium" + }, + "hideInAnswer": "recycling_type=container" + } + ] + }, + { + "id": "operator", + "render": { + "en": "This recycling facility is operated by {operator}", + "nl": "Deze recyclingfaciliteit wordt beheerd door {operator}" + }, + "question": { + "en": "What company operates this recycling facility?", + "nl": "Wat is de beheerder van deze recyclingfaciliteit?" + }, + "freeform": { + "key": "operator", + "type": "string" + } + }, + { + "builtin": [ + "website", + "email", + "phone" + ], + "override": { + "condition": "recycling_type=centre" + } + }, + { + "id": "opening_hours", + "render": "{opening_hours_table()}", + "question": { + "en": "What are the opening hours of this recycling facility?", + "nl": "Wat zijn de openingstijden van deze recyclingfaciliteit?" + }, + "freeform": { + "key": "opening_hours", + "type": "opening_hours" + }, + "mappings": [ + { + "if": "opening_hours=24/7", + "then": { + "en": "24/7", + "nl": "24/7" + } + } + ] + } + ], + "filter": [ + { + "id": "isOpen", + "options": [ + { + "question": { + "en": "Currently open", + "nl": "Op dit moment open" + }, + "osmTags": "_isOpen=yes" + } + ] + }, + { + "id": "recyclingType", + "options": [ + { + "question": { + "en": "All recycling types", + "nl": "Alle recyclingtypes" + } + }, + { + "question": { + "en": "Recycling of batteries", + "nl": "Recycling van batterijen" + }, + "osmTags": "recycling:batteries=yes" + }, + { + "question": { + "en": "Recycling of beverage cartons", + "nl": "Recycling van drankpakken" + }, + "osmTags": "recycling:beverage_cartons=yes" + }, + { + "question": { + "en": "Recycling of cans", + "nl": "Recycling van blikken" + }, + "osmTags": "recycling:cans=yes" + }, + { + "question": { + "en": "Recycling of clothes", + "nl": "Recycling van kleding" + }, + "osmTags": "recycling:clothes=yes" + }, + { + "question": { + "en": "Recycling of cooking oil", + "nl": "Recycling van frituurvet" + }, + "osmTags": "recycling:cooking_oil=yes" + }, + { + "question": { + "en": "Recycling of engine oil", + "nl": "Recycling van motorolie" + }, + "osmTags": "recycling:engine_oil=yes" + }, + { + "question": { + "en": "Recycling of green waste", + "nl": "Recycling van groen afval" + }, + "osmTags": { + "or": [ + "recycling:green_waste=yes", + "recycling:organic=yes" + ] + } + }, + { + "question": { + "en": "Recycling of glass bottles", + "nl": "Recycling van glazen flessen" + }, + "osmTags": "recycling:glass_bottles=yes" + }, + { + "question": { + "en": "Recycling of glass", + "nl": "Recycling van glas" + }, + "osmTags": "recycling:glass=yes" + }, + { + "question": { + "en": "Recycling of newspapers", + "nl": "Recycling van kranten" + }, + "osmTags": "recycling:newspaper=yes" + }, + { + "question": { + "en": "Recycling of paper", + "nl": "Recycling van papier" + }, + "osmTags": "recycling:paper=yes" + }, + { + "question": { + "en": "Recycling of plastic bottles", + "nl": "Recycling van plastic flessen" + }, + "osmTags": "recycling:plastic_bottles=yes" + }, + { + "question": { + "en": "Recycling of plastic packaging", + "nl": "Recycling van plastic verpakking" + }, + "osmTags": "recycling:plastic_packaging=yes" + }, + { + "question": { + "en": "Recycling of plastic", + "nl": "Recycling van plastic" + }, + "osmTags": "recycling:plastic=yes" + }, + { + "question": { + "en": "Recycling of scrap metal", + "nl": "Recycling van oud metaal" + }, + "osmTags": "recycling:scrap_metal=yes" + }, + { + "question": { + "en": "Recycling of small electrical appliances", + "nl": "Recycling van kleine elektrische apparaten" + }, + "osmTags": { + "or": [ + "recycling:small_appliances=yes", + "recycling:small_electrical_appliances=yes" + ] + } + }, + { + "question": { + "en": "Recycling of residual waste", + "nl": "Recycling van restafval" + }, + "osmTags": "recycling:waste=yes" + } + ] + } + ], + "deletion": { + "neededChangesets": 1 + }, + "allowMove": { + "enableRelocation": false, + "enableImproveAccuracy": true + } +} \ No newline at end of file diff --git a/assets/layers/recycling/scrap_metal.svg b/assets/layers/recycling/scrap_metal.svg new file mode 100644 index 000000000..9ff4dfeeb --- /dev/null +++ b/assets/layers/recycling/scrap_metal.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/assets/layers/recycling/shoes.svg b/assets/layers/recycling/shoes.svg new file mode 100644 index 000000000..0a5bd834e --- /dev/null +++ b/assets/layers/recycling/shoes.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/layers/recycling/small_electrical_appliances.svg b/assets/layers/recycling/small_electrical_appliances.svg new file mode 100644 index 000000000..b5529c91f --- /dev/null +++ b/assets/layers/recycling/small_electrical_appliances.svg @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/layers/slow_roads/slow_roads.json b/assets/layers/slow_roads/slow_roads.json index 2964b7808..9984d916f 100644 --- a/assets/layers/slow_roads/slow_roads.json +++ b/assets/layers/slow_roads/slow_roads.json @@ -71,7 +71,11 @@ { "if": "highway=living_street", "then": { - "nl:": "
Dit is een woonerf:
  • Voetgangers mogen hier de volledige breedte van de straat gebruiken
  • Gemotoriseerd verkeer mag maximaal 20km/h rijden
" + "nl:": "
Dit is een woonerf:
  • Voetgangers mogen hier de volledige breedte van de straat gebruiken
  • Gemotoriseerd verkeer mag maximaal 20km/h rijden
" + }, + "icon": { + "path": "./assets/layers/slow_roads/woonerf.svg", + "class": "medium" } }, { diff --git a/assets/layers/trail/trail.json b/assets/layers/trail/trail.json index 84416e15f..0ec5f307b 100644 --- a/assets/layers/trail/trail.json +++ b/assets/layers/trail/trail.json @@ -77,7 +77,11 @@ ] }, "then": { - "nl": "Dit gebied wordt beheerd door Natuurpunt" + "nl": "Dit gebied wordt beheerd door Natuurpunt" + }, + "icon": { + "path": "./assets/themes/buurtnatuur/Natuurpunt.jpg", + "class": "small" } }, { @@ -87,9 +91,13 @@ ] }, "then": { - "nl": "Dit gebied wordt beheerd door {operator}" + "nl": "Dit gebied wordt beheerd door {operator}" }, - "hideInAnswer": true + "hideInAnswer": true, + "icon": { + "path": "./assets/themes/buurtnatuur/Natuurpunt.jpg", + "class": "small" + } } ], "id": "Operator tag" diff --git a/assets/layers/waste_basket/waste_basket.json b/assets/layers/waste_basket/waste_basket.json index 6a18e9b0c..d78595b87 100644 --- a/assets/layers/waste_basket/waste_basket.json +++ b/assets/layers/waste_basket/waste_basket.json @@ -86,6 +86,14 @@ "nl": "Een vuilnisbak voor injectienaalden en andere scherpe voorwerpen", "de": "Ein Abfalleimer für Nadeln und andere scharfe Gegenstände" } + }, + { + "if": "waste=plastic", + "then": { + "en": "A waste basket for plastic", + "nl": "Een vuilnisbak voor plastic", + "de": "Ein Abfalleimer für Plastik" + } } ] }, @@ -137,7 +145,7 @@ "nl": "Deze vuilnisbak heeft waarschijnlijk geen verdeler voor hondenpoepzakjes", "de": "Dieser Abfalleimer hat keinen Spender für (Hunde-)Kotbeutel" }, - "hideInAnwer": true + "hideInAnswer": true } ] } @@ -159,6 +167,81 @@ } } ], + "filter": [ + { + "id": "waste-type", + "options": [ + { + "question": { + "en": "All types", + "nl": "Alle soorten", + "de": "Alle Typen" + } + }, + { + "question": { + "en": "Waste basket for cigarettes", + "nl": "Vuilnisbak voor sigarettenpeuken", + "de": "Mülleimer für Zigaretten" + }, + "osmTags": "waste~.*cigarettes.*" + }, + { + "question": { + "en": "Waste basket for drugs", + "nl": "Vuilnisbak voor (vervallen) medicatie en drugs", + "de": "Mülleimer für Drogen" + }, + "osmTags": "waste~.*drugs.*" + }, + { + "question": { + "en": "Waste basket for dog excrement", + "nl": "Vuilnisbak voor hondenuitwerpselen", + "de": "Mülleimer für Hundekot" + }, + "osmTags": "waste~.*dog_excrement.*" + }, + { + "question": { + "en": "Waste basket for trash", + "nl": "Vuilnisbak voor zwerfvuil", + "de": "Mülleimer für allgemeinen Müll" + }, + "osmTags": "waste~.*trash.*" + }, + { + "question": { + "en": "Waste basket for sharps", + "nl": "Vuilnisbak voor injectienaalden en andere scherpe voorwerpen", + "de": "Mülleimer für Nadeln und andere scharfe Gegenstände" + }, + "osmTags": "waste~.*sharps.*" + }, + { + "question": { + "en": "Waste basket for plastic", + "nl": "Vuilnisbak voor plastic", + "de": "Mülleimer für Plastik" + }, + "osmTags": "waste~.*plastic.*" + } + ] + }, + { + "id": "waste-vending", + "options": [ + { + "question": { + "en": "Waste basket with dispenser for (dog) excrement bags", + "nl": "Vuilnisbak met verdeler voor hondenpoepzakjes", + "de": "Abfalleimer mit Spender für (Hunde-)Kotbeutel" + }, + "osmTags": "vending=dog_excrement_bag" + } + ] + } + ], "deletion": { "softDeletionTags": { "and": [ diff --git a/assets/layers/waste_disposal/license_info.json b/assets/layers/waste_disposal/license_info.json new file mode 100644 index 000000000..aeeec767e --- /dev/null +++ b/assets/layers/waste_disposal/license_info.json @@ -0,0 +1,12 @@ +[ + { + "path": "waste_disposal.svg", + "license": "CC0", + "authors": [ + "kocio-pl" + ], + "sources": [ + "https://github.com/gravitystorm/openstreetmap-carto/blob/master/symbols/amenity/waste_disposal.svg" + ] + } +] \ No newline at end of file diff --git a/assets/layers/waste_disposal/waste_disposal.json b/assets/layers/waste_disposal/waste_disposal.json new file mode 100644 index 000000000..a589d75d1 --- /dev/null +++ b/assets/layers/waste_disposal/waste_disposal.json @@ -0,0 +1,119 @@ +{ + "id": "waste_disposal", + "name": { + "en": "Waste Disposal Bins" + }, + "description": { + "en": "Waste Disposal Bin, medium to large bin for disposal of (household) waste" + }, + "source": { + "osmTags": "amenity=waste_disposal" + }, + "minzoom": 18, + "minzoomVisible": 18, + "title": { + "render": { + "en": "Waste Disposal" + } + }, + "mapRendering": [ + { + "location": [ + "point", + "centroid" + ], + "icon": "circle:white;./assets/layers/waste_disposal/waste_disposal.svg" + } + ], + "presets": [ + { + "title": { + "en": "Waste Disposal Bin" + }, + "tags": [ + "amenity=waste_disposal" + ], + "description": { + "en": "Medium to large bin for disposal of (household) waste" + } + } + ], + "tagRenderings": [ + { + "id": "access", + "render": { + "en": "Access: {access}" + }, + "question": { + "en": "Who can use this waste disposal bin?" + }, + "freeform": { + "key": "access", + "type": "string" + }, + "mappings": [ + { + "if": "access=yes", + "then": { + "en": "This bin can be used by anyone" + } + }, + { + "if": "access=no", + "then": { + "en": "This bin is private" + } + }, + { + "if": "access=residents", + "then": { + "en": "This bin is only for residents" + } + } + ] + }, + { + "id": "disposal-location", + "question": { + "en": "Where is this container located?", + "nl": "Waar bevindt deze container zich?" + }, + "mappings": [ + { + "if": "location=underground", + "then": { + "en": "This is an underground container", + "nl": "Dit is een ondergrondse container" + } + }, + { + "if": "location=indoor", + "then": { + "en": "This container is located indoors", + "nl": "Deze container bevindt zich binnen" + } + }, + { + "if": "location=", + "then": { + "en": "This container is located outdoors", + "nl": "Deze container is buiten" + } + } + ] + } + ], + "filter": [ + { + "id": "public-access", + "options": [ + { + "question": { + "en": "Only public access" + }, + "osmTags": "access=yes" + } + ] + } + ] +} \ No newline at end of file diff --git a/assets/layers/waste_disposal/waste_disposal.svg b/assets/layers/waste_disposal/waste_disposal.svg new file mode 100644 index 000000000..db755349c --- /dev/null +++ b/assets/layers/waste_disposal/waste_disposal.svg @@ -0,0 +1,31 @@ + + + + + + image/svg+xml + + + + + + + + diff --git a/assets/layers/watermill/watermill.json b/assets/layers/watermill/watermill.json index 1e885ee8e..b69a4d4de 100644 --- a/assets/layers/watermill/watermill.json +++ b/assets/layers/watermill/watermill.json @@ -146,7 +146,11 @@ ] }, "then": { - "nl": "Dit gebied wordt beheerd door Natuurpunt" + "nl": "Dit gebied wordt beheerd door Natuurpunt" + }, + "icon": { + "path": "./assets/themes/buurtnatuur/Natuurpunt.jpg", + "class": "small" } }, { @@ -156,9 +160,13 @@ ] }, "then": { - "nl": "Dit gebied wordt beheerd door {operator}" + "nl": "Dit gebied wordt beheerd door {operator}" }, - "hideInAnswer": true + "hideInAnswer": true, + "icon": { + "path": "./assets/themes/buurtnatuur/Natuurpunt.jpg", + "class": "small" + } } ], "id": "Operator tag" diff --git a/assets/layoutconfigmeta.json b/assets/layoutconfigmeta.json index 40abedbc8..c596f78b9 100644 --- a/assets/layoutconfigmeta.json +++ b/assets/layoutconfigmeta.json @@ -485,6 +485,48 @@ "icon" ], "typeHint": "icon", + "type": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "class", + "path" + ] + }, + { + "type": "string" + } + ] + }, + { + "path": [ + "layers", + "isShown", + "mappings", + "icon", + "path" + ], + "typeHint": "icon", + "type": "string" + }, + { + "path": [ + "layers", + "isShown", + "mappings", + "icon", + "class" + ], "type": "string" }, { @@ -753,6 +795,48 @@ "icon" ], "typeHint": "icon", + "type": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "class", + "path" + ] + }, + { + "type": "string" + } + ] + }, + { + "path": [ + "layers", + "title", + "mappings", + "icon", + "path" + ], + "typeHint": "icon", + "type": "string" + }, + { + "path": [ + "layers", + "title", + "mappings", + "icon", + "class" + ], "type": "string" }, { @@ -1014,6 +1098,48 @@ "icon" ], "typeHint": "icon", + "type": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "class", + "path" + ] + }, + { + "type": "string" + } + ] + }, + { + "path": [ + "layers", + "titleIcons", + "mappings", + "icon", + "path" + ], + "typeHint": "icon", + "type": "string" + }, + { + "path": [ + "layers", + "titleIcons", + "mappings", + "icon", + "class" + ], "type": "string" }, { @@ -1291,6 +1417,50 @@ "icon" ], "typeHint": "icon", + "type": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "class", + "path" + ] + }, + { + "type": "string" + } + ] + }, + { + "path": [ + "layers", + "mapRendering", + "icon", + "mappings", + "icon", + "path" + ], + "typeHint": "icon", + "type": "string" + }, + { + "path": [ + "layers", + "mapRendering", + "icon", + "mappings", + "icon", + "class" + ], "type": "string" }, { @@ -1585,6 +1755,52 @@ "icon" ], "typeHint": "icon", + "type": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "class", + "path" + ] + }, + { + "type": "string" + } + ] + }, + { + "path": [ + "layers", + "mapRendering", + "iconBadges", + "then", + "mappings", + "icon", + "path" + ], + "typeHint": "icon", + "type": "string" + }, + { + "path": [ + "layers", + "mapRendering", + "iconBadges", + "then", + "mappings", + "icon", + "class" + ], "type": "string" }, { @@ -1852,6 +2068,50 @@ "icon" ], "typeHint": "icon", + "type": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "class", + "path" + ] + }, + { + "type": "string" + } + ] + }, + { + "path": [ + "layers", + "mapRendering", + "iconSize", + "mappings", + "icon", + "path" + ], + "typeHint": "icon", + "type": "string" + }, + { + "path": [ + "layers", + "mapRendering", + "iconSize", + "mappings", + "icon", + "class" + ], "type": "string" }, { @@ -2116,6 +2376,50 @@ "icon" ], "typeHint": "icon", + "type": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "class", + "path" + ] + }, + { + "type": "string" + } + ] + }, + { + "path": [ + "layers", + "mapRendering", + "rotation", + "mappings", + "icon", + "path" + ], + "typeHint": "icon", + "type": "string" + }, + { + "path": [ + "layers", + "mapRendering", + "rotation", + "mappings", + "icon", + "class" + ], "type": "string" }, { @@ -2380,6 +2684,50 @@ "icon" ], "typeHint": "icon", + "type": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "class", + "path" + ] + }, + { + "type": "string" + } + ] + }, + { + "path": [ + "layers", + "mapRendering", + "label", + "mappings", + "icon", + "path" + ], + "typeHint": "icon", + "type": "string" + }, + { + "path": [ + "layers", + "mapRendering", + "label", + "mappings", + "icon", + "class" + ], "type": "string" }, { @@ -2651,6 +2999,50 @@ "icon" ], "typeHint": "icon", + "type": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "class", + "path" + ] + }, + { + "type": "string" + } + ] + }, + { + "path": [ + "layers", + "mapRendering", + "color", + "mappings", + "icon", + "path" + ], + "typeHint": "icon", + "type": "string" + }, + { + "path": [ + "layers", + "mapRendering", + "color", + "mappings", + "icon", + "class" + ], "type": "string" }, { @@ -2918,6 +3310,50 @@ "icon" ], "typeHint": "icon", + "type": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "class", + "path" + ] + }, + { + "type": "string" + } + ] + }, + { + "path": [ + "layers", + "mapRendering", + "width", + "mappings", + "icon", + "path" + ], + "typeHint": "icon", + "type": "string" + }, + { + "path": [ + "layers", + "mapRendering", + "width", + "mappings", + "icon", + "class" + ], "type": "string" }, { @@ -3182,6 +3618,50 @@ "icon" ], "typeHint": "icon", + "type": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "class", + "path" + ] + }, + { + "type": "string" + } + ] + }, + { + "path": [ + "layers", + "mapRendering", + "dashArray", + "mappings", + "icon", + "path" + ], + "typeHint": "icon", + "type": "string" + }, + { + "path": [ + "layers", + "mapRendering", + "dashArray", + "mappings", + "icon", + "class" + ], "type": "string" }, { @@ -3446,6 +3926,50 @@ "icon" ], "typeHint": "icon", + "type": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "class", + "path" + ] + }, + { + "type": "string" + } + ] + }, + { + "path": [ + "layers", + "mapRendering", + "lineCap", + "mappings", + "icon", + "path" + ], + "typeHint": "icon", + "type": "string" + }, + { + "path": [ + "layers", + "mapRendering", + "lineCap", + "mappings", + "icon", + "class" + ], "type": "string" }, { @@ -3714,6 +4238,50 @@ "icon" ], "typeHint": "icon", + "type": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "class", + "path" + ] + }, + { + "type": "string" + } + ] + }, + { + "path": [ + "layers", + "mapRendering", + "fill", + "mappings", + "icon", + "path" + ], + "typeHint": "icon", + "type": "string" + }, + { + "path": [ + "layers", + "mapRendering", + "fill", + "mappings", + "icon", + "class" + ], "type": "string" }, { @@ -3978,6 +4546,50 @@ "icon" ], "typeHint": "icon", + "type": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "class", + "path" + ] + }, + { + "type": "string" + } + ] + }, + { + "path": [ + "layers", + "mapRendering", + "fillColor", + "mappings", + "icon", + "path" + ], + "typeHint": "icon", + "type": "string" + }, + { + "path": [ + "layers", + "mapRendering", + "fillColor", + "mappings", + "icon", + "class" + ], "type": "string" }, { @@ -4242,6 +4854,50 @@ "icon" ], "typeHint": "icon", + "type": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "class", + "path" + ] + }, + { + "type": "string" + } + ] + }, + { + "path": [ + "layers", + "mapRendering", + "offset", + "mappings", + "icon", + "path" + ], + "typeHint": "icon", + "type": "string" + }, + { + "path": [ + "layers", + "mapRendering", + "offset", + "mappings", + "icon", + "class" + ], "type": "string" }, { @@ -4625,6 +5281,48 @@ "icon" ], "typeHint": "icon", + "type": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "class", + "path" + ] + }, + { + "type": "string" + } + ] + }, + { + "path": [ + "layers", + "tagRenderings", + "mappings", + "icon", + "path" + ], + "typeHint": "icon", + "type": "string" + }, + { + "path": [ + "layers", + "tagRenderings", + "mappings", + "icon", + "class" + ], "type": "string" }, { @@ -4871,6 +5569,50 @@ "icon" ], "typeHint": "icon", + "type": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "class", + "path" + ] + }, + { + "type": "string" + } + ] + }, + { + "path": [ + "layers", + "tagRenderings", + "renderings", + "mappings", + "icon", + "path" + ], + "typeHint": "icon", + "type": "string" + }, + { + "path": [ + "layers", + "tagRenderings", + "renderings", + "mappings", + "icon", + "class" + ], "type": "string" }, { @@ -5160,6 +5902,13 @@ ], "type": "boolean" }, + { + "path": [ + "layers", + "syncSelection" + ], + "type": "string" + }, { "path": [ "layers", diff --git a/assets/tagrenderingconfigmeta.json b/assets/tagrenderingconfigmeta.json index fd9483b82..6f7b349c1 100644 --- a/assets/tagrenderingconfigmeta.json +++ b/assets/tagrenderingconfigmeta.json @@ -138,6 +138,44 @@ "icon" ], "typeHint": "icon", + "type": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "class", + "path" + ] + }, + { + "type": "string" + } + ] + }, + { + "path": [ + "mappings", + "icon", + "path" + ], + "typeHint": "icon", + "type": "string" + }, + { + "path": [ + "mappings", + "icon", + "class" + ], "type": "string" }, { diff --git a/assets/themes/benches/benches.json b/assets/themes/benches/benches.json index 193b38d40..7ed0d305e 100644 --- a/assets/themes/benches/benches.json +++ b/assets/themes/benches/benches.json @@ -45,7 +45,6 @@ "startLon": 0, "startZoom": 1, "widenFactor": 1.5, - "socialImage": "", "layers": [ "bench", "bench_at_pt", diff --git a/assets/themes/bicycle_rental/bicycle_rental.json b/assets/themes/bicycle_rental/bicycle_rental.json index 9f30efca3..b26d1bf16 100644 --- a/assets/themes/bicycle_rental/bicycle_rental.json +++ b/assets/themes/bicycle_rental/bicycle_rental.json @@ -19,7 +19,6 @@ "startLon": 0, "startZoom": 1, "widenFactor": 0.05, - "socialImage": "", "layers": [ "bicycle_rental" ] diff --git a/assets/themes/binoculars/binoculars.json b/assets/themes/binoculars/binoculars.json index 1e77b49c6..fed6bcb98 100644 --- a/assets/themes/binoculars/binoculars.json +++ b/assets/themes/binoculars/binoculars.json @@ -33,7 +33,6 @@ "startLon": 0, "startZoom": 1, "widenFactor": 1.5, - "socialImage": "", "layers": [ "binocular" ] diff --git a/assets/themes/buurtnatuur/buurtnatuur.json b/assets/themes/buurtnatuur/buurtnatuur.json index 1ef2e4f1d..991654c46 100644 --- a/assets/themes/buurtnatuur/buurtnatuur.json +++ b/assets/themes/buurtnatuur/buurtnatuur.json @@ -504,7 +504,8 @@ ] }, "then": { - "nl": "Dit gebied wordt beheerd door Natuurpunt" + "nl": "Dit gebied wordt beheerd door Natuurpunt", + "icon": "./assets/themes/buurtnatuur/Natuurpunt.jpg" } }, { @@ -514,7 +515,8 @@ ] }, "then": { - "nl": "Dit gebied wordt beheerd door {operator}" + "nl": "Dit gebied wordt beheerd door {operator}", + "icon": "./assets/themes/buurtnatuur/Natuurpunt.jpg" }, "hideInAnswer": true }, @@ -525,7 +527,8 @@ ] }, "then": { - "nl": "Dit gebied wordt beheerd door het Agentschap Natuur en Bos" + "nl": "Dit gebied wordt beheerd door het Agentschap Natuur en Bos", + "icon": "./assets/themes/buurtnatuur/ANB.jpg" } }, { diff --git a/assets/themes/cafes_and_pubs/cafes_and_pubs.json b/assets/themes/cafes_and_pubs/cafes_and_pubs.json index 9ab64a6c9..92256e06a 100644 --- a/assets/themes/cafes_and_pubs/cafes_and_pubs.json +++ b/assets/themes/cafes_and_pubs/cafes_and_pubs.json @@ -22,7 +22,6 @@ "startLon": 0, "startZoom": 1, "widenFactor": 1.5, - "socialImage": "", "layers": [ "cafe_pub" ] diff --git a/assets/themes/campersite/campersite.json b/assets/themes/campersite/campersite.json index 59acbe7c6..bc792011e 100644 --- a/assets/themes/campersite/campersite.json +++ b/assets/themes/campersite/campersite.json @@ -42,7 +42,7 @@ "startLon": 3.14, "startZoom": 14, "widenFactor": 1.5, - "socialImage": "./assets/themes/campersite/Bar%C3%9Fel_Wohnmobilstellplatz.jpg", + "socialImage": "./assets/themes/campersite/social_image.jpg", "layers": [ { "id": "caravansites", diff --git a/assets/themes/campersite/license_info.json b/assets/themes/campersite/license_info.json index 6e12c74c2..d1b076033 100644 --- a/assets/themes/campersite/license_info.json +++ b/assets/themes/campersite/license_info.json @@ -1,14 +1,4 @@ [ - { - "path": "Barßel_Wohnmobilstellplatz.jpg", - "license": "CC-BY-SA 3.0", - "authors": [ - "ES01" - ], - "sources": [ - "https://commons.wikimedia.org/wiki/File:Bar%C3%9Fel_Wohnmobilstellplatz.jpg" - ] - }, { "path": "caravan.svg", "license": "CC0", @@ -41,5 +31,15 @@ "https://github.com/osmandapp/Osmand/blob/master/LICENSE", "https://github.com/osmandapp/OsmAnd-resources/blob/16892d8b2fc00dd422abfb2fef967d5ccd05eeac/icons/svg/poi/sanitary_dump_station.svg" ] + }, + { + "path": "social_image.jpg", + "license": "CC-BY-SA 3.0", + "authors": [ + "ES01" + ], + "sources": [ + "https://commons.wikimedia.org/wiki/File:Bar%C3%9Fel_Wohnmobilstellplatz.jpg" + ] } ] \ No newline at end of file diff --git a/assets/themes/campersite/Barßel_Wohnmobilstellplatz.jpg b/assets/themes/campersite/social_image.jpg similarity index 100% rename from assets/themes/campersite/Barßel_Wohnmobilstellplatz.jpg rename to assets/themes/campersite/social_image.jpg diff --git a/assets/themes/charging_stations/charging_stations.json b/assets/themes/charging_stations/charging_stations.json index 2b6fc04d2..6c0dbdc86 100644 --- a/assets/themes/charging_stations/charging_stations.json +++ b/assets/themes/charging_stations/charging_stations.json @@ -39,7 +39,6 @@ "startLon": 0, "startZoom": 1, "widenFactor": 1.5, - "socialImage": "", "defaultBackgroundId": "CartoDB.Voyager", "layers": [ "charging_station" diff --git a/assets/themes/climbing/climbing.json b/assets/themes/climbing/climbing.json index fc948705d..e93273ebf 100644 --- a/assets/themes/climbing/climbing.json +++ b/assets/themes/climbing/climbing.json @@ -41,7 +41,6 @@ "startLon": 0, "startZoom": 1, "widenFactor": 1.5, - "socialImage": "", "layers": [ { "id": "climbing_club", diff --git a/assets/themes/cyclofix/cyclofix.json b/assets/themes/cyclofix/cyclofix.json index 99ec5635b..82a94047f 100644 --- a/assets/themes/cyclofix/cyclofix.json +++ b/assets/themes/cyclofix/cyclofix.json @@ -33,7 +33,7 @@ "startLon": 0, "startZoom": 1, "widenFactor": 2, - "socialImage": "assets/themes/cyclofix/logo.svg", + "socialImage": "./assets/themes/cyclofix/logo.svg", "layers": [ "bike_cafe", "bike_shop", diff --git a/assets/themes/etymology.json b/assets/themes/etymology.json index bd76bf8a3..1ed79cf96 100644 --- a/assets/themes/etymology.json +++ b/assets/themes/etymology.json @@ -32,7 +32,6 @@ "startLon": 0, "startZoom": 1, "widenFactor": 2, - "socialImage": "", "clustering": { "maxZoom": 14, "minNeededElements": 250 diff --git a/assets/themes/facadegardens/facadegardens.json b/assets/themes/facadegardens/facadegardens.json index a9d50b9c6..923346ff3 100644 --- a/assets/themes/facadegardens/facadegardens.json +++ b/assets/themes/facadegardens/facadegardens.json @@ -36,7 +36,6 @@ "startLon": 4.480705, "startZoom": 15, "widenFactor": 1.5, - "socialImage": "", "layers": [ { "id": "facadegardens", diff --git a/assets/themes/food/food.json b/assets/themes/food/food.json index 796aeefd4..05b4516ce 100644 --- a/assets/themes/food/food.json +++ b/assets/themes/food/food.json @@ -20,7 +20,6 @@ "startLon": 0, "startZoom": 1, "widenFactor": 3, - "socialImage": "", "layers": [ "food" ] diff --git a/assets/themes/fritures/fritures.json b/assets/themes/fritures/fritures.json index 22677da03..46d5a4e8e 100644 --- a/assets/themes/fritures/fritures.json +++ b/assets/themes/fritures/fritures.json @@ -16,7 +16,6 @@ "startLon": 0, "startZoom": 1, "widenFactor": 3, - "socialImage": "", "layers": [ { "builtin": "food", diff --git a/assets/themes/fruit_trees/fruit_trees.json b/assets/themes/fruit_trees/fruit_trees.json index 3b687bd89..7fe8c9323 100644 --- a/assets/themes/fruit_trees/fruit_trees.json +++ b/assets/themes/fruit_trees/fruit_trees.json @@ -16,7 +16,6 @@ "startLon": 0, "startZoom": 1, "widenFactor": 2, - "socialImage": "", "hideFromOverview": true, "layers": [ { diff --git a/assets/themes/grb_import/grb.json b/assets/themes/grb_import/grb.json index c184f8594..28b9c62a4 100644 --- a/assets/themes/grb_import/grb.json +++ b/assets/themes/grb_import/grb.json @@ -11,14 +11,13 @@ "en": "This theme is an attempt to help automating the GRB import.", "hu": "Ez a sablon a flandriai GRB épületimportálás automatizlását kívánja megkönnyíteni." }, - "maintainer": "", + "maintainer": "Pieter Vander Vennet", "icon": "./assets/themes/grb_import/logo.svg", "version": "0", "startLat": 51.0249, "startLon": 4.026489, "startZoom": 9, "widenFactor": 2, - "socialImage": "", "clustering": { "maxZoom": 15 }, @@ -605,15 +604,14 @@ }, "iconSize": "50,50,center", "icon": { - "render": "./assets/themes/grb_import/housenumber_blank.svg", "mappings": [ { "if": "_intersects_with_other_features~*", "then": "./assets/themes/grb_import/warning.svg" }, { - "if": "addr:housenumber=", - "then": "" + "if": "addr:housenumber~*", + "then": "./assets/themes/grb_import/housenumber_blank.svg" } ] }, diff --git a/assets/themes/grb_import/grb_fixme.json b/assets/themes/grb_import/grb_fixme.json index 313b5efb6..2ae057dfd 100644 --- a/assets/themes/grb_import/grb_fixme.json +++ b/assets/themes/grb_import/grb_fixme.json @@ -16,7 +16,6 @@ "startLon": 3.231, "startZoom": 14, "widenFactor": 2, - "socialImage": "", "clustering": { "maxZoom": 15 }, diff --git a/assets/themes/grb_import/missing_streets.json b/assets/themes/grb_import/missing_streets.json index 4bcd22c2a..d30e0a2cc 100644 --- a/assets/themes/grb_import/missing_streets.json +++ b/assets/themes/grb_import/missing_streets.json @@ -16,7 +16,6 @@ "startLon": 4.026489, "startZoom": 9, "widenFactor": 2, - "socialImage": "", "clustering": { "maxZoom": 15 }, diff --git a/assets/themes/hackerspaces/hackerspaces.json b/assets/themes/hackerspaces/hackerspaces.json index 4deafa356..c5728cb7e 100644 --- a/assets/themes/hackerspaces/hackerspaces.json +++ b/assets/themes/hackerspaces/hackerspaces.json @@ -29,7 +29,6 @@ "startLon": 0, "startZoom": 1, "widenFactor": 5, - "socialImage": "", "layers": [ { "id": "hackerspaces", diff --git a/assets/themes/hailhydrant/hailhydrant.json b/assets/themes/hailhydrant/hailhydrant.json index a135d0f4e..0d4007b45 100644 --- a/assets/themes/hailhydrant/hailhydrant.json +++ b/assets/themes/hailhydrant/hailhydrant.json @@ -37,7 +37,6 @@ "startLon": 121.6625, "startZoom": 6, "widenFactor": 3, - "socialImage": "", "layers": [ "hydrant", "extinguisher", diff --git a/assets/themes/mapcomplete-changes/mapcomplete-changes.json b/assets/themes/mapcomplete-changes/mapcomplete-changes.json index d0108bdbf..8653e1842 100644 --- a/assets/themes/mapcomplete-changes/mapcomplete-changes.json +++ b/assets/themes/mapcomplete-changes/mapcomplete-changes.json @@ -299,6 +299,14 @@ "if": "theme=uk_addresses", "then": "./assets/themes/uk_addresses/housenumber_unknown.svg" }, + { + "if": "theme=waste", + "then": "./assets/layers/recycling/recycling-14.svg" + }, + { + "if": "theme=waste_assen", + "then": "./assets/layers/recycling/recycling-14.svg" + }, { "if": "theme=waste_basket", "then": "./assets/themes/waste_basket/waste_basket.svg" diff --git a/assets/themes/maps/maps.json b/assets/themes/maps/maps.json index 305eb4a17..b18aecf4f 100644 --- a/assets/themes/maps/maps.json +++ b/assets/themes/maps/maps.json @@ -37,7 +37,6 @@ "startLon": 0, "startZoom": 1, "widenFactor": 5, - "socialImage": "", "layers": [ "map" ] diff --git a/assets/themes/nature/nature.json b/assets/themes/nature/nature.json index 8dc62a6df..294d6be52 100644 --- a/assets/themes/nature/nature.json +++ b/assets/themes/nature/nature.json @@ -19,7 +19,6 @@ "startLon": 3.22435, "startZoom": 12, "widenFactor": 2, - "socialImage": "", "layers": [ "drinking_water", "birdhide", diff --git a/assets/themes/natuurpunt/natuurpunt.json b/assets/themes/natuurpunt/natuurpunt.json index c66a67d92..3231ecbab 100644 --- a/assets/themes/natuurpunt/natuurpunt.json +++ b/assets/themes/natuurpunt/natuurpunt.json @@ -41,7 +41,6 @@ ] ], "widenFactor": 2, - "socialImage": "", "defaultBackgroundId": "CartoDB.Positron", "enablePdfDownload": true, "enableDownload": false, diff --git a/assets/themes/observation_towers/observation_towers.json b/assets/themes/observation_towers/observation_towers.json index 5a76cb753..f6bf823f9 100644 --- a/assets/themes/observation_towers/observation_towers.json +++ b/assets/themes/observation_towers/observation_towers.json @@ -32,7 +32,6 @@ "startLon": 0, "startZoom": 1, "widenFactor": 5, - "socialImage": "", "layers": [ "observation_tower" ] diff --git a/assets/themes/play_forests/play_forests.json b/assets/themes/play_forests/play_forests.json index 7c6950912..1a003edff 100644 --- a/assets/themes/play_forests/play_forests.json +++ b/assets/themes/play_forests/play_forests.json @@ -17,7 +17,6 @@ "startZoom": 1, "hideFromOverview": true, "widenFactor": 3, - "socialImage": "", "layers": [ "play_forest" ] diff --git a/assets/themes/playgrounds/playgrounds.json b/assets/themes/playgrounds/playgrounds.json index 696b7ff1b..8ab906ae8 100644 --- a/assets/themes/playgrounds/playgrounds.json +++ b/assets/themes/playgrounds/playgrounds.json @@ -45,7 +45,6 @@ "startLon": 4.399, "startZoom": 13, "widenFactor": 5, - "socialImage": "", "layers": [ "playground" ] diff --git a/assets/themes/postal_codes/postal_codes.json b/assets/themes/postal_codes/postal_codes.json index 6e14f97ec..247b4de33 100644 --- a/assets/themes/postal_codes/postal_codes.json +++ b/assets/themes/postal_codes/postal_codes.json @@ -22,7 +22,6 @@ "startLon": 0, "startZoom": 1, "widenFactor": 0.05, - "socialImage": "", "hideFromOverview": true, "clustering": false, "overpassTimeout": 180, diff --git a/assets/themes/shops/shops.json b/assets/themes/shops/shops.json index 6f07ffdf2..0c058075e 100644 --- a/assets/themes/shops/shops.json +++ b/assets/themes/shops/shops.json @@ -35,7 +35,6 @@ "startLon": 0, "startZoom": 1, "widenFactor": 3, - "socialImage": "", "layers": [ "shops" ] diff --git a/assets/themes/sidewalks/sidewalks.json b/assets/themes/sidewalks/sidewalks.json index 72ef76042..28cbe8ae8 100644 --- a/assets/themes/sidewalks/sidewalks.json +++ b/assets/themes/sidewalks/sidewalks.json @@ -18,7 +18,6 @@ "startLon": 0, "startZoom": 1, "widenFactor": 0.05, - "socialImage": "", "hideFromOverview": true, "layers": [ { diff --git a/assets/themes/sport_pitches/sport_pitches.json b/assets/themes/sport_pitches/sport_pitches.json index d2a88cd00..b0237a414 100644 --- a/assets/themes/sport_pitches/sport_pitches.json +++ b/assets/themes/sport_pitches/sport_pitches.json @@ -39,7 +39,6 @@ "startLon": 0, "startZoom": 1, "widenFactor": 2, - "socialImage": "", "layers": [ "sport_pitch" ] diff --git a/assets/themes/street_lighting/street_lighting_assen.json b/assets/themes/street_lighting/street_lighting_assen.json index 7de783931..646a82550 100644 --- a/assets/themes/street_lighting/street_lighting_assen.json +++ b/assets/themes/street_lighting/street_lighting_assen.json @@ -26,7 +26,7 @@ }, "calculatedTags": [ "_closest_osm_street_lamp=feat.closest('street_lamps')?.properties?.id", - "_closest_osm_street_lamp_distance=feat.distanceTo(feat.properties._closest_osm_street_lamp) * 1000", + "_closest_osm_street_lamp_distance=feat.distanceTo(feat.properties._closest_osm_street_lamp)", "_has_closeby_feature=Number(feat.properties._closest_osm_street_lamp_distance) < 5 ? 'yes' : 'no'" ], "title": "Straatlantaarn in dataset", diff --git a/assets/themes/surveillance/surveillance.json b/assets/themes/surveillance/surveillance.json index fefeb406c..197362a86 100644 --- a/assets/themes/surveillance/surveillance.json +++ b/assets/themes/surveillance/surveillance.json @@ -39,7 +39,6 @@ "startLon": 0, "startZoom": 1, "widenFactor": 2, - "socialImage": "", "defaultBackgroundId": "osm", "layers": [ "direction", diff --git a/assets/themes/uk_addresses/uk_addresses.json b/assets/themes/uk_addresses/uk_addresses.json index febb712fe..4a46793bf 100644 --- a/assets/themes/uk_addresses/uk_addresses.json +++ b/assets/themes/uk_addresses/uk_addresses.json @@ -24,7 +24,6 @@ "startLon": 51.52224, "startZoom": 17, "widenFactor": 1.01, - "socialImage": "", "hideFromOverview": true, "clustering": { "minNeededFeatures": 25, @@ -240,6 +239,7 @@ }, "mappings": [ { + "#": "ignore-image-in-then", "if": "addr:substreet~*", "then": "
The envelope below shows the address that we have recorded. You can change this by answering any remaining questions above, or by clicking the pencil icons below. We do not need you to provide a recipient's name or any of the parts shown in [blue].
{addr:unit} {addr:housename}
{addr:housenumber} {addr:substreet}
{addr:street}
{addr:parentstreet}
[Suburb]
[Town]
[Postal code]
" } diff --git a/assets/themes/waste/waste.json b/assets/themes/waste/waste.json new file mode 100644 index 000000000..1998b893a --- /dev/null +++ b/assets/themes/waste/waste.json @@ -0,0 +1,32 @@ +{ + "id": "waste", + "maintainer": "", + "version": "2022-01-19", + "language": [ + "en", + "nl" + ], + "title": { + "en": "Waste", + "nl": "Afval" + }, + "description": { + "en": "Map showing waste baskets and recycling facilities.", + "nl": "Kaart met afvalbakken en recyclingfaciliteiten." + }, + "icon": "./assets/layers/recycling/recycling-14.svg", + "startZoom": 19, + "startLat": 53.24865, + "startLon": 6.60075, + "layers": [ + { + "builtin": "waste_basket", + "override": { + "minzoom": 18, + "minzoomVisible": 18 + } + }, + "recycling", + "waste_disposal" + ] +} \ No newline at end of file diff --git a/assets/themes/waste/waste_assen.json b/assets/themes/waste/waste_assen.json new file mode 100644 index 000000000..810c64a31 --- /dev/null +++ b/assets/themes/waste/waste_assen.json @@ -0,0 +1,109 @@ +{ + "id": "waste_assen", + "maintainer": "Robin van der Linde", + "version": "2022-01-09", + "language": [ + "en", + "nl" + ], + "title": { + "nl": "Afval - Assen" + }, + "description": { + "nl": "Kaart met afvalbakken en recyclingfaciliteiten + een dataset voor Assen." + }, + "icon": "./assets/layers/recycling/recycling-14.svg", + "startZoom": 19, + "startLat": 53.24865, + "startLon": 6.60075, + "layers": [ + { + "builtin": "waste_basket", + "override": { + "minzoom": 12 + } + }, + { + "id": "waste_basket_assen", + "name": "Dataset Afvalbakken Assen", + "source": { + "osmTags": "OBJECTID~*", + "geoJson": "https://opendata.arcgis.com/datasets/5b6953ac5a9d4616a7dc75ab0beeac2f_0.geojson", + "isOsmCache": false + }, + "calculatedTags": [ + "_closest_osm_waste_basket=feat.closest('waste_basket')?.properties?.id", + "_closest_osm_waste_basket_distance=feat.distanceTo(feat.properties._closest_osm_waste_basket)", + "_has_closeby_feature=Number(feat.properties._closest_osm_waste_basket_distance) < 10 ? 'yes' : 'no'" + ], + "title": "Afvalbak in dataset", + "mapRendering": [ + { + "location": [ + "point", + "centroid" + ], + "icon": { + "render": "circle:red", + "mappings": [ + { + "if": "_has_closeby_feature=yes", + "then": "circle:#008000aa" + } + ] + }, + "iconSize": "20,20,center" + } + ], + "tagRenderings": [ + "all_tags" + ] + }, + "recycling", + { + "id": "recycling_assen", + "name": "Dataset Recyclingcontainers Assen", + "source": { + "osmTags": "OBJECTID~*", + "geoJson": "https://opendata.arcgis.com/datasets/edb893998e27461b8ed82aad9854d27d_0.geojson", + "isOsmCache": false + }, + "calculatedTags": [ + "_closest_osm_recycling=feat.closest('recycling')?.properties?.id", + "_closest_osm_waste_disposal=feat.closest('waste_disposal')?.properties?.id", + "_closest_osm_recycling_distance=feat.distanceTo(feat.properties._closest_osm_recycling)", + "_closest_osm_waste_disposal_distance=feat.distanceTo(feat.properties._closest_osm_waste_disposal)", + "_has_closeby_recycling=Number(feat.properties._closest_osm_recycling_distance) < 10 ? 'yes' : 'no'", + "_has_closeby_waste_disposal=Number(feat.properties._closest_osm_waste_disposal_distance) < 10 ? 'yes' : 'no'" + ], + "title": "Recyclingcontainer in dataset", + "mapRendering": [ + { + "location": [ + "point", + "centroid" + ], + "icon": { + "render": "circle:red", + "mappings": [ + { + "if": "_has_closeby_recycling=yes", + "then": "circle:#008000aa" + }, + { + "if": "_has_closeby_waste_disposal=yes", + "then": "circle:#008000aa" + } + ] + }, + "iconSize": "20,20,center" + } + ], + "tagRenderings": [ + "all_tags" + ] + }, + "waste_disposal" + ], + "hideFromOverview": true +} \ No newline at end of file diff --git a/assets/themes/waste_basket/waste_basket.json b/assets/themes/waste_basket/waste_basket.json index b0bca66e1..d312125a8 100644 --- a/assets/themes/waste_basket/waste_basket.json +++ b/assets/themes/waste_basket/waste_basket.json @@ -32,7 +32,6 @@ "startLon": 0, "startZoom": 1, "widenFactor": 2, - "socialImage": "", "layers": [ { "builtin": "waste_basket", diff --git a/langs/layers/de.json b/langs/layers/de.json index 38e3accf2..3c845d54d 100644 --- a/langs/layers/de.json +++ b/langs/layers/de.json @@ -1242,7 +1242,7 @@ "crossing-continue-through-red": { "mappings": { "0": { - "then": "Ein Radfahrer kann bei roter Ampel geradeaus fahren " + "then": "Ein Radfahrer kann bei roter Ampel geradeaus fahren" }, "1": { "then": "Ein Radfahrer kann bei roter Ampel geradeaus fahren" @@ -1278,7 +1278,7 @@ "crossing-right-turn-through-red": { "mappings": { "0": { - "then": "Ein Radfahrer kann bei roter Ampel rechts abbiegen " + "then": "Ein Radfahrer kann bei roter Ampel rechts abbiegen" }, "1": { "then": "Ein Radfahrer kann bei roter Ampel rechts abbiegen" @@ -3175,6 +3175,40 @@ }, "waste_basket": { "description": "Dies ist ein öffentlicher Abfalleimer, in den Sie Ihren Müll entsorgen können.", + "filter": { + "0": { + "options": { + "0": { + "question": "Alle Typen" + }, + "1": { + "question": "Mülleimer für Zigaretten" + }, + "2": { + "question": "Mülleimer für Drogen" + }, + "3": { + "question": "Mülleimer für Hundekot" + }, + "4": { + "question": "Mülleimer für allgemeinen Müll" + }, + "5": { + "question": "Mülleimer für Nadeln und andere scharfe Gegenstände" + }, + "6": { + "question": "Mülleimer für Plastik" + } + } + }, + "1": { + "options": { + "0": { + "question": "Abfalleimer mit Spender für (Hunde-)Kotbeutel" + } + } + } + }, "mapRendering": { "0": { "iconSize": { @@ -3226,6 +3260,9 @@ }, "5": { "then": "Ein Abfalleimer für Nadeln und andere scharfe Gegenstände" + }, + "6": { + "then": "Ein Abfalleimer für Plastik" } }, "question": "Um was für einen Abfalleimer handelt es sich?" diff --git a/langs/layers/en.json b/langs/layers/en.json index e2076a2ea..0cae77363 100644 --- a/langs/layers/en.json +++ b/langs/layers/en.json @@ -2305,7 +2305,7 @@ "crossing-continue-through-red": { "mappings": { "0": { - "then": "A cyclist can go straight on if the light is red " + "then": "A cyclist can go straight on if the light is red" }, "1": { "then": "A cyclist can go straight on if the light is red" @@ -2341,7 +2341,7 @@ "crossing-right-turn-through-red": { "mappings": { "0": { - "then": "A cyclist can turn right if the light is red " + "then": "A cyclist can turn right if the light is red" }, "1": { "then": "A cyclist can turn right if the light is red" @@ -4124,6 +4124,215 @@ "render": "Bookcase" } }, + "recycling": { + "description": "A layer with recycling containers and centres", + "filter": { + "0": { + "options": { + "0": { + "question": "Currently open" + } + } + }, + "1": { + "options": { + "0": { + "question": "All recycling types" + }, + "1": { + "question": "Recycling of batteries" + }, + "2": { + "question": "Recycling of beverage cartons" + }, + "3": { + "question": "Recycling of cans" + }, + "4": { + "question": "Recycling of clothes" + }, + "5": { + "question": "Recycling of cooking oil" + }, + "6": { + "question": "Recycling of engine oil" + }, + "7": { + "question": "Recycling of green waste" + }, + "8": { + "question": "Recycling of glass bottles" + }, + "9": { + "question": "Recycling of glass" + }, + "10": { + "question": "Recycling of newspapers" + }, + "11": { + "question": "Recycling of paper" + }, + "12": { + "question": "Recycling of plastic bottles" + }, + "13": { + "question": "Recycling of plastic packaging" + }, + "14": { + "question": "Recycling of plastic" + }, + "15": { + "question": "Recycling of scrap metal" + }, + "16": { + "question": "Recycling of small electrical appliances" + }, + "17": { + "question": "Recycling of residual waste" + } + } + } + }, + "name": "Recycling", + "presets": { + "0": { + "title": "recycling container" + }, + "1": { + "title": "recycling centre" + } + }, + "tagRenderings": { + "container-location": { + "mappings": { + "0": { + "then": "This is an underground container" + }, + "1": { + "then": "This container is located indoors" + }, + "2": { + "then": "This container is located outdoors" + } + }, + "question": "Where is this container located?" + }, + "opening_hours": { + "mappings": { + "0": { + "then": "24/7" + } + }, + "question": "What are the opening hours of this recycling facility?" + }, + "operator": { + "question": "What company operates this recycling facility?", + "render": "This recycling facility is operated by {operator}" + }, + "recycling-accepts": { + "mappings": { + "0": { + "then": "Batteries can be recycled here" + }, + "1": { + "then": "Beverage cartons can be recycled here" + }, + "2": { + "then": "Cans can be recycled here" + }, + "3": { + "then": "Clothes can be recycled here" + }, + "4": { + "then": "Cooking oil can be recycled here" + }, + "5": { + "then": "Engine oil can be recycled here" + }, + "6": { + "then": "Green waste can be recycled here" + }, + "7": { + "then": "Organic waste can be recycled here" + }, + "8": { + "then": "Glass bottles can be recycled here" + }, + "9": { + "then": "Glass can be recycled here" + }, + "10": { + "then": "Newspapers can be recycled here" + }, + "11": { + "then": "Paper can be recycled here" + }, + "12": { + "then": "Plastic bottles can be recycled here" + }, + "13": { + "then": "Plastic packaging can be recycled here" + }, + "14": { + "then": "Plastic can be recycled here" + }, + "15": { + "then": "Scrap metal can be recycled here" + }, + "16": { + "then": "Shoes can be recycled here" + }, + "17": { + "then": "Small electrical appliances can be recycled here" + }, + "18": { + "then": "Small electrical appliances can be recycled here" + }, + "19": { + "then": "Residual waste can be recycled here" + } + }, + "question": "What can be recycled here?" + }, + "recycling-centre-name": { + "mappings": { + "0": { + "then": "This recycling centre doesn't have a specific name" + } + }, + "question": "What is the name of this recycling centre?", + "render": "This recycling centre is named {name}" + }, + "recycling-type": { + "mappings": { + "0": { + "then": "This is a recycling container" + }, + "1": { + "then": "This is a recycling centre" + }, + "2": { + "then": "Waste disposal container for residual waste" + } + }, + "question": "What type of recycling is this?" + } + }, + "title": { + "mappings": { + "0": { + "then": "Recycling centre" + }, + "1": { + "then": "Recycling container" + }, + "2": { + "then": "Recycling container" + } + }, + "render": "Recycling facility" + } + }, "shops": { "description": "A shop", "name": "Shop", @@ -4961,6 +5170,40 @@ }, "waste_basket": { "description": "This is a public waste basket, thrash can, where you can throw away your thrash.", + "filter": { + "0": { + "options": { + "0": { + "question": "All types" + }, + "1": { + "question": "Waste basket for cigarettes" + }, + "2": { + "question": "Waste basket for drugs" + }, + "3": { + "question": "Waste basket for dog excrement" + }, + "4": { + "question": "Waste basket for trash" + }, + "5": { + "question": "Waste basket for sharps" + }, + "6": { + "question": "Waste basket for plastic" + } + } + }, + "1": { + "options": { + "0": { + "question": "Waste basket with dispenser for (dog) excrement bags" + } + } + } + }, "mapRendering": { "0": { "iconSize": { @@ -5012,6 +5255,9 @@ }, "5": { "then": "A waste basket for needles and other sharp objects" + }, + "6": { + "then": "A waste basket for plastic" } }, "question": "What kind of waste basket is this?" @@ -5021,6 +5267,59 @@ "render": "Waste Basket" } }, + "waste_disposal": { + "description": "Waste Disposal Bin, medium to large bin for disposal of (household) waste", + "filter": { + "0": { + "options": { + "0": { + "question": "Only public access" + } + } + } + }, + "name": "Waste Disposal Bins", + "presets": { + "0": { + "description": "Medium to large bin for disposal of (household) waste", + "title": "Waste Disposal Bin" + } + }, + "tagRenderings": { + "access": { + "mappings": { + "0": { + "then": "This bin can be used by anyone" + }, + "1": { + "then": "This bin is private" + }, + "2": { + "then": "This bin is only for residents" + } + }, + "question": "Who can use this waste disposal bin?", + "render": "Access: {access}" + }, + "disposal-location": { + "mappings": { + "0": { + "then": "This is an underground container" + }, + "1": { + "then": "This container is located indoors" + }, + "2": { + "then": "This container is located outdoors" + } + }, + "question": "Where is this container located?" + } + }, + "title": { + "render": "Waste Disposal" + } + }, "watermill": { "name": "Watermill" } diff --git a/langs/layers/nl.json b/langs/layers/nl.json index e77e706d5..52b62bafe 100644 --- a/langs/layers/nl.json +++ b/langs/layers/nl.json @@ -2321,7 +2321,7 @@ "crossing-continue-through-red": { "mappings": { "0": { - "then": "Een fietser mag wel rechtdoor gaan als het licht rood is " + "then": "Een fietser mag wel rechtdoor gaan als het licht rood is" }, "1": { "then": "Een fietser mag wel rechtdoor gaan als het licht rood is" @@ -2357,7 +2357,7 @@ "crossing-right-turn-through-red": { "mappings": { "0": { - "then": "Een fietser mag wel rechtsaf slaan als het licht rood is " + "then": "Een fietser mag wel rechtsaf slaan als het licht rood is" }, "1": { "then": "Een fietser mag wel rechtsaf slaan als het licht rood is" @@ -3947,6 +3947,215 @@ "render": "Boekenruilkast" } }, + "recycling": { + "description": "Een laag met recyclagingcontainers en -centrums", + "filter": { + "0": { + "options": { + "0": { + "question": "Op dit moment open" + } + } + }, + "1": { + "options": { + "0": { + "question": "Alle recyclingtypes" + }, + "1": { + "question": "Recycling van batterijen" + }, + "2": { + "question": "Recycling van drankpakken" + }, + "3": { + "question": "Recycling van blikken" + }, + "4": { + "question": "Recycling van kleding" + }, + "5": { + "question": "Recycling van frituurvet" + }, + "6": { + "question": "Recycling van motorolie" + }, + "7": { + "question": "Recycling van groen afval" + }, + "8": { + "question": "Recycling van glazen flessen" + }, + "9": { + "question": "Recycling van glas" + }, + "10": { + "question": "Recycling van kranten" + }, + "11": { + "question": "Recycling van papier" + }, + "12": { + "question": "Recycling van plastic flessen" + }, + "13": { + "question": "Recycling van plastic verpakking" + }, + "14": { + "question": "Recycling van plastic" + }, + "15": { + "question": "Recycling van oud metaal" + }, + "16": { + "question": "Recycling van kleine elektrische apparaten" + }, + "17": { + "question": "Recycling van restafval" + } + } + } + }, + "name": "Recycling", + "presets": { + "0": { + "title": "recycling container" + }, + "1": { + "title": "recycling centre" + } + }, + "tagRenderings": { + "container-location": { + "mappings": { + "0": { + "then": "Dit is een ondergrondse container" + }, + "1": { + "then": "Deze container bevindt zich binnen" + }, + "2": { + "then": "Deze container is buiten" + } + }, + "question": "Waar bevindt deze container zich?" + }, + "opening_hours": { + "mappings": { + "0": { + "then": "24/7" + } + }, + "question": "Wat zijn de openingstijden van deze recyclingfaciliteit?" + }, + "operator": { + "question": "Wat is de beheerder van deze recyclingfaciliteit?", + "render": "Deze recyclingfaciliteit wordt beheerd door {operator}" + }, + "recycling-accepts": { + "mappings": { + "0": { + "then": "Batterijen kunnen hier gerecycled worden" + }, + "1": { + "then": "Drankpakken kunnen hier gerecycled worden" + }, + "2": { + "then": "Blikken kunnen hier gerecycled worden" + }, + "3": { + "then": "Kleren kunnen hier gerecycled worden" + }, + "4": { + "then": "Frituurvet kan hier gerecycled worden" + }, + "5": { + "then": "Motorolie kan hier gerecycled worden" + }, + "6": { + "then": "Groen afval kan hier gerecycled worden" + }, + "7": { + "then": "Organisch afval kan hier gerecycled worden" + }, + "8": { + "then": "Glazen flessen kunnen hier gerecycled worden" + }, + "9": { + "then": "Glas kan hier gerecycled worden" + }, + "10": { + "then": "Kranten kunnen hier gerecycled worden" + }, + "11": { + "then": "Papier kan hier gerecycled worden" + }, + "12": { + "then": "Plastic flessen kunnen hier gerecycled worden" + }, + "13": { + "then": "Plastic verpakking kan hier gerecycled worden" + }, + "14": { + "then": "Plastic kan hier gerecycled worden" + }, + "15": { + "then": "Oud metaal kan hier gerecycled worden" + }, + "16": { + "then": "Schoenen kunnen hier gerecycled worden" + }, + "17": { + "then": "Kleine elektrische apparaten kunnen hier gerecycled worden" + }, + "18": { + "then": "Kleine elektrische apparaten kunnen hier gerecycled worden" + }, + "19": { + "then": "Restafval kan hier gerecycled worden" + } + }, + "question": "Wat kan hier gerecycled worden?" + }, + "recycling-centre-name": { + "mappings": { + "0": { + "then": "Dit recyclagecentrum heeft geen specifieke naam" + } + }, + "question": "Wat is de naam van dit recyclagecentrum?", + "render": "Dit recyclagecentrum heet {name}" + }, + "recycling-type": { + "mappings": { + "0": { + "then": "Dit is een recyclingcontainer" + }, + "1": { + "then": "Dit is een recyclingcentrum" + }, + "2": { + "then": "Afvalcontainer voor restafval" + } + }, + "question": "Wat voor soort recycling is dit?" + } + }, + "title": { + "mappings": { + "0": { + "then": "Recyclingcentrum" + }, + "1": { + "then": "Recyclingcontainer" + }, + "2": { + "then": "Recyclingcontainer" + } + }, + "render": "Recyclingfaciliteit" + } + }, "shops": { "description": "Een winkel", "name": "Winkel", @@ -4643,10 +4852,10 @@ "Operator tag": { "mappings": { "0": { - "then": "Dit gebied wordt beheerd door Natuurpunt" + "then": "Dit gebied wordt beheerd door Natuurpunt" }, "1": { - "then": "Dit gebied wordt beheerd door {operator}" + "then": "Dit gebied wordt beheerd door {operator}" } }, "question": "Wie beheert deze wandeltocht?", @@ -4848,6 +5057,40 @@ }, "waste_basket": { "description": "Dit is een publieke vuilnisbak waar je je afval kan weggooien.", + "filter": { + "0": { + "options": { + "0": { + "question": "Alle soorten" + }, + "1": { + "question": "Vuilnisbak voor sigarettenpeuken" + }, + "2": { + "question": "Vuilnisbak voor (vervallen) medicatie en drugs" + }, + "3": { + "question": "Vuilnisbak voor hondenuitwerpselen" + }, + "4": { + "question": "Vuilnisbak voor zwerfvuil" + }, + "5": { + "question": "Vuilnisbak voor injectienaalden en andere scherpe voorwerpen" + }, + "6": { + "question": "Vuilnisbak voor plastic" + } + } + }, + "1": { + "options": { + "0": { + "question": "Vuilnisbak met verdeler voor hondenpoepzakjes" + } + } + } + }, "mapRendering": { "0": { "iconSize": { @@ -4899,6 +5142,9 @@ }, "5": { "then": "Een vuilnisbak voor injectienaalden en andere scherpe voorwerpen" + }, + "6": { + "then": "Een vuilnisbak voor plastic" } }, "question": "Wat voor soort vuilnisbak is dit?" @@ -4908,6 +5154,24 @@ "render": "Vuilnisbak" } }, + "waste_disposal": { + "tagRenderings": { + "disposal-location": { + "mappings": { + "0": { + "then": "Dit is een ondergrondse container" + }, + "1": { + "then": "Deze container bevindt zich binnen" + }, + "2": { + "then": "Deze container is buiten" + } + }, + "question": "Waar bevindt deze container zich?" + } + } + }, "watermill": { "description": "Watermolens", "name": "Watermolens", @@ -4939,10 +5203,10 @@ "Operator tag": { "mappings": { "0": { - "then": "Dit gebied wordt beheerd door Natuurpunt" + "then": "Dit gebied wordt beheerd door Natuurpunt" }, "1": { - "then": "Dit gebied wordt beheerd door {operator}" + "then": "Dit gebied wordt beheerd door {operator}" } }, "question": "Wie beheert dit pad?", diff --git a/langs/themes/en.json b/langs/themes/en.json index ccdc4f080..fb0db2db4 100644 --- a/langs/themes/en.json +++ b/langs/themes/en.json @@ -1286,6 +1286,10 @@ "shortDescription": "Map all the trees", "title": "Trees" }, + "waste": { + "description": "Map showing waste baskets and recycling facilities.", + "title": "Waste" + }, "waste_basket": { "description": "On this map, you'll find waste baskets near you. If a waste basket is missing on this map, you can add it yourself", "shortDescription": "A map with waste baskets", diff --git a/langs/themes/icon.json b/langs/themes/icon.json new file mode 100644 index 000000000..9544aaac9 --- /dev/null +++ b/langs/themes/icon.json @@ -0,0 +1,21 @@ +{ + "buurtnatuur": { + "overrideAll": { + "tagRenderings+": { + "1": { + "mappings": { + "1": { + "then": "./assets/themes/buurtnatuur/Natuurpunt.jpg" + }, + "2": { + "then": "./assets/themes/buurtnatuur/Natuurpunt.jpg" + }, + "3": { + "then": "./assets/themes/buurtnatuur/ANB.jpg" + } + } + } + } + } + } +} \ No newline at end of file diff --git a/langs/themes/nl.json b/langs/themes/nl.json index 4e77e8d09..3b591ad64 100644 --- a/langs/themes/nl.json +++ b/langs/themes/nl.json @@ -134,13 +134,13 @@ "1": { "mappings": { "1": { - "then": "Dit gebied wordt beheerd door Natuurpunt" + "then": "Dit gebied wordt beheerd door Natuurpunt" }, "2": { - "then": "Dit gebied wordt beheerd door {operator}" + "then": "Dit gebied wordt beheerd door {operator}" }, "3": { - "then": "Dit gebied wordt beheerd door het Agentschap Natuur en Bos" + "then": "Dit gebied wordt beheerd door het Agentschap Natuur en Bos" } }, "question": "Wie beheert dit gebied?", @@ -1050,6 +1050,14 @@ "shortDescription": "Breng bomen in kaart", "title": "Bomen" }, + "waste": { + "description": "Kaart met afvalbakken en recyclingfaciliteiten.", + "title": "Afval" + }, + "waste_assen": { + "description": "Kaart met afvalbakken en recyclingfaciliteiten + een dataset voor Assen.", + "title": "Afval - Assen" + }, "waste_basket": { "description": "Op deze kaart vind je vuilnisbakken waar je afval in kan smijten. Ontbreekt er een vuilnisbak? Dan kan je die zelf toevoegen", "shortDescription": "Een kaart met vuilnisbakken", diff --git a/scripts/fixImagesInTagRenderings.ts b/scripts/fixImagesInTagRenderings.ts index 704896521..5440238fb 100644 --- a/scripts/fixImagesInTagRenderings.ts +++ b/scripts/fixImagesInTagRenderings.ts @@ -56,7 +56,6 @@ function main() { const path = args[0] const iconClass = args[1] ?? "small" const targetFile = args[2] ?? path + ".autoconverted.json" - console.log("Fixing images in " + path) const parsed = JSON.parse(readFileSync(path, "UTF8")) const converted = new ConvertImagesToIcon(iconClass).convertStrict(parsed, "While running the fixImagesInTagRenderings-script") writeFileSync(targetFile, JSON.stringify(converted, null, " ")) diff --git a/scripts/generateLayerOverview.ts b/scripts/generateLayerOverview.ts index 4ee9aa129..a14a88623 100644 --- a/scripts/generateLayerOverview.ts +++ b/scripts/generateLayerOverview.ts @@ -169,7 +169,7 @@ class LayerOverviewUtils { for (const sharedLayerJson of layerFiles) { const context = "While building builtin layer " + sharedLayerJson.path const fixed = prepLayer.convertStrict(sharedLayerJson.parsed, context) - const validator = new ValidateLayer(knownImagePaths, sharedLayerJson.path, true); + const validator = new ValidateLayer(sharedLayerJson.path, true); validator.convertStrict(fixed, context) if (sharedLayers.has(fixed.id)) { @@ -200,7 +200,10 @@ class LayerOverviewUtils { new PrevalidateTheme().convertStrict(themeFile, themePath) themeFile = new PrepareTheme(convertState).convertStrict(themeFile, themePath) - new ValidateThemeAndLayers(knownImagePaths, themePath, true) + if(knownImagePaths === undefined){ + throw "Could not load known images/licenses" + } + new ValidateThemeAndLayers(knownImagePaths, themePath, true, convertState.tagRenderings) .convertStrict(themeFile, themePath) this.writeTheme(themeFile) diff --git a/test/CodeQuality.spec.ts b/test/CodeQuality.spec.ts index 9962892e1..7b3ce78c5 100644 --- a/test/CodeQuality.spec.ts +++ b/test/CodeQuality.spec.ts @@ -2,34 +2,48 @@ import T from "./TestHelper"; import {exec} from "child_process"; export default class CodeQualitySpec extends T { + constructor() { super([ [ "no constructor.name in compiled code", () => { - - const excludedDirs = [".git", "node_modules", "dist", ".cache", ".parcel-cache", "assets"] - - exec("grep \"constructor.name\" -r . " + excludedDirs.map(d => "--exclude-dir=" + d).join(" "), ((error, stdout, stderr) => { - if (error?.message?.startsWith("Command failed: grep")) { - return; - } - if (error !== null) { - throw error - - } - if (stderr !== "") { - throw stderr - } - - const found = stdout.split("\n").filter(s => s !== "").filter(s => s.startsWith("test/")); - if (found.length > 0) { - throw "Found a 'constructor.name' at " + found.join(", ") + ". This is not allowed, as minification does erase names." - } - - })) - - } - ] + CodeQualitySpec.detectInCode("constructor\\.name", "This is not allowed, as minification does erase names.") + }], + [ + "no reverse in compiled code", () => { + CodeQualitySpec.detectInCode("reverse()", "Reverse is stateful and changes the source list. This often causes subtle bugs") + }] ]); } + + /** + * + * @param forbidden: a GREP-regex. This means that '.' is a wildcard and should be escaped to match a literal dot + * @param reason + * @private + */ + private static detectInCode(forbidden: string, reason: string) { + + const excludedDirs = [".git", "node_modules", "dist", ".cache", ".parcel-cache", "assets"] + + exec("grep -n \"" + forbidden + "\" -r . " + excludedDirs.map(d => "--exclude-dir=" + d).join(" "), ((error, stdout, stderr) => { + if (error?.message?.startsWith("Command failed: grep")) { + console.warn("Command failed!") + return; + } + if (error !== null) { + throw error + + } + if (stderr !== "") { + throw stderr + } + + const found = stdout.split("\n").filter(s => s !== "").filter(s => !s.startsWith("./test/")); + if (found.length > 0) { + throw `Found a '${forbidden}' at \n ${found.join("\n ")}.\n ${reason}` + } + + })) + } } \ No newline at end of file diff --git a/test/ImageAttribution.spec.ts b/test/ImageAttribution.spec.ts index 10883105a..86b394a79 100644 --- a/test/ImageAttribution.spec.ts +++ b/test/ImageAttribution.spec.ts @@ -10,7 +10,7 @@ export default class ImageAttributionSpec extends T { [ "Should find all the images", () => { - const images = new Set(new ExtractImages(true).convertStrict( cyclofix, "test")) + const images = new Set(new ExtractImages(true, new Map()).convertStrict( cyclofix, "test")) const expectedValues = [ './assets/layers/bike_repair_station/repair_station.svg', './assets/layers/bike_repair_station/repair_station_pump.svg', diff --git a/test/ImportMultiPolygon.spec.ts b/test/ImportMultiPolygon.spec.ts new file mode 100644 index 000000000..e4b480f92 --- /dev/null +++ b/test/ImportMultiPolygon.spec.ts @@ -0,0 +1,219 @@ +import T from "./TestHelper"; +import CreateMultiPolygonWithPointReuseAction from "../Logic/Osm/Actions/CreateMultiPolygonWithPointReuseAction"; +import { Tag } from "../Logic/Tags/Tag"; +import FeaturePipelineState from "../Logic/State/FeaturePipelineState"; +import { Changes } from "../Logic/Osm/Changes"; +import {ChangesetHandler} from "../Logic/Osm/ChangesetHandler"; +import * as Assert from "assert"; + +export default class ImportMultiPolygonSpec extends T { + + constructor() { + super([ + ["Correct changeset", + async () => { + + const feature = { + "type": "Feature", + "properties": { + "osm_id": "41097039", + "size_grb_building": "1374.89", + "addr:housenumber": "53", + "addr:street": "Startelstraat", + "building": "house", + "source:geometry:entity": "Gbg", + "source:geometry:date": "2014-04-28", + "source:geometry:oidn": "150044", + "source:geometry:uidn": "5403181", + "H_DTM_MIN": "50.35", + "H_DTM_GEM": "50.97", + "H_DSM_MAX": "59.40", + "H_DSM_P99": "59.09", + "HN_MAX": "8.43", + "HN_P99": "8.12", + "detection_method": "derived from OSM landuse: farmyard", + "auto_target_landuse": "farmyard", + "size_source_landuse": "8246.28", + "auto_building": "farm", + "id": "41097039", + "_lat": "50.84633355000016", + "_lon": "5.262964150000011", + "_layer": "grb", + "_length": "185.06002152312757", + "_length:km": "0.2", + "_now:date": "2022-02-22", + "_now:datetime": "2022-02-22 10:15:51", + "_loaded:date": "2022-02-22", + "_loaded:datetime": "2022-02-22 10:15:51", + "_geometry:type": "Polygon", + "_intersects_with_other_features": "", + "_country": "be", + "_overlaps_with_buildings": "[]", + "_overlap_percentage": "null", + "_grb_date": "2014-04-28", + "_grb_ref": "Gbg/150044", + "_building:min_level": "", + "_surface": "548.1242491529038", + "_surface:ha": "0", + "_reverse_overlap_percentage": "null", + "_imported_osm_object_found": "false", + "_imported_osm_still_fresh": "false", + "_target_building_type": "house" + }, + "geometry": { + "type": "Polygon", + "coordinates": <[number, number][][]>[ + [ + [ + 5.262684300000043, + 50.84624409999995 + ], + [ + 5.262777500000024, + 50.84620759999988 + ], + [ + 5.262798899999998, + 50.84621390000019 + ], + [ + 5.262999799999994, + 50.84619519999999 + ], + [ + 5.263107500000007, + 50.84618920000014 + ], + [ + 5.263115, + 50.84620990000026 + ], + [ + 5.26310279999998, + 50.84623050000014 + ], + [ + 5.263117999999977, + 50.846247400000166 + ], + [ + 5.263174599999989, + 50.84631019999971 + ], + [ + 5.263166999999989, + 50.84631459999995 + ], + [ + 5.263243999999979, + 50.84640239999989 + ], + [ + 5.2631607000000065, + 50.84643459999996 + ], + [ + 5.26313309999997, + 50.84640089999985 + ], + [ + 5.262907499999996, + 50.84647790000018 + ], + [ + 5.2628939999999576, + 50.846463699999774 + ], + [ + 5.262872100000033, + 50.846440700000294 + ], + [ + 5.262784699999991, + 50.846348899999924 + ], + [ + 5.262684300000043, + 50.84624409999995 + ] + ], + [ + [ + 5.262801899999976, + 50.84623269999982 + ], + [ + 5.2629535000000285, + 50.84638830000012 + ], + [ + 5.263070700000018, + 50.84634720000008 + ], + [ + 5.262998000000025, + 50.84626279999982 + ], + [ + 5.263066799999966, + 50.84623959999975 + ], + [ + 5.263064000000004, + 50.84623330000007 + ], + [ + 5.263009599999997, + 50.84623730000026 + ], + [ + 5.263010199999956, + 50.84621629999986 + ], + [ + 5.262801899999976, + 50.84623269999982 + ] + ] + ] + }, + } + + const innerRings = [...feature.geometry.coordinates] + innerRings.splice(0, 1) + + const action = new CreateMultiPolygonWithPointReuseAction( + [new Tag("building", "yes")], + feature.geometry.coordinates[0], + innerRings, + undefined, + [], + "import" + ) + const descriptions = await action.Perform(new Changes()) + + function getCoor(id: number): {lat: number, lon:number} { + return descriptions.find(d => d.type === "node" && d.id === id).changes + } + + const ways= descriptions.filter(d => d.type === "way") + T.isTrue(ways[0].id == -18, "unexpected id") + T.isTrue(ways[1].id == -27, "unexpected id") + const outer = ways[0].changes["coordinates"] + const outerExpected = [[5.262684300000043,50.84624409999995],[5.262777500000024,50.84620759999988],[5.262798899999998,50.84621390000019],[5.262999799999994,50.84619519999999],[5.263107500000007,50.84618920000014],[5.263115,50.84620990000026],[5.26310279999998,50.84623050000014],[5.263117999999977,50.846247400000166],[5.263174599999989,50.84631019999971],[5.263166999999989,50.84631459999995],[5.263243999999979,50.84640239999989],[5.2631607000000065,50.84643459999996],[5.26313309999997,50.84640089999985],[5.262907499999996,50.84647790000018],[5.2628939999999576,50.846463699999774],[5.262872100000033,50.846440700000294],[5.262784699999991,50.846348899999924],[5.262684300000043,50.84624409999995]] + T.listIdentical(feature.geometry.coordinates[0], outer) + const inner = ways[1].changes["coordinates"] + T.listIdentical(feature.geometry.coordinates[1], inner) + const members = <{type: string, role: string, ref: number}[]> descriptions.find(d => d.type === "relation").changes["members"] + T.isTrue(members[0].role == "outer", "incorrect role") + T.isTrue(members[1].role == "inner", "incorrect role") + T.isTrue(members[0].type == "way", "incorrect type") + T.isTrue(members[1].type == "way", "incorrect type") + T.isTrue(members[0].ref == -18, "incorrect id") + T.isTrue(members[1].ref == -27, "incorrect id") + }] + ]); + } + + +} \ No newline at end of file diff --git a/test/LegacyThemeLoader.spec.ts b/test/LegacyThemeLoader.spec.ts index 83284c5be..8b1972120 100644 --- a/test/LegacyThemeLoader.spec.ts +++ b/test/LegacyThemeLoader.spec.ts @@ -5,7 +5,7 @@ import {TagRenderingConfigJson} from "../Models/ThemeConfig/Json/TagRenderingCon import {AddMiniMap} from "../Models/ThemeConfig/Conversion/PrepareTheme"; import {DetectMappingsWithImages, DetectShadowedMappings} from "../Models/ThemeConfig/Conversion/Validation"; import * as Assert from "assert"; -import {FixImages} from "../Models/ThemeConfig/Conversion/FixImages"; +import {ExtractImages, FixImages} from "../Models/ThemeConfig/Conversion/FixImages"; export default class LegacyThemeLoaderSpec extends T { @@ -144,7 +144,7 @@ export default class LegacyThemeLoaderSpec extends T { ] } - private static readonly verkeerde_borden ={ + private static readonly verkeerde_borden = { "id": "https://raw.githubusercontent.com/seppesantens/MapComplete-Themes/main/VerkeerdeBordenDatabank/VerkeerdeBordenDatabank.json", "title": { "nl": "VerkeerdeBordenDatabank", @@ -242,7 +242,7 @@ export default class LegacyThemeLoaderSpec extends T { "icon": "./TS_bolt.svg", iconBadges: [{ if: "id=yes", - then:{ + then: { mappings: [ { if: "id=yes", @@ -351,7 +351,6 @@ export default class LegacyThemeLoaderSpec extends T { } - constructor() { super([ ["Walking_node_theme", () => { @@ -423,9 +422,9 @@ export default class LegacyThemeLoaderSpec extends T { } ] }, "test"); - T.isTrue(r.errors.length > 0, "Failing case 0 is not detected") - - const r0 = new DetectShadowedMappings().convert( { + T.isTrue(r.warnings.length > 0, "Failing case 0 is not detected") + T.isTrue(r.warnings[0].indexOf("The mapping key=value is fully matched by a previous mapping (namely 0)") >= 0, "Error message does not contain tag and indices") + const r0 = new DetectShadowedMappings().convert({ mappings: [ { if: {or: ["key=value", "x=y"]}, @@ -437,37 +436,75 @@ export default class LegacyThemeLoaderSpec extends T { } ] }, "test"); - T.isTrue(r0.errors.length > 0, "Failing case 1 is not detected") + T.isTrue(r0.warnings.length > 0, "Failing case 1 is not detected") } ], - ["Images are rewritten", () => { - const fixed = new FixImages(new Set()).convertStrict(LegacyThemeLoaderSpec.verkeerde_borden, "test") - const fixedValue = fixed.layers[0]["mapRendering"][0].icon - Assert.equal("https://raw.githubusercontent.com/seppesantens/MapComplete-Themes/main/VerkeerdeBordenDatabank/TS_bolt.svg", - fixedValue) + ["Images are rewritten", () => { + const fixed = new FixImages(new Set()).convertStrict(LegacyThemeLoaderSpec.verkeerde_borden, "test") + const fixedValue = fixed.layers[0]["mapRendering"][0].icon + Assert.equal("https://raw.githubusercontent.com/seppesantens/MapComplete-Themes/main/VerkeerdeBordenDatabank/TS_bolt.svg", + fixedValue) - const fixedMapping = fixed.layers[0]["mapRendering"][0].iconBadges[0].then.mappings[0].then - Assert.equal("https://raw.githubusercontent.com/seppesantens/MapComplete-Themes/main/VerkeerdeBordenDatabank/Something.svg", - fixedMapping) - } ], - ["Images in 'thens' are detected", () => { - const r = new DetectMappingsWithImages().convert({ + const fixedMapping = fixed.layers[0]["mapRendering"][0].iconBadges[0].then.mappings[0].then + Assert.equal("https://raw.githubusercontent.com/seppesantens/MapComplete-Themes/main/VerkeerdeBordenDatabank/Something.svg", + fixedMapping) + }], + ["Images in 'thens' are detected", () => { + const r = new DetectMappingsWithImages().convert({ "mappings": [ - { - "if": "bicycle_parking=stands", - "then": { - "en": "Staple racks ", - "nl": "Nietjes ", - "fr": "Arceaux ", - "gl": "De roda (Stands) ", - "de": "Fahrradbügel ", - "hu": "Korlát ", - "it": "Archetti ", - "zh_Hant": "單車架 " - } - }]}, "test"); - T.isTrue(r.warnings.length > 0, "No images found"); - }] + { + "if": "bicycle_parking=stands", + "then": { + "en": "Staple racks ", + "nl": "Nietjes ", + "fr": "Arceaux ", + "gl": "De roda (Stands) ", + "de": "Fahrradbügel ", + "hu": "Korlát ", + "it": "Archetti ", + "zh_Hant": "單車架 " + } + }] + }, "test"); + const errors = r.errors; + T.isTrue(errors.length > 0, "No images found"); + T.isTrue(errors.some(msg => msg.indexOf("./assets/layers/bike_parking/staple.svg") >= 0), "staple.svg not mentioned"); + }], + ["Images in 'thens' icons are detected", () => { + const r = new ExtractImages(true, new Map()).convert({ + "layers": [ + { + tagRenderings: [ + { + "mappings": [ + { + "if": "bicycle_parking=stands", + "then": { + "en": "Staple racks", + }, + "icon": { + path: "./assets/layers/bike_parking/staple.svg", + class: "small" + } + }, + { + "if": "bicycle_parking=stands", + "then": { + "en": "Bollard", + }, + "icon": "./assets/layers/bike_parking/bollard.svg", + } + ] + } + ] + } + ] + }, "test"); + const images = r.result + T.isTrue(images.length > 0, "No images found"); + T.isTrue(images.findIndex(img => img =="./assets/layers/bike_parking/staple.svg") >= 0, "staple.svg not mentioned"); + T.isTrue(images.findIndex(img => img == "./assets/layers/bike_parking/bollard.svg") >= 0, "bollard.svg not mentioned"); + }] ] ); } diff --git a/test/TestAll.ts b/test/TestAll.ts index de9211864..37f408220 100644 --- a/test/TestAll.ts +++ b/test/TestAll.ts @@ -20,6 +20,7 @@ import CreateNoteImportLayerSpec from "./CreateNoteImportLayer.spec"; import ValidatedTextFieldTranslationsSpec from "./ValidatedTextFieldTranslations.spec"; import CreateCacheSpec from "./CreateCache.spec"; import CodeQualitySpec from "./CodeQuality.spec"; +import ImportMultiPolygonSpec from "./ImportMultiPolygon.spec"; async function main() { @@ -43,7 +44,8 @@ async function main() { new CreateNoteImportLayerSpec(), new ValidatedTextFieldTranslationsSpec(), new CreateCacheSpec(), - new CodeQualitySpec() + new CodeQualitySpec(), + new ImportMultiPolygonSpec() ] ScriptUtils.fixUtils(); const realDownloadFunc = Utils.externalDownloadFunction; @@ -59,7 +61,7 @@ async function main() { let args = [...process.argv] args.splice(0, 2) - args = args.map(a => a.toLowerCase()) + args = args.map(a => a.toLowerCase().replace(/"/g, "")) const allFailures: { testsuite: string, name: string, msg: string } [] = [] let testsToRun = allTests @@ -72,6 +74,7 @@ async function main() { } }) testsToRun = allTests.filter(t => args.indexOf(t.name.toLowerCase()) >= 0) + console.log("Only running test "+testsToRun.join(", ")) } if (testsToRun.length == 0) {