diff --git a/packages/geoview-core/public/configs/validator/01-simple.json b/packages/geoview-core/public/configs/validator/01-simple-esri-feature.json similarity index 81% rename from packages/geoview-core/public/configs/validator/01-simple.json rename to packages/geoview-core/public/configs/validator/01-simple-esri-feature.json index b2a2f7bacfe..a6348201936 100644 --- a/packages/geoview-core/public/configs/validator/01-simple.json +++ b/packages/geoview-core/public/configs/validator/01-simple-esri-feature.json @@ -64,13 +64,24 @@ "hasDefault": false, "info": [ { + "visible": true, "label": "", - "visible": false, + "values": [], "settings": { - "symbol": "square", + "type": "simpleSymbol", + "rotation": 0, + "color": "rgba(156,0,130,1)", "stroke": { - "color": "rgba(128,128,128,1)" - } + "color": "rgba(0,0,0,1)", + "lineStyle": "solid", + "width": 1 + }, + "size": 2.668, + "symbol": "square", + "offset": [ + 0, + 0 + ] } } ] diff --git a/packages/geoview-core/public/configs/validator/02-unique-value-esri-dynamic.json b/packages/geoview-core/public/configs/validator/02-unique-value-esri-dynamic.json new file mode 100644 index 00000000000..0fded235cf3 --- /dev/null +++ b/packages/geoview-core/public/configs/validator/02-unique-value-esri-dynamic.json @@ -0,0 +1,62 @@ +{ + "map": { + "interaction": "dynamic", + "viewSettings": { + "minZoom": 5, + "projection": 3978 + }, + "basemapOptions": { + "basemapId": "transport", + "shaded": false, + "labeled": true + }, + "listOfGeoviewLayerConfig": [ + { + "geoviewLayerId": "LYR1", + "geoviewLayerName": { + "en": "Earthquakes", + "fr": "Tremblements de terre" + }, + "metadataAccessPath": { + "en": "https://maps-cartes.services.geo.ca/server_serveur/rest/services/NRCan/earthquakes_en/MapServer/", + "fr": "https://maps-cartes.services.geo.ca/server_serveur/rest/services/NRCan/earthquakes_fr/MapServer/" + }, + "geoviewLayerType": "esriDynamic", + "listOfLayerEntryConfig": [ + { + "layerId": "0", + "layerName": { + "en": "Earthquakes 1980-1990", + "fr": "Tremblements de terre 1980-1990" + }, + "initialSettings": { + "controls": { + "query": true + }, + "states": { + "queryable": true + } + }, + "attributions": ["Ressources naturelles Canada, Secteur des terres et des minéraux, Commission Géologique du Canada, Service canadien d\\'information sur les risques"] + }, + { + "layerId": "1", + "layerName": { + "en": "Earthquakes 1990-2000", + "fr": " Tremblements de terre 1990-2000" + }, + "attributions": ["Ressources naturelles Canada, Secteur des terres et des minéraux, Commission Géologique du Canada, Service canadien d\\'information sur les risques"] + } + ] + } + ] + }, + "components": ["overview-map"], + "footerBar": { + "tabs": { + "core": ["legend", "layers", "details", "data-table"] + } + }, + "corePackages": [], + "theme": "geo.ca" +} diff --git a/packages/geoview-core/public/configs/validator/02-unique-value.json b/packages/geoview-core/public/configs/validator/02-unique-value.json deleted file mode 100644 index c322723dd3b..00000000000 --- a/packages/geoview-core/public/configs/validator/02-unique-value.json +++ /dev/null @@ -1,402 +0,0 @@ -{ - "map": { - "interaction": "dynamic", - "viewSettings": { - "minZoom": 5, - "projection": 3978 - }, - "basemapOptions": { - "basemapId": "transport", - "shaded": false, - "labeled": true - }, - "listOfGeoviewLayerConfig": [ - { - "geoviewLayerId": "LYR1", - "geoviewLayerName": { - "en": "Earthquakes", - "fr": "Tremblements de terre" - }, - "metadataAccessPath": { - "en": "https://maps-cartes.services.geo.ca/server_serveur/rest/services/NRCan/earthquakes_en/MapServer/", - "fr": "https://maps-cartes.services.geo.ca/server_serveur/rest/services/NRCan/earthquakes_fr/MapServer/" - }, - "geoviewLayerType": "esriDynamic", - "listOfLayerEntryConfig": [ - { - "layerId": "0", - "geometryType": "point", - "layerName": { - "en": "Earthquakes 1980-1990", - "fr": "Tremblements de terre 1980-1990" - }, - "initialSettings": { - "controls": { - "query": true - }, - "states": { - "queryable": true - } - }, - "attributions": ["Ressources naturelles Canada, Secteur des terres et des minéraux, Commission Géologique du Canada, Service canadien d\\'information sur les risques"], - "source": { - "layerFilter": "", - "maxRecordCount": 0, - "featureInfo": { - "queryable": true, - "nameField": "date", - "outfields": [ - { - "name": "magnitude_codelist", - "alias": "magnitude_codelist", - "type": "string", - "domain": [] - }, - { - "name": "magnitude", - "alias": "Magnitude", - "type": "number", - "domain": [] - }, - { - "name": "magnitude_type", - "alias": "Magnitude Type", - "type": "string", - "domain": [] - }, - { - "name": "date", - "alias": "Date", - "type": "string", - "domain": [] - }, - { - "name": "place", - "alias": "Place", - "type": "string", - "domain": [] - }, - { - "name": "depth", - "alias": "Depth", - "type": "number", - "domain": [] - }, - { - "name": "latitude", - "alias": "Latitude", - "type": "number", - "domain": [] - }, - { - "name": "longitude", - "alias": "Longitude", - "type": "number", - "domain": [] - }, - { - "name": "OBJECTID", - "alias": "OBJECTID", - "type": "number", - "domain": [] - } - ] - } - }, - "style": { - "type": "uniqueValue", - "fields": ["magnitude_codelist"], - "hasDefault": false, - "info": [ - { - "label": "Magnitude less than 2", - "visible": true, - "values": ["<2"], - "settings": { - "type": "simpleSymbol", - "symbol": "circle", - "stroke": { - "color": "rgba(0,0,0,1)", - "lineStyle": "solid", - "width": 1 - }, - "size": 3.335, - "rotation": 0, - "offset": [0,0], - "color": "rgba(30,0,120,1)" - } - }, - { - "label": "2 to <3", - "visible": true, - "values": ["2"], - "settings": { - "type": "simpleSymbol", - "symbol": "circle", - "stroke": { - "color": "rgba(0,0,0,1)", - "lineStyle": "solid", - "width": 1 - }, - "size": 4.669, - "rotation": 0, - "offset": [0,0], - "color": "rgba(120,0,190,1)" - } - }, - { - "label": "3 to <4", - "visible": true, - "values": ["3"], - "settings": { - "type": "simpleSymbol", - "symbol": "circle", - "stroke": { - "color": "rgba(0,0,0,1)", - "lineStyle": "solid", - "width": 1 - }, - "size": 6.003, - "rotation": 0, - "offset": [0,0], - "color": "rgba(190,0,120,1)" - } - }, - { - "label": "4 to <5", - "visible": true, - "values": ["4"], - "settings": { - "type": "simpleSymbol", - "symbol": "circle", - "stroke": { - "color": "rgba(0,0,0,1)", - "lineStyle": "solid", - "width": 1 - }, - "size": 7.337, - "rotation": 0, - "offset": [0,0], - "color": "rgba(255,0,0,1)" - } - }, - { - "label": "5 to <6", - "visible": true, - "values": ["5"], - "settings": { - "mimeType": "image/png", - "offset": [0,0], - "opacity": 1, - "rotation": 0, - "src": "iVBORw0KGgoAAAANSUhEUgAAACIAAAAfCAYAAACCox+xAAAAAXNSR0IB2cksfwAAAAlwSFlzAAAOxAAADsQBlSsOGwAAA2VJREFUWIXNl39o1GUcx1+3W3uM0209tmBudguuLV0/RqVX80fUDKtp5s5JfyxYQaChkMQSKopZgjRBCO0HFAlSf0y04mwykAnGzKMfiDFo7vrjqHEj9ZOODvx4eeuP79du2u685Zdd73+e5/l8P5/P84Ln+XwevqX8T1TqQY4Sd8wUFcQYsxSYUNVvigoCRIAJoKggPlVtc0G2uOPMg1hrwyJS684Xi0isKCCpVGrdNfPigARUI00GzsPlYdUI0DXjINbaB0Sk7sUFkE7DxiHusNbeLyI/zihIKpWKAISrIAN+hv6xzSyIVW2fZ6ChHDIZCEJmzCnl170AsWQ7ZU4ZY+qTqqHuENxU4kRsaYCXh7XBGNOsqqcL2DsDSC6Qh4C9QFW+DKoKwMPzsrZlNZQwDKo6WADEGaAT6MsF0gfcZ2CfQgvAeguPVv87kw+oL8+uF1TAB41T7zqQhP2SXQIdQHKyz1R3JKnwOLDVwDu9gv/ZO+HpWvDnObSbS2HDwqttf2Ug+psDYeCywpvADqZ4IHNd1glgR8Dao3XI/rYY87vGYOu9MHdWbpjJOnsR3j0FPQkIweivxrSj+m0u/7xVIyIxgbub4OOeBO3HxmD3Enhwbn6I78/BhkH4QSEIX8ThBVTP54sppHzHTzpX5flz8OGiAcoONUNrzdTOX4/CquMQhDSwOQEfFbBH4X1E4FNRaoFtFf7cfpXutwS8TYEQ0wJx9RTAPTZruPLu+9xx0reVODCeg9QA4deCUFHmGP68BHt+duYv3QVzyqC8DN4KQXecZqCaa8rUC5A2wLfidmcxMg5d38FXbn8YPAM7Fzm9paUauuP4gLXA+16DRAAaKyE6Cu3HIQCZkGHbYsvso0leaejHdzAMy2+7KsZTkCpg6UoL752C7QlYZvh9KGDXxEVOxJMQNubLpoAeaotR2RWEiIUDwiPArcBZr0CeAfz9Av0CTxqih5UOVMavOMRUB1HqOi0HehLO8wD4gTXAJ16BOMdiSM8OsPGw5Ex8Ya+w4jnLpp9S7DqplLqxnoBUAo+ttvwSFZ5AiV8vYJ+wG+hfbznSK7QAFcCFGwVpNcbsioq+gdMtC9VIrxAyxmxX1Vbg8xsF6VPVz6YBMFlpVX0VuOV6joWA/PEfIaaVw4tfTk/0N13QHet8ojHFAAAAAElFTkSuQmCC", - "type": "iconSymbol" - } - }, - { - "label": "> or equal to 6", - "visible": true, - "values": [">=6"], - "settings": { - "mimeType": "image/png", - "offset": [0,0], - "opacity": 1, - "rotation": 0, - "src": "iVBORw0KGgoAAAANSUhEUgAAACcAAAAkCAYAAAAKNyObAAAAAXNSR0IB2cksfwAAAAlwSFlzAAAOxAAADsQBlSsOGwAABD5JREFUWIXNmH1MVXUYxz/AlR8vl7KjLQ9UYKC1rhOQTYyZBcEgyRgvTjC3NplBL+KWtaKJvdl6maswX0bmS3PDWOVGRNPRbEot19LR5p22qHZb7thWJ1+4xcPLpT/OvSAGlwscBt9/zjnP+T3f3+fe5/xeznEwg+WYboBgshuuFPjULjM74WYD7wHNQJ8dhnbCPQzoSqn7ReRLOwxtg3PpVLgNEJFSYEbBxZkmuf7zYuBJwDdZU7vgHjJk0OsWpdRyETk5WVNb4AIlTVTgERCRMmBGwMV2meQDNFRBwQ4ASoBNwMBkjO2Ae9AjRFZmQY4L7k2Edg8JmqZlmqZ5alrhAiUtTgdHBDy6AtoPgdfrLQOmFS5KhEKA9CQrsCzZOopICfDMZMyvh7sRCAPwvKGWRDgkJljy52fU0upGUZVZoN9kxRbMgzQdOgzmNz+l1WQkmb8E8+jpVX/fUStu/2U/cHU0uPuA/cCcxOdljN8FYLUpTh+KzHLAY9nwRCMU7TTrQ/UAfldKrRORE6PBfQakAoeA7BDogKGSBpSVEmrmsH7Xi8hf1wZHeuYuALlALfAS4MhP4creDcTdOscq+VhafDv4Phj53k8X6Vu7i+7TBk6sv+1ZrA3D/zTagPABrymljotI47FOkhY8h6+5hp68Raiw8FAQh6vfB0e+p2vN+zgBp1LqfGxsbLlpmj+MlhN0tIrIt0Aa0CCwpmAHamsh/2zOJyYu6FAZLvMq1B2ha087Tn/ogIhsFBFvsLxQppLLQDnQBtS/0krsV2fp2VtJ5ML4sZPP/Arle+jtNHECV4Bq4HAI/Y5rntsHfA181O4h7a6tcKoWliaPnnC0A1buBGAW8B1QAQSdWiYKB/AjcI+mME0h+oao4I1n+4uoKy4YwnKgdzydjXuFUEotMUWiM3VImRe87aIEUIAhJADxgGdK4bBeYlifY62lAXm74eR5yL4boiKtmDMa6lbBlhbA2qm8M6VwKZqUu42hNRTg5z+g6gByvBO1NoP+11cTcdtc6162C2gBTbHalCmE0zQtw22Y8XdqsFAHnw9aO+gr2k0EVgV9jaeJOHqWgaZqwnJcQ6U1hWWADhhTAuf1eksAHs+FfwXqWune3kZgWOwDXgbeNYWSvHrYtoqBjXmEvVAIL7YShvV+sXtK4FI0qXAb4BuAnDfp6zCIAi4BG4BP/M1KgSrg7S0txJw4hy8vlXAYLK39cLquu9yGMR/g6Y8Hc9uBdcBv1zVvwHqHONzWSWpbpxX0CiuAucCftsIZhlF6zWU/8CqwzX8+ks4BmUqpt0SkBkAgHCjCegTsg8M/hQAepdQjIvJNCDkiIpuAY8BB4GagzG64ZGAx0ARUicjlEPMC+sKf/yHwANZ3lUt2weUDlVi75InqIlAAbPb7NY2VECrcfqB74lyDGgC2A2OsypZChbMDbNx+M/rL5n+qSli//HCkMAAAAABJRU5ErkJggg==", - "type": "iconSymbol" - } - } - ] - } - }, - { - "layerId": "1", - "geometryType": "point", - "layerName": { - "en": "Earthquakes 1990-2000", - "fr": " Tremblements de terre 1990-2000" - }, - "attributions": ["Ressources naturelles Canada, Secteur des terres et des minéraux, Commission Géologique du Canada, Service canadien d\\'information sur les risques"], - "source": { - "layerFilter": "", - "maxRecordCount": 0, - "featureInfo": { - "queryable": true, - "nameField": "date", - "outfields": [ - { - "name": "magnitude_codelist", - "alias": "magnitude_codelist", - "type": "date", - "domain": [] - }, - { - "name": "magnitude", - "alias": "Magnitude", - "type": "number", - "domain": [] - }, - { - "name": "magnitude_type", - "alias": "Magnitude Type", - "type": "string", - "domain": [] - }, - { - "name": "date", - "alias": "Date", - "type": "string", - "domain": [] - }, - { - "name": "place", - "alias": "Place", - "type": "string", - "domain": [] - }, - { - "name": "depth", - "alias": "Depth", - "type": "number", - "domain": [] - }, - { - "name": "latitude", - "alias": "Latitude", - "type": "number", - "domain": [] - }, - { - "name": "longitude", - "alias": "Longitude", - "type": "number", - "domain": [] - }, - { - "name": "OBJECTID", - "alias": "OBJECTID", - "type": "number", - "domain": [] - } - ] - } - }, - "style": { - "type": "uniqueValue", - "fields": ["magnitude_codelist"], - "hasDefault": false, - "info": [ - { - "label": "Magnitude less than 2", - "visible": true, - "values": ["<2"], - "settings": { - "type": "simpleSymbol", - "symbol": "circle", - "stroke": { - "color": "rgba(0,0,0,1)", - "lineStyle": "solid", - "width": 1 - }, - "size": 3.335, - "rotation": 0, - "offset": [0,0], - "color": "rgba(30,0,120,1)" - } - }, - { - "label": "2 to <3", - "visible": true, - "values": ["2"], - "settings": { - "type": "simpleSymbol", - "symbol": "circle", - "stroke": { - "color": "rgba(0,0,0,1)", - "lineStyle": "solid", - "width": 1 - }, - "size": 4.669, - "rotation": 0, - "offset": [0,0], - "color": "rgba(120,0,190,1)" - } - }, - { - "label": "3 to <4", - "visible": true, - "values": ["3"], - "settings": { - "type": "simpleSymbol", - "symbol": "circle", - "stroke": { - "color": "rgba(0,0,0,1)", - "lineStyle": "solid", - "width": 1 - }, - "size": 6.003, - "rotation": 0, - "offset": [0,0], - "color": "rgba(190,0,120,1)" - } - }, - { - "label": "4 to <5", - "visible": true, - "values": ["4"], - "settings": { - "type": "simpleSymbol", - "symbol": "circle", - "stroke": { - "color": "rgba(0,0,0,1)", - "lineStyle": "solid", - "width": 1 - }, - "size": 7.337, - "rotation": 0, - "offset": [0,0], - "color": "rgba(255,0,0,1)" - } - }, - { - "label": "5 to <6", - "visible": true, - "values": ["5"], - "settings": { - "mimeType": "image/png", - "offset": [0,0], - "opacity": 1, - "rotation": 0, - "src": "iVBORw0KGgoAAAANSUhEUgAAACIAAAAfCAYAAACCox+xAAAAAXNSR0IB2cksfwAAAAlwSFlzAAAOxAAADsQBlSsOGwAAA2VJREFUWIXNl39o1GUcx1+3W3uM0209tmBudguuLV0/RqVX80fUDKtp5s5JfyxYQaChkMQSKopZgjRBCO0HFAlSf0y04mwykAnGzKMfiDFo7vrjqHEj9ZOODvx4eeuP79du2u685Zdd73+e5/l8P5/P84Ln+XwevqX8T1TqQY4Sd8wUFcQYsxSYUNVvigoCRIAJoKggPlVtc0G2uOPMg1hrwyJS684Xi0isKCCpVGrdNfPigARUI00GzsPlYdUI0DXjINbaB0Sk7sUFkE7DxiHusNbeLyI/zihIKpWKAISrIAN+hv6xzSyIVW2fZ6ChHDIZCEJmzCnl170AsWQ7ZU4ZY+qTqqHuENxU4kRsaYCXh7XBGNOsqqcL2DsDSC6Qh4C9QFW+DKoKwMPzsrZlNZQwDKo6WADEGaAT6MsF0gfcZ2CfQgvAeguPVv87kw+oL8+uF1TAB41T7zqQhP2SXQIdQHKyz1R3JKnwOLDVwDu9gv/ZO+HpWvDnObSbS2HDwqttf2Ug+psDYeCywpvADqZ4IHNd1glgR8Dao3XI/rYY87vGYOu9MHdWbpjJOnsR3j0FPQkIweivxrSj+m0u/7xVIyIxgbub4OOeBO3HxmD3Enhwbn6I78/BhkH4QSEIX8ThBVTP54sppHzHTzpX5flz8OGiAcoONUNrzdTOX4/CquMQhDSwOQEfFbBH4X1E4FNRaoFtFf7cfpXutwS8TYEQ0wJx9RTAPTZruPLu+9xx0reVODCeg9QA4deCUFHmGP68BHt+duYv3QVzyqC8DN4KQXecZqCaa8rUC5A2wLfidmcxMg5d38FXbn8YPAM7Fzm9paUauuP4gLXA+16DRAAaKyE6Cu3HIQCZkGHbYsvso0leaejHdzAMy2+7KsZTkCpg6UoL752C7QlYZvh9KGDXxEVOxJMQNubLpoAeaotR2RWEiIUDwiPArcBZr0CeAfz9Av0CTxqih5UOVMavOMRUB1HqOi0HehLO8wD4gTXAJ16BOMdiSM8OsPGw5Ex8Ya+w4jnLpp9S7DqplLqxnoBUAo+ttvwSFZ5AiV8vYJ+wG+hfbznSK7QAFcCFGwVpNcbsioq+gdMtC9VIrxAyxmxX1Vbg8xsF6VPVz6YBMFlpVX0VuOV6joWA/PEfIaaVw4tfTk/0N13QHet8ojHFAAAAAElFTkSuQmCC", - "type": "iconSymbol" - } - }, - { - "label": "> or equal to 6", - "visible": true, - "values": [">=6"], - "settings": { - "mimeType": "image/png", - "offset": [0,0], - "opacity": 1, - "rotation": 0, - "src": "iVBORw0KGgoAAAANSUhEUgAAACcAAAAkCAYAAAAKNyObAAAAAXNSR0IB2cksfwAAAAlwSFlzAAAOxAAADsQBlSsOGwAABD5JREFUWIXNmH1MVXUYxz/AlR8vl7KjLQ9UYKC1rhOQTYyZBcEgyRgvTjC3NplBL+KWtaKJvdl6maswX0bmS3PDWOVGRNPRbEot19LR5p22qHZb7thWJ1+4xcPLpT/OvSAGlwscBt9/zjnP+T3f3+fe5/xeznEwg+WYboBgshuuFPjULjM74WYD7wHNQJ8dhnbCPQzoSqn7ReRLOwxtg3PpVLgNEJFSYEbBxZkmuf7zYuBJwDdZU7vgHjJk0OsWpdRyETk5WVNb4AIlTVTgERCRMmBGwMV2meQDNFRBwQ4ASoBNwMBkjO2Ae9AjRFZmQY4L7k2Edg8JmqZlmqZ5alrhAiUtTgdHBDy6AtoPgdfrLQOmFS5KhEKA9CQrsCzZOopICfDMZMyvh7sRCAPwvKGWRDgkJljy52fU0upGUZVZoN9kxRbMgzQdOgzmNz+l1WQkmb8E8+jpVX/fUStu/2U/cHU0uPuA/cCcxOdljN8FYLUpTh+KzHLAY9nwRCMU7TTrQ/UAfldKrRORE6PBfQakAoeA7BDogKGSBpSVEmrmsH7Xi8hf1wZHeuYuALlALfAS4MhP4creDcTdOscq+VhafDv4Phj53k8X6Vu7i+7TBk6sv+1ZrA3D/zTagPABrymljotI47FOkhY8h6+5hp68Raiw8FAQh6vfB0e+p2vN+zgBp1LqfGxsbLlpmj+MlhN0tIrIt0Aa0CCwpmAHamsh/2zOJyYu6FAZLvMq1B2ha087Tn/ogIhsFBFvsLxQppLLQDnQBtS/0krsV2fp2VtJ5ML4sZPP/Arle+jtNHECV4Bq4HAI/Y5rntsHfA181O4h7a6tcKoWliaPnnC0A1buBGAW8B1QAQSdWiYKB/AjcI+mME0h+oao4I1n+4uoKy4YwnKgdzydjXuFUEotMUWiM3VImRe87aIEUIAhJADxgGdK4bBeYlifY62lAXm74eR5yL4boiKtmDMa6lbBlhbA2qm8M6VwKZqUu42hNRTg5z+g6gByvBO1NoP+11cTcdtc6162C2gBTbHalCmE0zQtw22Y8XdqsFAHnw9aO+gr2k0EVgV9jaeJOHqWgaZqwnJcQ6U1hWWADhhTAuf1eksAHs+FfwXqWune3kZgWOwDXgbeNYWSvHrYtoqBjXmEvVAIL7YShvV+sXtK4FI0qXAb4BuAnDfp6zCIAi4BG4BP/M1KgSrg7S0txJw4hy8vlXAYLK39cLquu9yGMR/g6Y8Hc9uBdcBv1zVvwHqHONzWSWpbpxX0CiuAucCftsIZhlF6zWU/8CqwzX8+ks4BmUqpt0SkBkAgHCjCegTsg8M/hQAepdQjIvJNCDkiIpuAY8BB4GagzG64ZGAx0ARUicjlEPMC+sKf/yHwANZ3lUt2weUDlVi75InqIlAAbPb7NY2VECrcfqB74lyDGgC2A2OsypZChbMDbNx+M/rL5n+qSli//HCkMAAAAABJRU5ErkJggg==", - "type": "iconSymbol" - } - } - ] - } - } - ] - } - ] - }, - "components": ["overview-map"], - "footerBar": { - "tabs": { - "core": ["legend", "layers", "details", "data-table"] - } - }, - "corePackages": [], - "theme": "geo.ca" -} diff --git a/packages/geoview-core/public/configs/validator/03-class-breaks-esri-feature.json b/packages/geoview-core/public/configs/validator/03-class-breaks-esri-feature.json new file mode 100644 index 00000000000..e9e1a1bb1e9 --- /dev/null +++ b/packages/geoview-core/public/configs/validator/03-class-breaks-esri-feature.json @@ -0,0 +1,44 @@ +{ + "map": { + "interaction": "dynamic", + "viewSettings": { + "minZoom": 5, + "projection": 3978 + }, + "basemapOptions": { + "basemapId": "transport", + "shaded": false, + "labeled": true + }, + "listOfGeoviewLayerConfig": [ + { + "geoviewLayerId": "LYR1", + "geoviewLayerName": { + "en": "ClassBreaks", + "fr": "ClassBreaks" + }, + "metadataAccessPath": { + "en": "https://maps-cartes.ec.gc.ca/arcgis/rest/services/DMS/CSO_volume/MapServer/" + }, + "geoviewLayerType": "esriFeature", + "listOfLayerEntryConfig": [ + { + "layerId": "11", + "layerName": { + "en": "2021 Combined sewer overflow : total volume discharged - Watewater" + }, + "attributions": ["Environment and Climate Change Canada and ministère de l\\'Environnement et de la Lutte contre les changements climatiques du Québec"] + } + ] + } + ] + }, + "components": ["overview-map"], + "footerBar": { + "tabs": { + "core": ["legend", "layers", "details", "data-table"] + } + }, + "corePackages": [], + "theme": "geo.ca" +} \ No newline at end of file diff --git a/packages/geoview-core/public/configs/validator/03-class-breaks.json b/packages/geoview-core/public/configs/validator/03-class-breaks.json deleted file mode 100644 index 127d8fe2e04..00000000000 --- a/packages/geoview-core/public/configs/validator/03-class-breaks.json +++ /dev/null @@ -1,395 +0,0 @@ -{ - "map": { - "interaction": "dynamic", - "viewSettings": { - "minZoom": 5, - "projection": 3978 - }, - "basemapOptions": { - "basemapId": "transport", - "shaded": false, - "labeled": true - }, - "listOfGeoviewLayerConfig": [ - { - "geoviewLayerId": "LYR1", - "geoviewLayerName": { - "en": "ClassBreaks", - "fr": "ClassBreaks" - }, - "metadataAccessPath": { - "en": "https://maps-cartes.ec.gc.ca/arcgis/rest/services/DMS/CSO_volume/MapServer/" - }, - "geoviewLayerType": "esriFeature", - "listOfLayerEntryConfig": [ - { - "layerId": "11", - "geometryType": "point", - "layerName": { - "en": "2021 Combined sewer overflow : total volume discharged - Watewater" - }, - "initialSettings": { - "controls": { - "query": true - }, - "states": { - "queryable": true - } - }, - "attributions": ["Environment and Climate Change Canada and ministère de l’Environnement et de la Lutte contre les changements climatiques du Québec"], - "source": { - "layerFilter": "", - "maxRecordCount": 0, - "featureInfo": { - "queryable": true, - "nameField": "Owner", - "outfields": [ - { - "name": "OBJECTID", - "alias": "OBJECTID", - "type": "number", - "domain": [] - }, - { - "name": "Facility_id", - "alias": "Facility Identification", - "type": "number", - "domain": [] - }, - { - "name": "Owner", - "alias": "Owner Name", - "type": "string", - "domain": [] - }, - { - "name": "System", - "alias": "System Name", - "type": "string", - "domain": [] - }, - { - "name": "City", - "alias": "City", - "type": "string", - "domain": [] - }, - { - "name": "Province_EN", - "alias": "Province", - "type": "string", - "domain": [] - }, - { - "name": "Latitude", - "alias": "Latitude", - "type": "number", - "domain": [] - }, - { - "name": "Longitude", - "alias": "Longitude", - "type": "number", - "domain": [] - }, - { - "name": "Approval_State", - "alias": "Approval State", - "type": "string", - "domain": [] - }, - { - "name": "Jan_CSO_Discharge", - "alias": "January - Was effluent discharged from a CSO points during month?", - "type": "string", - "domain": [] - }, - { - "name": "Jan_CSO_Volume", - "alias": "January - Volume of effluent deposited via a CSO point in the month (m3),", - "type": "number", - "domain": [] - }, - { - "name": "Feb_CSO_Discharge", - "alias": "February - Was effluent discharged from a CSO points during month?", - "type": "string", - "domain": [] - }, - { - "name": "Feb_CSO_Volume", - "alias": "February - Volume of effluent deposited via a CSO point in the month (m3),", - "type": "number", - "domain": [] - }, - { - "name": "Mar_CSO_Discharge", - "alias": "March - Was effluent discharged from a CSO points during month?", - "type": "string", - "domain": [] - }, - { - "name": "Mar_CSO_Volume", - "alias": "March - Volume of effluent deposited via a CSO point in the month (m3)", - "type": "number", - "domain": [] - }, - { - "name": "Apr_CSO_Discharge", - "alias": "April - Was effluent discharged from a CSO points during month?", - "type": "string", - "domain": [] - }, - { - "name": "Apr_CSO_Volume", - "alias": "April - Volume of effluent deposited via a CSO point in the month (m3)", - "type": "number", - "domain": [] - }, - { - "name": "May_CSO_Discharge", - "alias": "May - Was effluent discharged from a CSO points during month?", - "type": "string", - "domain": [] - }, - { - "name": "May_CSO_Volume", - "alias": "May - Volume of effluent deposited via a CSO point in the month (m3)", - "type": "number", - "domain": [] - }, - { - "name": "Jun_CSO_Discharge", - "alias": "June - Was effluent discharged from a CSO points during month?", - "type": "string", - "domain": [] - }, - { - "name": "Jun_CSO_Volume", - "alias": "June - Volume of effluent deposited via a CSO point in the month (m3)", - "type": "number", - "domain": [] - }, - { - "name": "Jul_CSO_Discharge", - "alias": "July - Was effluent discharged from a CSO points during month?", - "type": "string", - "domain": [] - }, - { - "name": "Jul_CSO_Volume", - "alias": "July - Volume of effluent deposited via a CSO point in the month (m3)", - "type": "number", - "domain": [] - }, - { - "name": "Aug_CSO_Discharge", - "alias": "August - Was effluent discharged from a CSO points during month?", - "type": "string", - "domain": [] - }, - { - "name": "Aug_CSO_Volume", - "alias": "August - Volume of effluent deposited via a CSO point in the month (m3)", - "type": "number", - "domain": [] - }, - { - "name": "Sept_CSO_Discharge", - "alias": "September - Was effluent discharged from a CSO points during month?", - "type": "string", - "domain": [] - }, - { - "name": "Sept_CSO_Volume", - "alias": "September - Volume of effluent deposited via a CSO point in the month (m3)", - "type": "number", - "domain": [] - }, - { - "name": "Oct_CSO_Discharge", - "alias": "October - Was effluent discharged from a CSO points during month?", - "type": "string", - "domain": [] - }, - { - "name": "Oct_CSO_Volume", - "alias": "October - Volume of effluent deposited via a CSO point in the month (m3)", - "type": "number", - "domain": [] - }, - { - "name": "Nov_CSO_Discharge", - "alias": "November - Was effluent discharged from a CSO points during month?", - "type": "string", - "domain": [] - }, - { - "name": "Nov_CSO_Volume", - "alias": "November - Volume of effluent deposited via a CSO point in the month (m3)", - "type": "number", - "domain": [] - }, - { - "name": "Dec_CSO_Discharge", - "alias": "December - Was effluent discharged from a CSO points during month?", - "type": "string", - "domain": [] - }, - { - "name": "Dec_CSO_Volume", - "alias": "December - Volume of effluent deposited via a CSO point in the month (m3)", - "type": "number", - "domain": [] - }, - { - "name": "Total_CSO_Volume", - "alias": "Total volume of effluent deposited via a CSO points", - "type": "number", - "domain": [] - } - ] - } - }, - "style": { - "type": "classBreaks", - "fields": ["Total_CSO_Volume"], - "hasDefault": false, - "info": [ - { - "label": "0 m3", - "values": [0,0], - "settings": { - "color": "rgba(0,156,130,1)", - "offset": [0,0], - "rotation": 0, - "size": 4.002, - "stroke": { - "color": "rgba(0,0,0,1)", - "lineStyle": "solid", - "width": 0.5 - }, - "symbol": "circle", - "type": "simpleSymbol" - }, - "visible": true - }, - { - "label": ">0 - 1,000 m3", - "values": [0,1000], - "settings": { - "color": "rgba(232,190,255,1)", - "offset": [0,0], - "rotation": 0, - "size": 5.336, - "stroke": { - "color": "rgba(0,0,0,1)", - "lineStyle": "solid", - "width": 0.5 - }, - "symbol": "circle", - "type": "simpleSymbol" - }, - "visible": true - }, - { - "label": ">1,000 - 500,000 m3", - "values": [1000,500000], - "settings": { - "color": "rgba(223,115,255,1)", - "offset": [0,0], - "rotation": 0, - "size": 10.005, - "stroke": { - "color": "rgba(0,0,0,1)", - "lineStyle": "solid", - "width": 0.5 - }, - "symbol": "circle", - "type": "simpleSymbol" - }, - "visible": true - }, - { - "label": ">500,000 - 5,000,000 m3", - "values": [500000,5000000], - "settings": { - "color": "rgba(197,0,255,1)", - "offset": [0,0], - "rotation": 0, - "size": 14.007, - "stroke": { - "color": "rgba(0,0,0,1)", - "lineStyle": "solid", - "width": 0.5 - }, - "symbol": "circle", - "type": "simpleSymbol" - }, - "visible": true - }, - { - "label": ">5,000,000 - 10,000,000 m3", - "values": [5000000,10000000], - "settings": { - "color": "rgba(132,0,168,1)", - "offset": [0,0], - "rotation": 0, - "size": 18.009, - "stroke": { - "color": "rgba(0,0,0,1)", - "lineStyle": "solid", - "width": 0.5 - }, - "symbol": "circle", - "type": "simpleSymbol" - }, - "visible": true - }, - { - "label": ">10,000,000 m3", - "values": [10000000,999000000], - "settings": { - "color": "rgba(76,0,115,1)", - "offset": [0,0], - "rotation": 0, - "size": 23.345, - "stroke": { - "color": "rgba(0,0,0,1)", - "lineStyle": "solid", - "width": 0.5 - }, - "symbol": "circle", - "type": "simpleSymbol" - }, - "visible": true - }, - { - "label": "No Data", - "values": [999000000,999999999], - "settings": { - "mimeType": "image/png", - "offset": [0,0], - "opacity": 1, - "rotation": 0, - "src": "iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAACXBIWXMAAA7EAAAOxAGVKw4bAAAAT0lEQVQYlWNhgIBQBvxgNQsDA0Noh4vLKiVBQawqzj57xtB59GgYCwMDA4OSoCBDqJYWXiNZCFg59BSeffYMp4J779/DFa7uPHo0jICBqwFMlhJ/CxAqXwAAAABJRU5ErkJggg==", - "type": "iconSymbol" - }, - "visible": true - } - ] - } - } - ] - } - ] - }, - "components": ["overview-map"], - "footerBar": { - "tabs": { - "core": ["legend", "layers", "details", "data-table"] - } - }, - "corePackages": [], - "theme": "geo.ca" -} \ No newline at end of file diff --git a/packages/geoview-core/public/configs/validator/05-metadata-group.json b/packages/geoview-core/public/configs/validator/05-esri-feature-and-dynamic.json similarity index 95% rename from packages/geoview-core/public/configs/validator/05-metadata-group.json rename to packages/geoview-core/public/configs/validator/05-esri-feature-and-dynamic.json index 4edfe618ef1..96e698022cb 100644 --- a/packages/geoview-core/public/configs/validator/05-metadata-group.json +++ b/packages/geoview-core/public/configs/validator/05-esri-feature-and-dynamic.json @@ -101,7 +101,7 @@ "layerId": "4" }, { - "layerId": "51" + "layerId": "5" } ] } diff --git a/packages/geoview-core/public/configs/validator/06-ogc-wms.json b/packages/geoview-core/public/configs/validator/06-ogc-wms.json new file mode 100644 index 00000000000..6fafa73732c --- /dev/null +++ b/packages/geoview-core/public/configs/validator/06-ogc-wms.json @@ -0,0 +1,104 @@ +{ + "map": { + "interaction": "dynamic", + "viewSettings": { + "projection": 3857 + }, + "basemapOptions": { + "basemapId": "transport", + "shaded": false, + "labeled": true + }, + "listOfGeoviewLayerConfig": [ + { + "geoviewLayerId": "wmsLYR1-Root", + "geoviewLayerName": { "en": "Weather Group" }, + "metadataAccessPath": { "en": "https://geo.weather.gc.ca/geomet" }, + "geoviewLayerType": "ogcWms", + "listOfLayerEntryConfig": [ + { + "isLayerGroup": true, + "layerId": "wmsLYR1-Group", + "layerName": { "en": "Group" }, + "listOfLayerEntryConfig": [ + { + "layerId": "CURRENT_CONDITIONS" + }, + { + "layerId": "GDPS.ETA_ICEC", + "layerFilter": "time = date \\'2023-04-18T07:00:00-04:00\\'", + "layerName": { "en": "Ice Cover" }, + "source": { + "wmsStyle": ["SEA_ICECONC"] + } + } + ] + } + ] + }, + { + "geoviewLayerId": "wmsLYR1-msi", + "geoviewLayerName": { "en": "MSI" }, + "metadataAccessPath": { "en": "https://datacube.services.geo.ca/ows/msi" }, + "geoviewLayerType": "ogcWms", + "listOfLayerEntryConfig": [ + { + "layerId": "msi-94-or-more", + "layerName": { "en": "Permanent Snow" }, + "source": { + "wmsStyle": ["msi-binary"], + "featureInfo": { + "queryable": true, + "nameField": "band-0-pixel-value", + "outfields": [ + { + "name": "band-0-pixel-value", + "alias": "Pixel value", + "type": "number", + "domain": null + } + ] + } + } + } + ] + }, + { + "geoviewLayerId": "wmsLYR1-spatiotemporel", + "geoviewLayerName": { "en": "Spatiotemporel" }, + "metadataAccessPath": { "en": "https://geo.weather.gc.ca/geomet" }, + "geoviewLayerType": "ogcWms", + "listOfLayerEntryConfig": [ + { + "layerId": "RADAR_1KM_RSNO", + "layerName": { "en": "Test Spatiotemporel" }, + "source": { + "wmsStyle": ["Radar-Snow_14colors"] + } + } + ] + }, + { + "geoviewLayerId": "LYR1", + "geoviewLayerName": { "en": "Canada Energy Regulator" }, + "metadataAccessPath": { + "en": "https://maps-cartes.services.geo.ca/server_serveur/services/NRCan/CER_Assessments_EN/MapServer/WMSServer" + }, + "geoviewLayerType": "ogcWms", + "listOfLayerEntryConfig": [ + { + "layerId": "0" + } + ] + } + ] + }, + "components": ["overview-map"], + "footerBar": { + "tabs": { + "core": ["legend", "layers", "details", "data-table"] + } + }, + "corePackages": [], + "theme": "geo.ca" +} diff --git a/packages/geoview-core/public/templates/config-sandbox.html b/packages/geoview-core/public/templates/config-sandbox.html index 328af3e2928..f578833c74d 100644 --- a/packages/geoview-core/public/templates/config-sandbox.html +++ b/packages/geoview-core/public/templates/config-sandbox.html @@ -139,11 +139,12 @@

Sandbox Configuration

Configuration to load: @@ -223,6 +224,7 @@

Sandbox Configuration

+      URL:  @@ -320,6 +322,7 @@

Sandbox Configuration

+      URL:  @@ -564,8 +567,8 @@

Sanbox Map

fetchJSONData(); }); - // Set the initial configuration of the drop-down to 'Simple' and load it - configLoaderTab1.value = './configs/validator/01-simple.json'; + // Set the initial configuration of the drop-down to 'Simple EsriFeature' and load it + configLoaderTab1.value = './configs/validator/01-simple-esri-feature.json'; configLoaderTab1.dispatchEvent(new Event('change')); // Create the listener for the Element Displayed drop-down located at the bottom of the output zone. @@ -652,6 +655,7 @@

Sanbox Map

geoCore: '12acd145-626a-49eb-b850-0a59c9bc7506', esriDynamic: 'https://maps-cartes.ec.gc.ca/arcgis/rest/services/CESI/MapServer/', esriFeature: 'https://maps-cartes.services.geo.ca/server_serveur/rest/services/NRCan/Temporal_Test_Bed_en/MapServer/', + ogcWms: 'https://geo.weather.gc.ca/geomet', }; // Get the GeoView Layer Type drop-down object used to select the type of layer. @@ -838,6 +842,7 @@

Sanbox Map

unknown: 'unknown', esriDynamic: 'https://maps-cartes.ec.gc.ca/arcgis/rest/services/CESI/MapServer/', esriFeature: 'https://maps-cartes.services.geo.ca/server_serveur/rest/services/NRCan/Temporal_Test_Bed_en/MapServer/', + ogcWms: 'https://geo.weather.gc.ca/geomet', }; // Get the GeoView Layer Type drop-down object used to select the type of layer. @@ -908,7 +913,11 @@

Sanbox Map

languageTab4 ); // Output instanciation result - metadataLayerTreeString = `${JSON.stringify(metadataLayerTree, undefined, 2)}\n`; + metadataLayerTreeString = `${JSON.stringify(metadataLayerTree, (key, value) => { + if (['', 'layerId', 'layerName', 'isLayerGroup', 'listOfLayerEntryConfig'].includes(key)) return value; + if (/^\d+$/.test(key)) return value; + return undefined; + }, 2)}\n`; layerPathErrorFlag = generateLayerPathErrorFlag(metadataLayerTree); if (metadataLayerTree) printMessage(messageTab4, 'Layer tree is valid'); diff --git a/packages/geoview-core/src/api/config/config-api.ts b/packages/geoview-core/src/api/config/config-api.ts index 3ebc6f253e5..77acb1c302f 100644 --- a/packages/geoview-core/src/api/config/config-api.ts +++ b/packages/geoview-core/src/api/config/config-api.ts @@ -468,7 +468,7 @@ export class ConfigApi { ): Promise { let geoviewLayerConfig: TypeJsonObject | undefined; - // If the lyerType is a GeoCore, we translate it to its GeoView configuration. + // If the layerType is a GeoCore, we translate it to its GeoView configuration. if (layerType === CV_CONFIG_GEOCORE_TYPE) { try { const layerConfig = { geoviewLayerId: serviceAccessString, geoviewLayerType: layerType }; @@ -499,7 +499,7 @@ export class ConfigApi { } /** - * Create the layer tree from the service metadata. + * Create the layer tree from the service metadata. If an error is detected, throw an error. * * @param {string} serviceAccessString The service access string (a URL or a layer identifier). * @param {TypeGeoviewLayerType | CV_CONFIG_GEOCORE_TYPE} layerType The GeoView layer type or 'geoCore'. @@ -520,6 +520,6 @@ export class ConfigApi { await geoviewLayerConfig.fetchServiceMetadata(); if (!geoviewLayerConfig.getErrorDetectedFlag()) return geoviewLayerConfig.getMetadataLayerTree(); } - return []; + throw new MapConfigError('Unable to build metadata layer tree.'); } } diff --git a/packages/geoview-core/src/api/config/types/classes/config-exceptions.ts b/packages/geoview-core/src/api/config/types/classes/config-exceptions.ts index 3c64849cc91..659ab93f17f 100644 --- a/packages/geoview-core/src/api/config/types/classes/config-exceptions.ts +++ b/packages/geoview-core/src/api/config/types/classes/config-exceptions.ts @@ -36,33 +36,6 @@ export class GeoviewLayerConfigError extends ConfigError { } } -export class GeoviewLayerMandatoryError extends ConfigError { - messageList: Record = { - LayerNameMandatory: 'Property geoviewLayerName is mandatory for GeoView layer', - }; - - messageKey: string; - - messageVariables: string[]; - - constructor(messageKey: string, messageVariables: string[]) { - super(); - this.messageKey = messageKey; - this.messageVariables = messageVariables; - - // Override the message - const messageFragments = this.messageList[messageKey].split('<=>'); - const message = messageFragments.reduce((accumulator, stringToAppend, i) => { - return i < messageVariables.length ? `${accumulator}${stringToAppend}${messageVariables[i]}` : `${accumulator}${stringToAppend}`; - }, ''); - this.message = message; - - // Set the prototype explicitly (as recommended by TypeScript doc) - // https://github.com/microsoft/TypeScript-wiki/blob/81fe7b91664de43c02ea209492ec1cea7f3661d0/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work) - Object.setPrototypeOf(this, MapConfigError.prototype); - } -} - export class GeoviewLayerInvalidParameterError extends ConfigError { messageList: Record = { LayerIdNotFound: 'Cannot find layer id <=> in the metadata.', diff --git a/packages/geoview-core/src/api/config/types/classes/geoview-config/abstract-geoview-esri-layer-config.ts b/packages/geoview-core/src/api/config/types/classes/geoview-config/abstract-geoview-esri-layer-config.ts index 3fba2e70eb6..0ba5b0b7ad4 100644 --- a/packages/geoview-core/src/api/config/types/classes/geoview-config/abstract-geoview-esri-layer-config.ts +++ b/packages/geoview-core/src/api/config/types/classes/geoview-config/abstract-geoview-esri-layer-config.ts @@ -2,7 +2,7 @@ import { toJsonObject, TypeJsonObject, TypeJsonArray } from '@config/types/confi import { AbstractGeoviewLayerConfig } from '@config/types/classes/geoview-config/abstract-geoview-layer-config'; import { TypeDisplayLanguage, TypeStyleGeometry } from '@config/types/map-schema-types'; import { EsriGroupLayerConfig } from '@config/types/classes/sub-layer-config/group-node/esri-group-layer-config'; -import { layerEntryIsAbstractBaseLayerEntryConfig, layerEntryIsGroupLayer } from '@config/types/type-guards'; +import { layerEntryIsGroupLayer } from '@config/types/type-guards'; import { ConfigError, GeoviewLayerInvalidParameterError } from '@config/types/classes/config-exceptions'; import { EntryConfigBaseClass } from '@/api/config/types/classes/sub-layer-config/entry-config-base-class'; @@ -78,7 +78,7 @@ export abstract class AbstractGeoviewEsriLayerConfig extends AbstractGeoviewLaye // When a list of layer entries is specified, the layer tree is the same as the resulting listOfLayerEntryConfig of the geoview instance. // Otherwise, a layer tree is built using all the layers that compose the metadata. this.setMetadataLayerTree(this.listOfLayerEntryConfig.length ? this.listOfLayerEntryConfig : this.createLayerTree()); - await this.#fetchListOfLayerMetadata(this.listOfLayerEntryConfig); + await this.fetchListOfLayerMetadata(); } } else { this.setErrorDetectedFlag(); @@ -102,6 +102,7 @@ export abstract class AbstractGeoviewEsriLayerConfig extends AbstractGeoviewLaye #processListOfLayerEntryConfig(listOfLayerEntryConfig: EntryConfigBaseClass[]): EntryConfigBaseClass[] { return listOfLayerEntryConfig.map((subLayer) => { if (subLayer.getErrorDetectedFlag()) return subLayer; + if (layerEntryIsGroupLayer(subLayer)) { // The next line replace the listOfLayerEntryConfig stored in the subLayer parameter // Since subLayer is the function parameter, we must disable the eslint no-param-reassign @@ -109,6 +110,7 @@ export abstract class AbstractGeoviewEsriLayerConfig extends AbstractGeoviewLaye subLayer.listOfLayerEntryConfig = this.#processListOfLayerEntryConfig(subLayer.listOfLayerEntryConfig); return subLayer; } + try { return this.#createLayerEntryNode(parseInt(subLayer.layerId, 10), subLayer.getParentNode()); } catch (error) { @@ -120,11 +122,13 @@ export abstract class AbstractGeoviewEsriLayerConfig extends AbstractGeoviewLaye } /** - * Create a layer entry node for a specific layerId using the service metadata. + * Create a layer entry node for a specific layerId using the service metadata. The node returned can be a + * layer or a group layer. * * @param {number} layerId The layer id to use for the subLayer creation. + * @param {EntryConfigBaseClass | undefined} parentNode The layer's parent node. * - * @returns {EntryConfigBaseClass[]} The subLayer created from the metadata. + * @returns {EntryConfigBaseClass} The subLayer created from the metadata. * @private */ #createLayerEntryNode(layerId: number, parentNode: EntryConfigBaseClass | undefined): EntryConfigBaseClass { @@ -148,12 +152,12 @@ export abstract class AbstractGeoviewEsriLayerConfig extends AbstractGeoviewLaye } // Create the layer group from the array of layers - const jsonConfig = this.#createGroupNode(parseInt(layerFound.id as string, 10), layerFound?.name as string); + const jsonConfig = this.#createGroupNodeJsonConfig(parseInt(layerFound.id as string, 10), layerFound?.name as string); return this.createGroupNode(jsonConfig, this.getLanguage(), this, parentNode)!; } /** - * Create a group node for a specific layer id. + * Create a group node JSON configuration for a specific layer id. * * @param {number} parentId The layer id of the parent node. * @param {string} groupName The name to assign to the group node. @@ -161,11 +165,11 @@ export abstract class AbstractGeoviewEsriLayerConfig extends AbstractGeoviewLaye * @returns {TypeJsonObject} A json configuration that can be used to create the group node. * @private */ - #createGroupNode = (parentId: number, groupName: string): TypeJsonObject => { - const layers = this.getServiceMetadata().layers as TypeJsonObject[]; - const listOfLayerEntryConfig = layers.reduce((accumulator, layer) => { + #createGroupNodeJsonConfig = (parentId: number, groupName: string): TypeJsonObject => { + const layersArray = this.getServiceMetadata().layers as TypeJsonObject[]; + const listOfLayerEntryConfig = layersArray.reduce((accumulator, layer) => { if (layer.parentLayerId === parentId) { - if (layer.type === 'Group Layer') accumulator.push(this.#createGroupNode(layer.id as number, layer.name as string)); + if (layer.type === 'Group Layer') accumulator.push(this.#createGroupNodeJsonConfig(layer.id as number, layer.name as string)); else { accumulator.push( toJsonObject({ @@ -186,32 +190,9 @@ export abstract class AbstractGeoviewEsriLayerConfig extends AbstractGeoviewLaye listOfLayerEntryConfig, }); }; - - /** - * Fetch the metadata using the list of layer entry configuration provided. - * - * @param {EntryConfigBaseClass[]} listOfLayerEntryConfig The list of layer entry config to process. - * - * @returns {Promise} A promise that will resolve when the process has completed. - * @private - */ - async #fetchListOfLayerMetadata(listOfLayerEntryConfig: EntryConfigBaseClass[]): Promise { - const listOfLayerMetadata: Promise[] = []; - listOfLayerEntryConfig.forEach((subLayerConfig) => { - if (layerEntryIsGroupLayer(subLayerConfig)) { - listOfLayerMetadata.push(subLayerConfig.fetchLayerMetadata()); - listOfLayerMetadata.push(this.#fetchListOfLayerMetadata(subLayerConfig.listOfLayerEntryConfig)); - } else if (layerEntryIsAbstractBaseLayerEntryConfig(subLayerConfig)) { - listOfLayerMetadata.push(subLayerConfig.fetchLayerMetadata()); - } - }); - - await Promise.allSettled(listOfLayerMetadata); - logger.logDebug(listOfLayerEntryConfig); - } - // #endregion PRIVATE // #region PROTECTED + /** * Converts an esri geometry type string to a TypeStyleGeometry. * @param {string} esriGeometryType - The esri geometry type to convert @@ -246,7 +227,7 @@ export abstract class AbstractGeoviewEsriLayerConfig extends AbstractGeoviewLaye const layers = this.getServiceMetadata().layers as TypeJsonArray; if (layers.length > 1) { const groupName = this.getServiceMetadata().mapName as string; - return [new EsriGroupLayerConfig(this.#createGroupNode(-1, groupName), this.getLanguage(), this)]; + return [new EsriGroupLayerConfig(this.#createGroupNodeJsonConfig(-1, groupName), this.getLanguage(), this)]; } if (layers.length === 1) diff --git a/packages/geoview-core/src/api/config/types/classes/geoview-config/abstract-geoview-layer-config.ts b/packages/geoview-core/src/api/config/types/classes/geoview-config/abstract-geoview-layer-config.ts index 1fa0b078ef8..a489c19c52c 100644 --- a/packages/geoview-core/src/api/config/types/classes/geoview-config/abstract-geoview-layer-config.ts +++ b/packages/geoview-core/src/api/config/types/classes/geoview-config/abstract-geoview-layer-config.ts @@ -1,12 +1,12 @@ -import merge from 'lodash/merge'; +import mergeWith from 'lodash/mergeWith'; import cloneDeep from 'lodash/cloneDeep'; import { Cast, TypeJsonObject, TypeJsonArray } from '@config/types/config-types'; import { TypeGeoviewLayerType, TypeDisplayLanguage } from '@config/types/map-schema-types'; -import { isvalidComparedToInputSchema, normalizeLocalizedString } from '@config/utils'; -import { EsriGroupLayerConfig } from '@config/types/classes/sub-layer-config/group-node/esri-group-layer-config'; +import { isvalidComparedToInputSchema, isvalidComparedToInternalSchema, normalizeLocalizedString } from '@config/utils'; import { layerEntryIsGroupLayer } from '@config/types/type-guards'; -import { EntryConfigBaseClass } from '@/api/config/types/classes/sub-layer-config/entry-config-base-class'; +import { EntryConfigBaseClass } from '@config/types/classes/sub-layer-config/entry-config-base-class'; +import { GeoviewLayerConfigError } from '@config/types/classes/config-exceptions'; import { generateId } from '@/core/utils/utilities'; import { logger } from '@/core/utils/logger'; @@ -129,7 +129,7 @@ export abstract class AbstractGeoviewLayerConfig { // Instanciate the sublayer list. this.listOfLayerEntryConfig = (this.#userGeoviewLayerConfig.listOfLayerEntryConfig as TypeJsonArray) ?.map((subLayerConfig) => { - if (layerEntryIsGroupLayer(subLayerConfig)) return new EsriGroupLayerConfig(subLayerConfig, language, this); + if (layerEntryIsGroupLayer(subLayerConfig)) return this.createGroupNode(subLayerConfig, language, this); return this.createLeafNode(subLayerConfig, language, this); }) // When a sublayer cannot be created, the value returned is undefined. These values will be filtered. @@ -227,6 +227,29 @@ export abstract class AbstractGeoviewLayerConfig { protected getLanguage(): TypeDisplayLanguage { return this.#language; } + + /** + * Fetch the metadata of all layer entry configurations defined in the list of layer entry config. + * + * @returns {Promise} A promise that will resolve when the process has completed. + * @protected @async + */ + protected async fetchListOfLayerMetadata(): Promise { + // The root of the GeoView layer tree is an array that contains only one node. + const rootLayer = this.listOfLayerEntryConfig[0]; + + try { + if (rootLayer) { + // If an error has been detected, there is a problem with the metadata and the layer is unusable. + if (rootLayer.getErrorDetectedFlag()) return; + + await rootLayer.fetchLayerMetadata(); + } + } catch (error) { + logger.logError(`An error occured while reading the metadata for the layerPath ${rootLayer.getLayerPath()}.`, error); + rootLayer.setErrorDetectedFlag(); + } + } // #endregion PROTECTED // ============== @@ -347,10 +370,21 @@ export abstract class AbstractGeoviewLayerConfig { return sublayer; }); }; - this.listOfLayerEntryConfig = merge( - this.listOfLayerEntryConfig, - convertUserConfigToInternalConfig(geoviewLayerConfig.listOfLayerEntryConfig as TypeJsonArray) - ); + const internalConfig = convertUserConfigToInternalConfig(geoviewLayerConfig.listOfLayerEntryConfig as TypeJsonArray); + this.listOfLayerEntryConfig = mergeWith(this.listOfLayerEntryConfig, internalConfig, (target, newValue, key) => { + // Keep the listOfLayerEntryConfig as it is. Do not replace it with the user' array. Only the internal properties will be replaced. + // This is because the newValue is not an instance of EntryConfigBaseClass. The type of the newValue property is a plain JSON object. + if (key === 'listOfLayerEntryConfig') return undefined; + // Replace arrays with user config arrays if they exist. + if (Array.isArray(target) && Array.isArray(newValue)) return newValue; + return undefined; + }); + + if (!isvalidComparedToInternalSchema(this.getGeoviewLayerSchema(), this, true)) { + throw new GeoviewLayerConfigError( + `GeoView internal configuration ${this.geoviewLayerId} is invalid compared to the internal schema specification.` + ); + } } /** diff --git a/packages/geoview-core/src/api/config/types/classes/geoview-config/raster-config/esri-dynamic-config.ts b/packages/geoview-core/src/api/config/types/classes/geoview-config/raster-config/esri-dynamic-config.ts index 95584395eba..4cf72dcc068 100644 --- a/packages/geoview-core/src/api/config/types/classes/geoview-config/raster-config/esri-dynamic-config.ts +++ b/packages/geoview-core/src/api/config/types/classes/geoview-config/raster-config/esri-dynamic-config.ts @@ -34,11 +34,11 @@ export class EsriDynamicLayerConfig extends AbstractGeoviewEsriLayerConfig { // #region OVERRIDE /** - * @protected @override * The getter method that returns the geoview layer schema to use for the validation. Each geoview layer type knows what * section of the schema must be used to do its validation. * * @returns {string} The GeoView layer schema associated to the config. + * @protected @override */ protected override getGeoviewLayerSchema(): string { /** The GeoView layer schema associated to EsriDynamicLayerConfig */ @@ -46,7 +46,6 @@ export class EsriDynamicLayerConfig extends AbstractGeoviewEsriLayerConfig { } /** - * @override * The method used to implement the class factory model that returns the instance of the class based on the sublayer * type needed. * @@ -56,6 +55,7 @@ export class EsriDynamicLayerConfig extends AbstractGeoviewEsriLayerConfig { * @param {EntryConfigBaseClass} parentNode The The parent node that owns this layer or undefined if it is the root layer.. * * @returns {EntryConfigBaseClass} The sublayer instance or undefined if there is an error. + * @override */ override createLeafNode( layerConfig: TypeJsonObject, @@ -67,7 +67,6 @@ export class EsriDynamicLayerConfig extends AbstractGeoviewEsriLayerConfig { } /** - * @override * The method used to implement the class factory model that returns the instance of the class based on the group * type needed. * @@ -77,6 +76,7 @@ export class EsriDynamicLayerConfig extends AbstractGeoviewEsriLayerConfig { * @param {EntryConfigBaseClass} parentNode The The parent node that owns this layer or undefined if it is the root layer.. * * @returns {EntryConfigBaseClass} The sublayer instance or undefined if there is an error. + * @override */ override createGroupNode( layerConfig: TypeJsonObject, diff --git a/packages/geoview-core/src/api/config/types/classes/geoview-config/raster-config/wms-config.ts b/packages/geoview-core/src/api/config/types/classes/geoview-config/raster-config/wms-config.ts new file mode 100644 index 00000000000..87931450940 --- /dev/null +++ b/packages/geoview-core/src/api/config/types/classes/geoview-config/raster-config/wms-config.ts @@ -0,0 +1,592 @@ +import WMSCapabilities from 'ol/format/WMSCapabilities'; + +import { CV_CONST_LAYER_TYPES, CV_GEOVIEW_SCHEMA_PATH } from '@config/types/config-constants'; +import { AbstractGeoviewLayerConfig } from '@config/types/classes/geoview-config/abstract-geoview-layer-config'; +import { WmsGroupLayerConfig } from '@config/types/classes/sub-layer-config/group-node/wms-group-layer-config'; +import { toJsonObject, TypeJsonArray, TypeJsonObject } from '@config/types/config-types'; +import { TypeDisplayLanguage } from '@config/types/map-schema-types'; +import { WmsLayerEntryConfig } from '@config/types/classes/sub-layer-config/leaf/raster/wms-layer-entry-config'; +import { EntryConfigBaseClass } from '@config/types/classes/sub-layer-config/entry-config-base-class'; +import { layerEntryIsGroupLayer } from '@config/types/type-guards'; +import { ConfigError, GeoviewLayerConfigError, GeoviewLayerInvalidParameterError } from '@config/types/classes/config-exceptions'; + +import { logger } from '@/core/utils/logger'; +import { xmlToJson } from '@/core/utils/utilities'; + +export type TypeWmsLayerNode = WmsGroupLayerConfig | WmsLayerEntryConfig; + +// ======================== +// #region CLASS DEFINITION + +/** + * The WMS geoview layer class. + */ +export class WmsLayerConfig extends AbstractGeoviewLayerConfig { + // ================== + // #region PROPERTIES + + /** + * Type of GeoView layer. + */ + override geoviewLayerType = CV_CONST_LAYER_TYPES.WMS; + + /** The layer entries to use from the GeoView layer. */ + declare listOfLayerEntryConfig: TypeWmsLayerNode[]; + + // #endregion PROPERTIES + // =================== + // #region CONSTRUCTOR + /** + * The class constructor. + * + * @param {TypeJsonObject} geoviewLayerConfig The layer configuration we want to instanciate. + * @param {TypeDisplayLanguage} language The initial language to use when interacting with the map feature configuration. + */ + constructor(geoviewLayerConfig: TypeJsonObject, language: TypeDisplayLanguage) { + super(geoviewLayerConfig, language); + const metadataAccessPathItems = this.metadataAccessPath.split(/\?Layers=/i); + const lastPathItem = metadataAccessPathItems[1]; + if (lastPathItem) { + // The metadataAccessPath ends with a layer parameter. It is therefore a path to a data layer rather than a path to service metadata. + // We therefore need to correct the configuration by separating the layer parameter and the path to the service metadata. + [this.metadataAccessPath] = metadataAccessPathItems; + if (this.listOfLayerEntryConfig.length) { + this.setErrorDetectedFlag(); + logger.logError('When an WMS metadataAccessPath ends with a layerId, the listOfLayerEntryConfig must be empty.'); + } + this.listOfLayerEntryConfig = [this.createLeafNode(toJsonObject({ layerId: lastPathItem }), language, this)! as TypeWmsLayerNode]; + } + } + // #endregion CONSTRUCTOR + + // =============== + // #region METHODS + /* + * Methods are listed in the following order: abstract, override, private, protected, public and static. + */ + + // ================ + // #region OVERRIDE + /** + * The getter method that returns the geoview layer schema to use for the validation. Each geoview layer type knows what + * section of the schema must be used to do its validation. + * + * @returns {string} The GeoView layer schema associated to the config. + * @protected @override + */ + protected override getGeoviewLayerSchema(): string { + /** The GeoView layer schema associated to WmsLayerConfig */ + return CV_GEOVIEW_SCHEMA_PATH.WMS; + } + + /** + * The method used to implement the class factory model that returns the instance of the class based on the sublayer + * type needed. + * + * @param {TypeJsonObject} layerConfig The sublayer configuration. + * @param {TypeDisplayLanguage} language The initial language to use when interacting with the geoview layer. + * @param {AbstractGeoviewLayerConfig} geoviewConfig The GeoView instance that owns the sublayer. + * @param {EntryConfigBaseClass} parentNode The The parent node that owns this layer or undefined if it is the root layer. + * + * @returns {EntryConfigBaseClass} The sublayer instance or undefined if there is an error. + * @override + */ + override createLeafNode( + layerConfig: TypeJsonObject, + language: TypeDisplayLanguage, + geoviewConfig: AbstractGeoviewLayerConfig, + parentNode?: EntryConfigBaseClass + ): EntryConfigBaseClass { + return new WmsLayerEntryConfig(layerConfig, language, geoviewConfig, parentNode); + } + + /** + * The method used to implement the class factory model that returns the instance of the class based on the group + * type needed. + * + * @param {TypeJsonObject} layerConfig The group node configuration. + * @param {TypeDisplayLanguage} language The initial language to use when interacting with the geoview layer. + * @param {AbstractGeoviewLayerConfig} geoviewConfig The GeoView instance that owns the sublayer. + * @param {EntryConfigBaseClass} parentNode The The parent node that owns this layer or undefined if it is the root layer. + * + * @returns {EntryConfigBaseClass} The sublayer instance or undefined if there is an error. + * @override + */ + override createGroupNode( + layerConfig: TypeJsonObject, + language: TypeDisplayLanguage, + geoviewConfig: AbstractGeoviewLayerConfig, + parentNode?: EntryConfigBaseClass + ): EntryConfigBaseClass { + return new WmsGroupLayerConfig(layerConfig, language, geoviewConfig, parentNode); + } + + /** + * Get the service metadata from the metadataAccessPath and store it in the private property of the geoview layer. + * @override @async + */ + override async fetchServiceMetadata(): Promise { + const metadataAccessPathIsXmlFile = this.metadataAccessPath.slice(-4).toLowerCase() === '.xml'; + if (metadataAccessPathIsXmlFile) { + // XML metadata is a special case that does not use GetCapabilities to get the metadata + await this.#fetchXmlServiceMetadata(this.metadataAccessPath); + } else { + const layerConfigsToQuery = this.#getLayersToQuery(this.listOfLayerEntryConfig); + if (layerConfigsToQuery.length === 0) { + // Use 'request=GetCapabilities' to get the service metadata + await this.#fetchUsingGetCapabilities(); + } else { + // Uses 'request=GetCapabilities&Layers=' to get the service metadata. + await this.#fetchUsingGetCapabilitiesAndLayers(layerConfigsToQuery); + } + } + + (this.listOfLayerEntryConfig as EntryConfigBaseClass[]) = this.#processListOfLayerEntryConfig(this.listOfLayerEntryConfig); + + // When a list of layer entries is specified, the layer tree is the same as the resulting listOfLayerEntryConfig of the geoview instance. + // Otherwise, a layer tree is built using all the layers that compose the metadata. + this.setMetadataLayerTree(this.listOfLayerEntryConfig.length ? this.listOfLayerEntryConfig : this.createLayerTree()); + await this.fetchListOfLayerMetadata(); + } + // #endregion OVERRIDE + // =============== + // #region PRIVATE + + /** + * A recursive method to process the listOfLayerEntryConfig. The goal is to process each valid sublayer, searching the service's + * metadata to verify the layer's existence and whether it is a layer group, in order to determine the node's final structure. + * If it is a layer group, it will be created. + * + * @param {TypeWmsLayerNode[]} listOfLayerEntryConfig the list of sublayers to process. + * + * @returns {TypeWmsLayerNode[]} the new list of sublayer configurations. + * @private + */ + #processListOfLayerEntryConfig(listOfLayerEntryConfig: TypeWmsLayerNode[]): EntryConfigBaseClass[] { + return listOfLayerEntryConfig.map((subLayer) => { + if (subLayer.getErrorDetectedFlag()) return subLayer; + + if (layerEntryIsGroupLayer(subLayer)) { + // The next line replace the listOfLayerEntryConfig stored in the subLayer parameter + // Since subLayer is the function parameter, we must disable the eslint no-param-reassign + // eslint-disable-next-line no-param-reassign + subLayer.listOfLayerEntryConfig = this.#processListOfLayerEntryConfig(subLayer.listOfLayerEntryConfig as TypeWmsLayerNode[]); + return subLayer; + } + + try { + return this.#createLayerEntryNode(subLayer.layerId, subLayer.getParentNode()); + } catch (error) { + subLayer.setErrorDetectedFlag(); + logger.logError((error as ConfigError).message, error); + return subLayer; + } + }); + } + + /** + * Create a layer entry node for a specific layerId using the service metadata. The node returned can be a + * layer or a group layer. + * + * @param {string} layerId The layer id to use for the subLayer creation. + * @param {TypeWmsLayerNode | undefined} parentNode The layer's parent node. + * + * @returns {TypeWmsLayerNode} The subLayer created from the metadata. + * @private + */ + #createLayerEntryNode(layerId: string, parentNode: EntryConfigBaseClass | undefined): EntryConfigBaseClass { + // If we cannot find the layerId in the layer definitions, throw an error. + const layerFound = WmsLayerConfig.getLayerMetadataEntry(layerId, this.getServiceMetadata().Capability.Layer); + if (!layerFound) { + throw new GeoviewLayerInvalidParameterError('LayerIdNotFound', [layerId?.toString()]); + } + + // if the layerFound is a group layer, create a the layer group. + if ('Layer' in layerFound) { + const jsonConfig = this.#createGroupNodeJsonConfig(layerId, layerFound.Layer as TypeJsonObject[]); + return this.createGroupNode(jsonConfig, this.getLanguage(), this, parentNode)!; + } + + // Create the layer using the metadata + const layerConfig = toJsonObject({ + layerId, + layerName: { en: layerFound.Title, fr: layerFound.Title }, + }); + return this.createLeafNode(layerConfig, this.getLanguage(), this, parentNode)!; + } + + /** + * Create a group node JSON config for a specific layer id. + * + * @param {string} groupId The layer group id of the node. + * @param {TypeJsonObject[]} metadataLayerArray The metadata array of layer to assign to the group node. + * + * @returns {TypeJsonObject} A json configuration that can be used to create the group node. + * @private + */ + #createGroupNodeJsonConfig = (groupId: string, metadataLayerArray: TypeJsonObject[]): TypeJsonObject => { + const listOfLayerEntryConfig = metadataLayerArray.reduce((accumulator, layer) => { + if ('Layer' in layer) accumulator.push(this.#createGroupNodeJsonConfig(layer.Name as string, layer.Layer as TypeJsonObject[])); + else { + accumulator.push( + toJsonObject({ + layerId: layer.Name, + layerName: { en: layer.Name, fr: layer.Name }, + }) + ); + } + return accumulator; + }, [] as TypeJsonObject[]); + + return toJsonObject({ + layerId: groupId, + layerName: { en: groupId, fr: groupId }, + isLayerGroup: true, + listOfLayerEntryConfig, + }); + }; + + /** *************************************************************************************************************************** + * This method reads the service metadata from a XML metadataAccessPath. + * + * @param {string} metadataUrl The localized value of the metadataAccessPath + * + * @returns {Promise} A promise that the execution is completed. + * @private + */ + async #fetchXmlServiceMetadata(metadataUrl: string): Promise { + try { + const parser = new WMSCapabilities(); + const response = await fetch(metadataUrl); + const capabilitiesString = await response.text(); + this.setServiceMetadata(parser.read(capabilitiesString)); + if (this.getServiceMetadata()) { + this.#processMetadataInheritance(); + this.metadataAccessPath = this.getServiceMetadata().Capability.Request.GetMap.DCPType[0].HTTP.Get.OnlineResource as string; + } else throw new GeoviewLayerConfigError('Unable to read the metadata, value returned is empty.'); + } catch (error) { + // In the event of a service metadata reading error, we report the geoview layer and all its sublayers as being in error. + this.setErrorDetectedFlag(); + this.setErrorDetectedFlagForAllLayers(this.listOfLayerEntryConfig); + logger.logError(`Error detected while reading WMS metadata for geoview layer ${this.geoviewLayerId}.`); + } + } + + /** *************************************************************************************************************************** + * This method converts the tree structure of the listOfLayerEntreyConfig to a one dimensionnal array of layers + * that will be used in the GetCapabilities. + * + * @param {TypeWmsLayerNode[]} The list of layer entry config. + * + * @returns {WmsLayerEntryConfig[]} The array of layer configurations. + * @private + */ + #getLayersToQuery(listOfLayerEntryConfig: TypeWmsLayerNode[]): WmsLayerEntryConfig[] { + const returnValue = listOfLayerEntryConfig.reduce((accumulator, layerConfig) => { + if (layerEntryIsGroupLayer(layerConfig)) + // eslint-disable-next-line no-param-reassign + accumulator = accumulator.concat(this.#getLayersToQuery(layerConfig.listOfLayerEntryConfig as TypeWmsLayerNode[])); + else accumulator.push(layerConfig); + return accumulator; + }, [] as WmsLayerEntryConfig[]); + return returnValue; + } + + /** *************************************************************************************************************************** + * This method fetch the service metadata using a getCapabilities request. + * @private + */ + async #fetchUsingGetCapabilities(): Promise { + try { + const serviceMetadata = await WmsLayerConfig.#executeServiceMetadataRequest( + `${this.metadataAccessPath}?service=WMS&version=1.3.0&request=GetCapabilities` + ); + this.setServiceMetadata(serviceMetadata); + this.#processMetadataInheritance(); + } catch (error) { + // In the event of a service metadata reading error, we report the geoview layer and all its sublayers as being in error. + this.setErrorDetectedFlag(); + this.setErrorDetectedFlagForAllLayers(this.listOfLayerEntryConfig); + logger.logError(`Error detected while reading WMS metadata for geoview layer ${this.geoviewLayerId}.`); + } + } + + /** *************************************************************************************************************************** + * This method reads service metadata using the URL provided. The syntax can be “request=GetCapabilities” or + * “request=GetCapabilities&Layers=layerId”. Callers are responsible for handling any errors that occur here. + * + * @param {string} url The GetCapabilities request to execute + * + * @returns {Promise} A promise that the execution is completed. + * @private @async + */ + static async #executeServiceMetadataRequest(url: string): Promise { + const response = await fetch(url); + const capabilitiesString = await response.text(); + + const xmlDomResponse = new DOMParser().parseFromString(capabilitiesString, 'text/xml'); + const errorObject = xmlToJson(xmlDomResponse)?.['ogc:ServiceExceptionReport']?.['ogc:ServiceException']; + if (errorObject) throw new GeoviewLayerConfigError(errorObject['#text'] as string); + + const parser = new WMSCapabilities(); + const serviceMetadata: TypeJsonObject = parser.read(capabilitiesString); + if (serviceMetadata?.Capability?.Layer) return serviceMetadata; + throw new GeoviewLayerConfigError(`Unable to read the metadata, value returned doesn't contain Capability, Layer or is empty.`); + } + + /** *************************************************************************************************************************** + * This method fetch the service metadata using a getCapabilities&Layers= request. To allow geomet service metadata to be + * retrieved using the "Layers" parameter in the request URL, we need to process each layer individually and merge all layer + * metadata at the end. Even though the "Layers" parameter is ignored by other WMS servers, the drawback of this method is + * sending unnecessary requests while only one GetCapabilities could be used when the server publishes a small set of metadata. + * Which is not the case for the Geomet service. + * + * @param {WmsLayerEntryConfig} layerConfigsToQuery The array of layers to process. + * @private + */ + async #fetchUsingGetCapabilitiesAndLayers(layerConfigsToQuery: WmsLayerEntryConfig[]): Promise { + try { + const promisedArrayOfMetadata: Promise[] = []; + let i: number; + layerConfigsToQuery.forEach((layerConfig: WmsLayerEntryConfig, layerIndex: number) => { + // verify if the a request with the same layerId has already been sent up to now. + for (i = 0; layerConfigsToQuery[i].layerId !== layerConfig.layerId; i++); + if (i === layerIndex) + // if the layer found is the same as the current layer index, + // this is the first time we execute this request + promisedArrayOfMetadata.push( + WmsLayerConfig.#executeServiceMetadataRequest( + `${this.metadataAccessPath}?service=WMS&version=1.3.0&request=GetCapabilities&Layers=${layerConfig.layerId}` + ) + ); + // otherwise, we are already waiting for the same request and we will wait for it to finish. + else promisedArrayOfMetadata.push(promisedArrayOfMetadata[i]); + }); + + // Since we use Promise.all, If one of the Promise awaited fails, then the whole service metadata fetching will fail. + const arrayOfMetadata = await Promise.all(promisedArrayOfMetadata); + + // Initialize service metadata using index 0 of arrayOfMetadata as a starting point. Other indexes will be added to it. + this.setServiceMetadata(arrayOfMetadata[0]); + for (i = 1; i < arrayOfMetadata.length; i++) { + if (!WmsLayerConfig.getLayerMetadataEntry(layerConfigsToQuery[i].layerId, this.getServiceMetadata().Capability.Layer)) { + const metadataLayerPathToAdd = this.#getMetadataLayerPath(layerConfigsToQuery[i].layerId!, arrayOfMetadata[i]!.Capability.Layer); + this.#addLayerToMetadataInstance( + metadataLayerPathToAdd, + this.getServiceMetadata().Capability.Layer, + arrayOfMetadata[i]!.Capability.Layer + ); + } + } + this.#processMetadataInheritance(); + } catch (error) { + // In the event of a service metadata reading error, we report the geoview layer and all its sublayers as being in error. + this.setErrorDetectedFlag(); + this.setErrorDetectedFlagForAllLayers(this.listOfLayerEntryConfig); + logger.logError( + `Error detected while reading WMS metadata for geoview layer ${this.geoviewLayerId}.\n${ + (error as GeoviewLayerConfigError).message || '' + }`, + error + ); + } + } + + /** *************************************************************************************************************************** + * This method find the layer path that lead to the layer identified by the layerName. Values stored in the array tell us which + * direction to use to get to the layer. A value of -1 tells us that the Layer property is an object. Other values tell us that + * the Layer property is an array and the value is the index to follow. If the layer can not be found, the returned value is + * an empty array. + * + * @param {string} layerName The layer name to be found + * @param {TypeJsonObject} layerProperty The layer property from the metadata + * @param {number[]} pathToTheLayerProperty The path leading to the parent of the layerProperty parameter + * + * @returns {number[]} An array containing the path to the layer or [] if not found. + * @private + */ + #getMetadataLayerPath(layerName: string, layerProperty: TypeJsonObject, pathToTheParentLayer: number[] = []): number[] { + const newLayerPath = [...pathToTheParentLayer]; + if (Array.isArray(layerProperty)) { + for (let i = 0; i < layerProperty.length; i++) { + newLayerPath.push(i); + if ('Name' in layerProperty[i] && layerProperty[i].Name === layerName) return newLayerPath; + if ('Layer' in layerProperty[i]) { + return this.#getMetadataLayerPath(layerName, layerProperty[i].Layer, newLayerPath); + } + } + } else { + newLayerPath.push(-1); + if ('Name' in layerProperty && layerProperty.Name === layerName) return newLayerPath; + if ('Layer' in layerProperty) { + return this.#getMetadataLayerPath(layerName, layerProperty.Layer, newLayerPath); + } + } + return []; + } + + /** *************************************************************************************************************************** + * This method merge the layer identified by the path stored in the metadataLayerPathToAdd array to the metadata property of + * the WMS instance. Values stored in the path array tell us which direction to use to get to the layer. A value of -1 tells us + * that the Layer property is an object. In this case, it is assumed that the metadata objects at this level only differ by the + * layer property to add. Other values tell us that the Layer property is an array and the value is the index to follow. If at + * this level in the path the layers have the same name, we move to the next level. Otherwise, the layer can be added. + * + * @param {number[]} metadataLayerPathToAdd The layer name to be found + * @param {TypeJsonObject | undefined} metadataLayer The metadata layer that will receive the new layer + * @param {TypeJsonObject} layerToAdd The layer property to add + * @private + */ + #addLayerToMetadataInstance( + metadataLayerPathToAdd: number[], + metadataLayer: TypeJsonObject | undefined, + layerToAdd: TypeJsonObject + ): void { + if (metadataLayerPathToAdd.length === 0 || !metadataLayer) return; + if (metadataLayerPathToAdd[0] === -1) + this.#addLayerToMetadataInstance(metadataLayerPathToAdd.slice(1), metadataLayer.Layer, layerToAdd.Layer); + else { + const metadataLayerFound = (metadataLayer as TypeJsonArray).find( + (layerEntry) => layerEntry.Name === layerToAdd[metadataLayerPathToAdd[0]].Name + ); + if (metadataLayerFound) + this.#addLayerToMetadataInstance( + metadataLayerPathToAdd.slice(1), + metadataLayerFound.Layer, + layerToAdd[metadataLayerPathToAdd[0]].Layer + ); + else (metadataLayer as TypeJsonArray).push(layerToAdd[metadataLayerPathToAdd[0]]); + } + } + + /** *************************************************************************************************************************** + * This method propagate the WMS metadata inherited values. + * + * @param {TypeJsonObject} parentLayer The parent layer that contains the inherited values + * @param {TypeJsonObject | undefined} layer The layer property from the metadata that will inherit the values + * @private + */ + #processMetadataInheritance( + parentLayer?: TypeJsonObject, + layer: TypeJsonObject | undefined = this.getServiceMetadata().Capability.Layer + ): void { + if (parentLayer && layer) { + // Table 7 — Inheritance of Layer properties specified in the standard with 'replace' behaviour. + // eslint-disable-next-line no-param-reassign + if (layer.EX_GeographicBoundingBox === undefined) layer.EX_GeographicBoundingBox = parentLayer.EX_GeographicBoundingBox; + // eslint-disable-next-line no-param-reassign + if (layer.queryable === undefined) layer.queryable = parentLayer.queryable; + // eslint-disable-next-line no-param-reassign + if (layer.cascaded === undefined) layer.cascaded = parentLayer.cascaded; + // eslint-disable-next-line no-param-reassign + if (layer.opaque === undefined) layer.opaque = parentLayer.opaque; + // eslint-disable-next-line no-param-reassign + if (layer.noSubsets === undefined) layer.noSubsets = parentLayer.noSubsets; + // eslint-disable-next-line no-param-reassign + if (layer.fixedWidth === undefined) layer.fixedWidth = parentLayer.fixedWidth; + // eslint-disable-next-line no-param-reassign + if (layer.fixedHeight === undefined) layer.fixedHeight = parentLayer.fixedHeight; + // eslint-disable-next-line no-param-reassign + if (layer.MinScaleDenominator === undefined) layer.MinScaleDenominator = parentLayer.MinScaleDenominator; + // eslint-disable-next-line no-param-reassign + if (layer.MaxScaleDenominator === undefined) layer.MaxScaleDenominator = parentLayer.MaxScaleDenominator; + // eslint-disable-next-line no-param-reassign + if (layer.BoundingBox === undefined) layer.BoundingBox = parentLayer.BoundingBox; + // eslint-disable-next-line no-param-reassign + if (layer.Dimension === undefined) layer.Dimension = parentLayer.Dimension; + // eslint-disable-next-line no-param-reassign + if (layer.Attribution === undefined) layer.Attribution = parentLayer.Attribution; + // eslint-disable-next-line no-param-reassign + if (layer.MaxScaleDenominator === undefined) layer.MaxScaleDenominator = parentLayer.MaxScaleDenominator; + // eslint-disable-next-line no-param-reassign + if (layer.MaxScaleDenominator === undefined) layer.MaxScaleDenominator = parentLayer.MaxScaleDenominator; + // Table 7 — Inheritance of Layer properties specified in the standard with 'add' behaviour. + // AuthorityURL inheritance is not implemented in the following code. + if (parentLayer.Style) { + // eslint-disable-next-line no-param-reassign + if (!layer.Style as TypeJsonArray) (layer.Style as TypeJsonArray) = []; + (parentLayer.Style as TypeJsonArray).forEach((parentStyle) => { + const styleFound = (layer.Style as TypeJsonArray).find((styleEntry) => styleEntry.Name === parentStyle.Name); + if (!styleFound) (layer.Style as TypeJsonArray).push(parentStyle); + }); + } + if (parentLayer.CRS) { + // eslint-disable-next-line no-param-reassign + if (!layer.CRS as TypeJsonArray) (layer.CRS as TypeJsonArray) = []; + (parentLayer.CRS as TypeJsonArray).forEach((parentCRS) => { + const crsFound = (layer.CRS as TypeJsonArray).find((crsEntry) => crsEntry.Name === parentCRS); + if (!crsFound) (layer.CRS as TypeJsonArray).push(parentCRS); + }); + } + } + if (layer?.Layer !== undefined) (layer.Layer as TypeJsonArray).forEach((subLayer) => this.#processMetadataInheritance(layer, subLayer)); + } + + // #endregion PRIVATE + // ================= + // #region PROTECTED + /** + * Create the layer tree using the service metadata. + * + * @returns {TypeJsonObject[]} The layer tree created from the metadata. + * @protected + */ + protected createLayerTree(): EntryConfigBaseClass[] { + const metadataLayer = this.getServiceMetadata().Capability.Layer; + // If it is a group layer, then create it. + if ('Layer' in metadataLayer) { + // Sometime, the Name property is undefined. However, the Title property is mandatory. + const groupId = (metadataLayer.Name || metadataLayer.Title) as string; + const jsonConfig = this.#createGroupNodeJsonConfig(groupId, metadataLayer.Layer as TypeJsonArray); + return [this.createGroupNode(jsonConfig, this.getLanguage(), this)!]; + } + + // Create a single layer using the metadata + const layerConfig = toJsonObject({ + layerId: metadataLayer.Name, + layerName: { en: metadataLayer.Name, fr: metadataLayer.Name }, + }); + return [this.createLeafNode(layerConfig, this.getLanguage(), this)!]; + } + + // #endregion PROTECTED + // ================= + // #region STATIC + /** **************************************************************************************************************************** + * This method search recursively the layerId in the layer entry of the capabilities. + * + * @param {string} layerId The layer identifier that must exists on the server. + * @param {TypeJsonObject | undefined} layer The layer entry from the capabilities that will be searched. + * + * @returns {TypeJsonObject | null} The found layer from the capabilities or null if not found. + * @static + */ + static getLayerMetadataEntry(layerId: string, layer: TypeJsonObject | undefined): TypeJsonObject | null { + // return null if the metadata doesn't have a Layer property in Capability (metadata fetch failed). + if (!layer) return null; + + // if we have a named layer with the right name, return the layer found. + if ('Name' in layer && (layer.Name as string) === layerId) return layer; + + if ('Layer' in layer) { + // if we have a layer group, search recursively in it. + if (Array.isArray(layer.Layer)) { + for (let i = 0; i < layer.Layer.length; i++) { + const layerFound = WmsLayerConfig.getLayerMetadataEntry(layerId, layer.Layer[i]); + // return the layer if it has been found. + if (layerFound) return layerFound; + } + // the recursive search failed, we return a null. + return null; + } + // If the Layer property is not an array, use a single call to the getLayerMetadataEntryto test the layer's existance. + return WmsLayerConfig.getLayerMetadataEntry(layerId, layer.Layer); + } + + // If we get here, the layer doesn't exist, we return a null. + return null; + } + // #endregion PROTECTED + // #endregion METHODS + // #endregion CLASS DEFINITION +} diff --git a/packages/geoview-core/src/api/config/types/classes/map-feature-config.ts b/packages/geoview-core/src/api/config/types/classes/map-feature-config.ts index e0143a7da57..58ee496d0bf 100644 --- a/packages/geoview-core/src/api/config/types/classes/map-feature-config.ts +++ b/packages/geoview-core/src/api/config/types/classes/map-feature-config.ts @@ -2,9 +2,10 @@ import cloneDeep from 'lodash/cloneDeep'; import defaultsDeep from 'lodash/defaultsDeep'; import { AbstractGeoviewLayerConfig } from '@config/types/classes/geoview-config/abstract-geoview-layer-config'; -import { EsriDynamicLayerConfig } from '@config/types/classes/geoview-config/raster-config/esri-dynamic-config'; import { Cast, toJsonObject, TypeJsonArray, TypeJsonObject } from '@config/types/config-types'; +import { EsriDynamicLayerConfig } from '@config/types/classes/geoview-config/raster-config/esri-dynamic-config'; import { EsriFeatureLayerConfig } from '@config/types/classes/geoview-config/vector-config/esri-feature-config'; +import { WmsLayerConfig } from '@config/types/classes/geoview-config/raster-config/wms-config'; import { CV_BASEMAP_ID, CV_BASEMAP_LABEL, @@ -458,8 +459,8 @@ export class MapFeatureConfig { // return new OgcFeatureLayerConfig(layerConfig); // case CONST_LAYER_TYPES.WFS: // return new WfsLayerConfig(layerConfig); - // case CONST_LAYER_TYPES.WMS: - // return new WmsLayerConfig(layerConfig); + case CV_CONST_LAYER_TYPES.WMS: + return new WmsLayerConfig(layerConfig, language); default: // TODO: Restore the commented line and remove the next line when we have converted our code to the new framework. // logger.logError(`Invalid GeoView layerType (${layerConfig.geoviewLayerType}).`); diff --git a/packages/geoview-core/src/api/config/types/classes/sub-layer-config/group-node/esri-group-layer-config.ts b/packages/geoview-core/src/api/config/types/classes/sub-layer-config/group-node/esri-group-layer-config.ts index 151e29245e0..75114f42c79 100644 --- a/packages/geoview-core/src/api/config/types/classes/sub-layer-config/group-node/esri-group-layer-config.ts +++ b/packages/geoview-core/src/api/config/types/classes/sub-layer-config/group-node/esri-group-layer-config.ts @@ -3,6 +3,8 @@ import axios from 'axios'; import { TypeJsonObject } from '@config/types/config-types'; import { GroupLayerEntryConfig } from '@config/types/classes/sub-layer-config/group-node/group-layer-entry-config'; import { Extent } from '@config/types/map-schema-types'; +import { GeoviewLayerConfigError } from '@config/types/classes/config-exceptions'; +import { isvalidComparedToInternalSchema } from '@config/utils'; import { logger } from '@/core/utils/logger'; import { Projection } from '@/geo/utils/projection'; @@ -45,27 +47,34 @@ export class EsriGroupLayerConfig extends GroupLayerEntryConfig { this.setLayerMetadata(serviceMetadata); // parse the raw service metadata and build the geoview configuration. this.#parseServiceMetadata(); - return; + } else { + // The layer exists and we can fetch its metadata and parse it. + const serviceUrl = serviceMetadata.metadataAccessPath as string; + const queryUrl = serviceUrl.endsWith('/') ? `${serviceUrl}${this.layerId}` : `${serviceUrl}/${this.layerId}`; + + try { + const { data } = await axios.get(`${queryUrl}?f=json`); + if ('error' in data) logger.logError('Error detected while reading layer metadata.', data.error); + else { + // The metadata used are the layer metadata. + this.setLayerMetadata(data); + // Parse the raw layer metadata and build the geoview configuration. + this.#parseLayerMetadata(); + return; + } + } catch (error) { + logger.logError('Error detected while reading Layer metadata.', error); + this.setErrorDetectedFlag(); + } } - // The layer exists and we can fetch its metadata and parse it. - const serviceUrl = serviceMetadata.metadataAccessPath as string; - const queryUrl = serviceUrl.endsWith('/') ? `${serviceUrl}${this.layerId}` : `${serviceUrl}/${this.layerId}`; - - try { - const { data } = await axios.get(`${queryUrl}?f=json`); - if ('error' in data) logger.logError('Error detected while reading layer metadata.', data.error); - else { - // The metadata used are the layer metadata. - this.setLayerMetadata(data); - // Parse the raw layer metadata and build the geoview configuration. - this.#parseLayerMetadata(); - return; - } - } catch (error) { - logger.logError('Error detected while reading Layer metadata.', error); + await this.fetchListOfLayerMetadata(); + + if (!isvalidComparedToInternalSchema(this.getSchemaPath(), this, true)) { + throw new GeoviewLayerConfigError( + `GeoView internal configuration ${this.getLayerPath()} is invalid compared to the internal schema specification.` + ); } - this.setErrorDetectedFlag(); } // #endregion OVERRIDE diff --git a/packages/geoview-core/src/api/config/types/classes/sub-layer-config/group-node/group-layer-entry-config.ts b/packages/geoview-core/src/api/config/types/classes/sub-layer-config/group-node/group-layer-entry-config.ts index 279d4664927..6005421f7d7 100644 --- a/packages/geoview-core/src/api/config/types/classes/sub-layer-config/group-node/group-layer-entry-config.ts +++ b/packages/geoview-core/src/api/config/types/classes/sub-layer-config/group-node/group-layer-entry-config.ts @@ -79,6 +79,26 @@ export abstract class GroupLayerEntryConfig extends EntryConfigBaseClass { } // #endregion OVERRIDE + // #region PROTECTED + /** + * Fetch the metadata of all layer entry configurations defined in the layer tree. + * + * @returns {Promise} A promise that will resolve when the process has completed. + * @protected @async + */ + protected async fetchListOfLayerMetadata(): Promise { + const arrayOfLayerPromises: Promise[] = []; + this.listOfLayerEntryConfig.forEach((subLayerConfig) => { + arrayOfLayerPromises.push(subLayerConfig.fetchLayerMetadata()); + }); + + const awaitedPromises = await Promise.allSettled(arrayOfLayerPromises); + awaitedPromises.forEach((promise, i) => { + if (promise.status === 'rejected') this.listOfLayerEntryConfig[i].setErrorDetectedFlag(); + }); + } + // #endregion PROTECTED + // #region PUBLIC /** * Scan the list of sublayers for duplicates. If duplicates exist, mark them as an error layer. diff --git a/packages/geoview-core/src/api/config/types/classes/sub-layer-config/group-node/wms-group-layer-config.ts b/packages/geoview-core/src/api/config/types/classes/sub-layer-config/group-node/wms-group-layer-config.ts new file mode 100644 index 00000000000..c9b97ef88f4 --- /dev/null +++ b/packages/geoview-core/src/api/config/types/classes/sub-layer-config/group-node/wms-group-layer-config.ts @@ -0,0 +1,84 @@ +import { GroupLayerEntryConfig } from '@config/types/classes/sub-layer-config/group-node/group-layer-entry-config'; +import { Extent } from '@config/types/map-schema-types'; +import { WmsLayerConfig } from '@config/types/classes/geoview-config/raster-config/wms-config'; +import { isvalidComparedToInternalSchema } from '@config/utils'; +import { GeoviewLayerConfigError } from '@config/types/classes/config-exceptions'; + +import { validateExtentWhenDefined } from '@/geo/utils/utilities'; + +// ======================== +// #region CLASS HEADER +/** + * Base type used to define the common implementation of an ESRI GeoView sublayer to display on the map. + */ +export class WmsGroupLayerConfig extends GroupLayerEntryConfig { + // =============== + // #region METHODS + /* + * Methods are listed in the following order: abstract, override, private, protected, public and static. + */ + + // ================ + // #region OVERRIDE + /** *************************************************************************************************************************** + * This method is used to fetch, parse and extract the relevant information from the metadata of the group layer. + * The same method signature is used by layer group nodes and leaf nodes (layers). + * @override @async + */ + override async fetchLayerMetadata(): Promise { + // If an error has already been detected, then the layer is unusable. + if (this.getErrorDetectedFlag()) return; + + // WMS service metadata contains the layer's metadata. We don't have to fetch again. + const layerMetadata = WmsLayerConfig.getLayerMetadataEntry( + this.layerId, + this.getGeoviewLayerConfig().getServiceMetadata().Capability.Layer + ); + if (layerMetadata) { + // The layer group exists in the service metadata, use its metadata + this.setLayerMetadata(layerMetadata); + } else { + // User defined layer groups cannot be found in the service metadata, we will use the topmost layer's metadata. + this.setLayerMetadata(this.getGeoviewLayerConfig().getServiceMetadata().Capability.Layer); + } + + // Parse the raw layer metadata and build the geoview configuration. + this.#parseLayerMetadata(); + + await this.fetchListOfLayerMetadata(); + + if (!isvalidComparedToInternalSchema(this.getSchemaPath(), this, true)) { + throw new GeoviewLayerConfigError( + `GeoView internal configuration ${this.getLayerPath()} is invalid compared to the internal schema specification.` + ); + } + } + + // #endregion OVERRIDE + + // =============== + // #region PRIVATE + /** *************************************************************************************************************************** + * This method is used to analyze metadata and extract the relevant information from a group layer based on a definition + * provided by the WMS service. + * @private + */ + #parseLayerMetadata(): void { + const layerMetadata = this.getLayerMetadata(); + + this.layerName = layerMetadata.Title as string; + + if (layerMetadata?.Attribution?.Title) this.attributions.push(layerMetadata.Attribution.Title as string); + + this.initialSettings.states!.queryable = (layerMetadata.queryable || false) as boolean; + + this.minScale = (layerMetadata.MinScaleDenominator as number) || 0; + this.maxScale = (layerMetadata.MaxScaleDenominator as number) || 0; + + this.initialSettings.extent = validateExtentWhenDefined(layerMetadata.EX_GeographicBoundingBox as Extent); + this.initialSettings.bounds = this.initialSettings.extent; + } + // #endregion PRIVATE + // #endregion METHODS + // #endregion CLASS DEFINITION +} diff --git a/packages/geoview-core/src/api/config/types/classes/sub-layer-config/leaf/abstract-base-esri-layer-entry-config.ts b/packages/geoview-core/src/api/config/types/classes/sub-layer-config/leaf/abstract-base-esri-layer-entry-config.ts index e8b25095262..e15848541e9 100644 --- a/packages/geoview-core/src/api/config/types/classes/sub-layer-config/leaf/abstract-base-esri-layer-entry-config.ts +++ b/packages/geoview-core/src/api/config/types/classes/sub-layer-config/leaf/abstract-base-esri-layer-entry-config.ts @@ -8,6 +8,8 @@ import { logger } from '@/core/utils/logger'; import { DateMgt, TimeDimensionESRI } from '@/core/utils/date-mgt'; import { Projection } from '@/geo/utils/projection'; import { validateExtentWhenDefined } from '@/geo/utils/utilities'; +import { isvalidComparedToInternalSchema } from '@/api/config/utils'; +import { GeoviewLayerConfigError } from '../../config-exceptions'; // ======================== // #region CLASS DEFINITION @@ -45,6 +47,12 @@ export abstract class AbstractBaseEsriLayerEntryConfig extends AbstractBaseLayer this.setLayerMetadata(data); // Parse the raw layer metadata and build the geoview configuration. this.parseLayerMetadata(); + + if (!isvalidComparedToInternalSchema(this.getSchemaPath(), this, true)) { + throw new GeoviewLayerConfigError( + `GeoView internal configuration ${this.getLayerPath()} is invalid compared to the internal schema specification.` + ); + } return; } } catch (error) { @@ -142,7 +150,7 @@ export abstract class AbstractBaseEsriLayerEntryConfig extends AbstractBaseLayer name: fieldEntry.name, alias: fieldEntry.alias, type: AbstractBaseEsriLayerEntryConfig.#convertEsriFieldType(fieldEntry.type as string), - domaine: Cast(fieldEntry.domain), + domain: Cast(fieldEntry.domain), }) ); }); diff --git a/packages/geoview-core/src/api/config/types/classes/sub-layer-config/leaf/abstract-base-layer-entry-config.ts b/packages/geoview-core/src/api/config/types/classes/sub-layer-config/leaf/abstract-base-layer-entry-config.ts index 128f76bd76c..c808f7c1c11 100644 --- a/packages/geoview-core/src/api/config/types/classes/sub-layer-config/leaf/abstract-base-layer-entry-config.ts +++ b/packages/geoview-core/src/api/config/types/classes/sub-layer-config/leaf/abstract-base-layer-entry-config.ts @@ -1,4 +1,4 @@ -import { TypeBaseSourceInitialConfig, TypeTemporalDimension, TypeStyleGeometry, TypeLayerEntryType } from '@config/types/map-schema-types'; +import { TypeTemporalDimension, TypeStyleGeometry, TypeLayerEntryType } from '@config/types/map-schema-types'; import { EntryConfigBaseClass } from '@/api/config/types/classes/sub-layer-config/entry-config-base-class'; // ======================== @@ -12,9 +12,6 @@ export abstract class AbstractBaseLayerEntryConfig extends EntryConfigBaseClass /** The geometry type of the leaf node. */ geometryType?: TypeStyleGeometry; - /** Source settings to apply to the GeoView vector layer source at creation time. */ - source?: TypeBaseSourceInitialConfig; - /** Optional temporal dimension. */ temporalDimension?: TypeTemporalDimension; // #endregion PUBLIC PROPERTIES diff --git a/packages/geoview-core/src/api/config/types/classes/sub-layer-config/leaf/raster/esri-dynamic-layer-entry-config.ts b/packages/geoview-core/src/api/config/types/classes/sub-layer-config/leaf/raster/esri-dynamic-layer-entry-config.ts index 4343e55b210..5bc0dae804e 100644 --- a/packages/geoview-core/src/api/config/types/classes/sub-layer-config/leaf/raster/esri-dynamic-layer-entry-config.ts +++ b/packages/geoview-core/src/api/config/types/classes/sub-layer-config/leaf/raster/esri-dynamic-layer-entry-config.ts @@ -15,20 +15,21 @@ import { EsriBaseRenderer, createStyleUsingEsriRenderer } from '@/api/config/esr /** * The ESRI dynamic geoview sublayer class. */ + export class EsriDynamicLayerEntryConfig extends AbstractBaseEsriLayerEntryConfig { // ========================= - // #region PUBLIC PROPERTIES + // #region PROPERTIES /** Source settings to apply to the GeoView image layer source at creation time. */ declare source: TypeSourceEsriDynamicInitialConfig; /** Style to apply to the raster layer. */ style?: TypeStyleConfig; - // #endregion PUBLIC PROPERTIES + // #endregion PROPERTIES // =============== // #region METHODS /* - * Methods are listed in the following order: abstract, override, private, protected and public. + * Methods are listed in the following order: abstract, override, private, protected, public and static. */ // ========================== // #region OVERRIDE diff --git a/packages/geoview-core/src/api/config/types/classes/sub-layer-config/leaf/raster/wms-layer-entry-config.ts b/packages/geoview-core/src/api/config/types/classes/sub-layer-config/leaf/raster/wms-layer-entry-config.ts new file mode 100644 index 00000000000..dfcf88e1693 --- /dev/null +++ b/packages/geoview-core/src/api/config/types/classes/sub-layer-config/leaf/raster/wms-layer-entry-config.ts @@ -0,0 +1,148 @@ +import { CV_CONST_SUB_LAYER_TYPES, CV_CONST_LEAF_LAYER_SCHEMA_PATH } from '@config/types/config-constants'; +import { TypeJsonArray, TypeJsonObject } from '@config/types/config-types'; +import { TypeStyleConfig, TypeLayerEntryType, TypeSourceWmsInitialConfig, Extent } from '@config/types/map-schema-types'; +import { AbstractBaseLayerEntryConfig } from '@config/types/classes/sub-layer-config/leaf/abstract-base-layer-entry-config'; +import { WmsLayerConfig } from '@config/types/classes/geoview-config/raster-config/wms-config'; +import { isvalidComparedToInternalSchema } from '@config/utils'; +import { GeoviewLayerConfigError } from '@config/types/classes/config-exceptions'; + +import { logger } from '@/core/utils/logger'; +import { DateMgt } from '@/core/utils/date-mgt'; + +// ======================== +// #region CLASS DEFINITION +/** + * The OGC WMS geoview sublayer class. + */ + +export class WmsLayerEntryConfig extends AbstractBaseLayerEntryConfig { + // ========================= + // #region PROPERTIES + /** Source settings to apply to the GeoView image layer source at creation time. */ + declare source: TypeSourceWmsInitialConfig; + + /** Style to apply to the raster layer. */ + style?: TypeStyleConfig; + // #endregion PROPERTIES + + // =============== + // #region METHODS + /* + * Methods are listed in the following order: abstract, override, private, protected, public and static. + */ + // ========================== + // #region OVERRIDE + + /** + * The getter method that returns the schemaPath property. Each geoview sublayer type knows what section of the schema must be + * used to do its validation. + * + * @returns {string} The schemaPath associated to the sublayer. + * @protected @override + */ + protected override getSchemaPath(): string { + return CV_CONST_LEAF_LAYER_SCHEMA_PATH.WMS; + } + + /** + * A method that returns the entryType property. Each sublayer knows what entry type is associated to it. + * + * @returns {TypeLayerEntryType} The entryType associated to the sublayer. + * @protected @override + */ + protected override getEntryType(): TypeLayerEntryType { + return CV_CONST_SUB_LAYER_TYPES.RASTER_IMAGE; + } + + /** + * This method is used to fetch, parse and extract the relevant information from the metadata of the leaf node. + * The same method signature is used by layer group nodes and leaf nodes (layers). + * @override + */ + override fetchLayerMetadata(): Promise { + // If an error has already been detected, then the layer is unusable. + if (this.getErrorDetectedFlag()) return Promise.resolve(); + + // WMS service metadata contains the layer's metadata. + const layerMetadata = WmsLayerConfig.getLayerMetadataEntry( + this.layerId, + this.getGeoviewLayerConfig().getServiceMetadata().Capability.Layer + ); + if (layerMetadata) { + this.setLayerMetadata(layerMetadata); + // Parse the raw layer metadata and build the geoview configuration. + this.#parseLayerMetadata(); + + if (!isvalidComparedToInternalSchema(this.getSchemaPath(), this, true)) { + throw new GeoviewLayerConfigError( + `GeoView internal configuration ${this.getLayerPath()} is invalid compared to the internal schema specification.` + ); + } + + return Promise.resolve(); + } + + logger.logError(`Can't find layer's metadata for layerPath ${this.getLayerPath()}.`); + this.setErrorDetectedFlag(); + return Promise.resolve(); + } + + /** + * Apply default values. The default values will be overwritten by the values in the metadata when they are analyzed. + * The resulting config will then be overwritten by the values provided in the user config. + * @override + */ + override applyDefaultValues(): void { + super.applyDefaultValues(); + this.source = { + crossOrigin: 'Anonymous', + serverType: 'mapserver', + projection: 3978, + featureInfo: { + queryable: false, + nameField: '', + outfields: [], + }, + }; + } + // #endregion OVERRIDE + + // =============== + // #region PRIVATE + /** + * This method is used to parse the layer metadata and extract the source information and other properties. + * @protected + */ + #parseLayerMetadata(): void { + const layerMetadata = this.getLayerMetadata(); + + if (layerMetadata?.Attribution?.Title) this.attributions.push(layerMetadata.Attribution.Title as string); + + this.bounds = layerMetadata.EX_GeographicBoundingBox as Extent; + + if (layerMetadata.queryable) this.source.featureInfo!.queryable = layerMetadata.queryable as boolean; + + this.source.wmsStyle = layerMetadata.Style + ? ((layerMetadata.Style as TypeJsonArray).map((style) => { + return style.Name; + }) as string[]) + : undefined; + + this.#processTemporalDimension(layerMetadata.Dimension); + } + + /** *************************************************************************************************************************** + * This method will create a Geoview temporal dimension if it existds in the service metadata + * @param {TypeJsonObject} wmsDimension The WMS time dimension object + * @protected + */ + #processTemporalDimension(wmsDimension: TypeJsonObject): void { + if (wmsDimension) { + const temporalDimension: TypeJsonObject | undefined = (wmsDimension as TypeJsonArray).find((dimension) => dimension.name === 'time'); + if (temporalDimension) this.temporalDimension = DateMgt.createDimensionFromOGC(temporalDimension); + } + } + // #endregion PRIVATE + // #endregion METHODS + // #endregion CLASS DEFINITION +} diff --git a/packages/geoview-core/src/api/config/types/config-constants.ts b/packages/geoview-core/src/api/config/types/config-constants.ts index 923f23cc158..fdacfcfeb13 100644 --- a/packages/geoview-core/src/api/config/types/config-constants.ts +++ b/packages/geoview-core/src/api/config/types/config-constants.ts @@ -82,7 +82,7 @@ export const CV_GEOVIEW_SCHEMA_PATH: Record = { VECTOR_TILES: '', OGC_FEATURE: '', WFS: '', - WMS: '', + WMS: 'https://cgpv/schema#/definitions/WmsLayerConfig', }; export const CV_MAP_CONFIG_SCHEMA_PATH = 'https://cgpv/schema#/definitions/MapFeatureConfig'; export const CV_LAYER_GROUP_SCHEMA_PATH = 'https://cgpv/schema#/definitions/GroupLayerEntryConfig'; diff --git a/packages/geoview-core/src/api/config/types/config-validation-schema.json b/packages/geoview-core/src/api/config/types/config-validation-schema.json index 727358c76cc..72d7e36d061 100644 --- a/packages/geoview-core/src/api/config/types/config-validation-schema.json +++ b/packages/geoview-core/src/api/config/types/config-validation-schema.json @@ -315,8 +315,9 @@ "description": "Universal map settings", "properties": { "canRemoveSublayers": { + "description": "Whether or not sublayers can be removed from layer groups.", "type": "boolean", - "description": "Whether or not sublayers can be removed from layer groups. Default = true." + "default": true } } }, @@ -398,16 +399,19 @@ "$ref": "#/definitions/TypeGeoviewLayerType" }, "serviceDateFormat": { - "description": "Date format used by the service endpoint (default 'DD/MM/YYYY HH:MM:SSZ').", - "type": "string" + "description": "Date format used by the service endpoint.", + "type": "string", + "default": "DD/MM/YYYY HH:MM:SSZ" }, "externalDateFormat": { - "description": "Date format used by the getFeatureInfo to output date variable (default 'DD/MM/YYYY HH:MM:SSZ').", - "type": "string" + "description": "Date format used by the getFeatureInfo to output date variable.", + "type": "string", + "default": "DD/MM/YYYY HH:MM:SSZ" }, "isTimeAware": { + "description": "Flag to mention if layer will use its time dimension if provided. Used to remove a layer from time enabled functions like time slider.", "type": "boolean", - "description": "Flag to mention if layer will use its time dimension if provided (default: true). Used to remove a layer from time enabled functions like time slider." + "default": true }, "listOfLayerEntryConfig": { "description": "The layer entries to use from the GeoView layer.", @@ -557,8 +561,9 @@ "default": 1 }, "projection": { + "description": "The projection code of the coordinates.", "type": "number", - "description": "The projection code of the coordinates. Default value is 4326." + "default": 4326 } }, "required": ["id", "coordinate"] @@ -815,6 +820,24 @@ } ] }, + "WmsLayerConfig": { + "description": "Structure used by the viewer to describe the configuration of a WMS layer.", + "type": "object", + "allOf": [ + { + "description": "The parent class.", + "$ref": "#/definitions/AbstractGeoviewLayerConfig" + }, + { + "type": "object", + "properties": { + "geoviewLayerType": { + "enum": ["ogcWms"] + } + } + } + ] + }, "EntryConfigBaseClass": { "description": "Base class from which we derive all the nodes (group and leaves) in the layer tree.", "type": "object", @@ -859,12 +882,12 @@ } }, "minScale": { - "description": "Minimum scale the layer can display (default: 0).", + "description": "Minimum scale the layer can display.", "type": "number", "default": 0 }, "maxScale": { - "description": "Maximum scale the layer can display (default: 0).", + "description": "Maximum scale the layer can display.", "type": "number", "default": 0 }, @@ -987,7 +1010,7 @@ } }, "entryType": { - "enum": ["raster-tile"] + "enum": ["raster-image"] }, "source": { "$ref": "#/definitions/TypeSourceEsriDynamicInitialConfig" @@ -1027,6 +1050,27 @@ } ] }, + "OgcWmsLayerEntryConfig": { + "description": "Class from which we derive all the WMS leaf nodes in the layer tree.", + "type": "object", + "allOf": [ + { + "description": "The parent class.", + "$ref": "#/definitions/AbstractBaseLayerEntryConfig" + }, + { + "type": "object", + "properties": { + "source": { + "$ref": "#/definitions/TypeSourceWmsInitialConfig" + }, + "style": { + "$ref": "#/definitions/TypeStyleConfig" + } + } + } + ] + }, "TypeStyleGeometry": { "description": "Valid keys for the geometryType property.", "enum": ["point", "linestring", "polygon"] @@ -1040,7 +1084,8 @@ }, "crossOrigin": { "description": "The crossOrigin attribute if needed to load the data.", - "type": "string" + "type": "string", + "default": "Anonymous" } } }, @@ -1064,47 +1109,38 @@ } ] }, - "TypeSourceImageWmsInitialConfig": { + "TypeSourceWmsInitialConfig": { "description": "Initial settings for tile image sources.", - "additionalProperties": false, "type": "object", "allOf": [ { "$ref": "#/definitions/TypeBaseSourceInitialConfig" }, { - "additionalProperties": false, "type": "object", "properties": { "featureInfo": { "$ref": "#/definitions/TypeFeatureInfoLayerConfig" }, "serverType": { - "$ref": "#/definitions/TypeOfServer", - "description": "The type of the remote WMS server. The default value is mapserver." + "description": "The type of the remote WMS server.", + "$ref": "#/definitions/TypeOfServer" }, - "style": { - "oneOf": [ - { - "type": "string", - "description": "Single style to apply" - }, - { - "type": "array", - "items": { - "type": "string" - }, - "description": "Array of style to choose." - } - ] + "wmsStyle": { + "description": "Array of style to choose from.", + "type": "array", + "items": { + "type": "string" + } } } } ] }, "TypeOfServer": { - "description": "The type of the remote WMS server. The default value is mapserver.", - "enum": ["mapserver", "geoserver", "qgis"] + "description": "The type of the remote WMS server.", + "enum": ["mapserver", "geoserver", "qgis"], + "default": "mapserver" }, "TypeSourceImageStaticInitialConfig": { "description": "Initial settings for static image sources.", @@ -1142,8 +1178,9 @@ "type": "object", "properties": { "maxRecordCount": { - "description": "Maximum number of records to fetch (default: 0).", - "type": "number" + "description": "Maximum number of records to fetch.", + "type": "number", + "default": 0 }, "layerFilter": { "description": "Filter to apply on features of this layer.", @@ -1156,9 +1193,9 @@ "$ref": "#/definitions/TypeVectorSourceFormats" }, "strategy": { + "description": "The loading strategy to use. By default an all strategy is used, a one-off strategy which loads all features at once.", "enum": ["all", "bbox"], - "default": "all", - "description": "The loading strategy to use. By default an all strategy is used, a one-off strategy which loads all features at once." + "default": "all" } } } @@ -1261,8 +1298,9 @@ "type": "object", "properties": { "maxRecordCount": { - "description": "Maximum number of records to fetch (default: 0).", - "type": "number" + "description": "Maximum number of records to fetch.", + "type": "number", + "default": 0 }, "layerFilter": { "description": "Filter to apply on features of this layer.", @@ -1284,7 +1322,7 @@ ] }, "TypeEsriFormatParameter": { - "description": "The format of the exported image. The default format is png.", + "description": "The format of the exported image.", "enum": ["png", "jpg", "gif", "svg"], "default": "png" }, @@ -1299,8 +1337,9 @@ "type": "object", "properties": { "maxRecordCount": { - "description": "Maximum number of records to fetch (default: 0).", - "type": "number" + "description": "Maximum number of records to fetch.", + "type": "number", + "default": 0 }, "layerFilter": { "description": "Filter to apply on features of this layer.", @@ -1320,7 +1359,7 @@ "type": "object", "properties": { "queryable": { - "description": "Allow querying. Default = false.", + "description": "Allow querying.", "type": "boolean", "default": false }, @@ -1350,11 +1389,21 @@ }, "type": { "description": "The field type.", - "type": "string" + "$ref": "#/definitions/TypeOutfieldsType" }, "domain": { - "description": "An array of values that constitute the domain.", - "type": "array" + "description": "The domain of values.", + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/definitions/codedValueType" + }, + { + "$ref": "#/definitions/rangeDomainType" + } + ] } }, "required": ["name", "alias", "type", "domain"] @@ -1364,6 +1413,64 @@ "additionalProperties": false, "enum": ["string", "number", "date", "url"] }, + "codedValueType": { + "description": "Coded value definition.", + "additionalProperties": false, + "type": "object", + "properties": { + "type": { + "enum": ["codedValue"] + }, + "name": { + "description": "The domain name.", + "type": "string" + }, + "description": { + "description": "A description of the domain.", + "type": "string" + }, + "codedValues": { + "description": "The list of coded values.", + "$ref": "#/definitions/codeValueEntryType" + } + } + }, + "codeValueEntryType": { + "description": "The structure of a code value.", + "additionalProperties": false, + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "code": { + "description": "The code property has an unknown type." + } + } + }, + "rangeDomainType": { + "description": "The structure of a range domain.", + "additionalProperties": false, + "type": "object", + "properties": { + "type": { + "enum": ["range"] + }, + "name": { + "description": "The domain name.", + "type": "string" + }, + "range": { + "type":"array", + "minItems": 2, + "maxItems": 2, + "items": { + "description": "The code property has an unknown type." + } + } + }, + "required": ["type", "name", "range"] + }, "TypeLayerEntryType": { "description": "Layer entry data type.", "enum": ["vector", "vector-tile", "raster-tile", "raster-image", "group"] @@ -1420,8 +1527,7 @@ "type": "array", "items": { "type": "string" - }, - "minItems": 1 + } }, "hasDefault": { "description": "Flag used to indicate that the symbology has a default value.", @@ -1444,14 +1550,15 @@ "type": "object", "properties": { "visible": { - "description": "Flag used to show/hide features associated to the label (default: true).", - "type": "boolean" + "description": "Flag used to show/hide features associated to the label.", + "type": "boolean", + "default": true }, "label": { "type": "string" }, "values": { - "description": "Simple type has a single value at index 0; uniqueValue type has many entries (up to 3 for ESRI) and classBreaks type has two entries (index 0 for min and index 1 for max).", + "description": "Simple type has an empty array; uniqueValue type has many entries (up to 3 for ESRI) and classBreaks type has two entries (index 0 for min and index 1 for max).", "type": "array", "items": { "oneOf": [ @@ -1462,8 +1569,7 @@ "type": "number" } ] - }, - "minItems": 1 + } }, "settings": { "$ref": "#/definitions/TypeKindOfVectorSettings" @@ -1583,16 +1689,19 @@ "$ref": "#/definitions/TypeStrokeSymbolConfig" }, "paternSize": { - "description": "Distance between patern lines. Default = 8", - "type": "number" + "description": "Distance between patern lines.", + "type": "number", + "default": 8 }, "paternWidth": { - "description": "Patern line width.default = 1.", - "type": "number" + "description": "Patern line width.", + "type": "number", + "default": 1 }, "fillStyle": { - "description": "Kind of filling for vector features. Default = solid. ", - "$ref": "#/definitions/TypeFillStyle" + "description": "Kind of filling for vector features.", + "$ref": "#/definitions/TypeFillStyle", + "default": "solid" } } } @@ -1693,7 +1802,8 @@ }, "crossOrigin": { "description": "The crossOrigin attribute for loaded images. Note that you must provide a crossOrigin value if you want to access pixel data with the Canvas renderer.", - "type": "string" + "type": "string", + "default": "Anonymous" } } } diff --git a/packages/geoview-core/src/api/config/types/map-schema-types.ts b/packages/geoview-core/src/api/config/types/map-schema-types.ts index 667b1e27b91..41ac805c82f 100644 --- a/packages/geoview-core/src/api/config/types/map-schema-types.ts +++ b/packages/geoview-core/src/api/config/types/map-schema-types.ts @@ -185,7 +185,7 @@ export type TypeViewSettings = { enableRotation?: boolean; /** * The initial rotation for the view in degree (positive rotation clockwise, 0 means North). Will be converted to radiant by - * the viewer. Domaine = [0..360], default = 0. + * the viewer. Domain = [0..360], default = 0. */ rotation?: number; /** The extent that constrains the view. Called with [minX, minY, maxX, maxY] extent coordinates. @@ -194,12 +194,12 @@ export type TypeViewSettings = { maxExtent?: Extent; /** * The minimum zoom level used to determine the resolution constraint. If not set, will use default from basemap. - * Domaine = [0..50]. + * Domain = [0..50]. */ minZoom?: number; /** * The maximum zoom level used to determine the resolution constraint. If not set, will use default from basemap. - * Domaine = [0..50]. + * Domain = [0..50]. */ maxZoom?: number; /** @@ -378,13 +378,13 @@ export interface TypeSourceTileInitialConfig extends TypeBaseSourceInitialConfig } /** Initial settings for WMS image sources. */ -export interface TypeSourceImageWmsInitialConfig extends TypeBaseSourceInitialConfig { +export interface TypeSourceWmsInitialConfig extends TypeBaseSourceInitialConfig { /** Definition of the feature information structure that will be used by the getFeatureInfo method. */ featureInfo?: TypeFeatureInfoLayerConfig; /** The type of the remote WMS server. The default value is mapserver. */ serverType?: TypeOfServer; /** Style to apply. Default = '' */ - style?: string | string[]; + wmsStyle?: string[]; } /** Type of server. */ diff --git a/packages/geoview-core/src/geo/layer/geoview-layers/raster/wms.ts b/packages/geoview-core/src/geo/layer/geoview-layers/raster/wms.ts index fec3799827a..b6ef0bb7bac 100644 --- a/packages/geoview-core/src/geo/layer/geoview-layers/raster/wms.ts +++ b/packages/geoview-core/src/geo/layer/geoview-layers/raster/wms.ts @@ -117,7 +117,7 @@ export class WMS extends AbstractGeoViewRaster { * * @returns {Promise} A promise that the execution is completed. */ - // GV Layers Refactoring - Obsolete (in config?) + // GV Layers Refactoring - Obsolete (in config) protected override async fetchServiceMetadata(): Promise { const metadataUrl = getLocalizedValue(this.metadataAccessPath, AppEventProcessor.getDisplayLanguage(this.mapId)); if (metadataUrl) { @@ -195,7 +195,7 @@ export class WMS extends AbstractGeoViewRaster { * @returns {Promise} A promise that the execution is completed. * @private */ - // GV Layers Refactoring - Obsolete (in config?) + // GV Layers Refactoring - Obsolete (in config) async #getServiceMetadata(url: string): Promise { try { const response = await fetch(url); @@ -217,7 +217,7 @@ export class WMS extends AbstractGeoViewRaster { * @returns {Promise} A promise that the execution is completed. * @private */ - // GV Layers Refactoring - Obsolete (in config?) + // GV Layers Refactoring - Obsolete (in config) async #fetchXmlServiceMetadata(metadataUrl: string): Promise { try { const parser = new WMSCapabilities(); @@ -261,7 +261,7 @@ export class WMS extends AbstractGeoViewRaster { * @returns {number[]} An array containing the path to the layer or [] if not found. * @private */ - // GV Layers Refactoring - Obsolete (in config?) + // GV Layers Refactoring - Obsolete (in config) #getMetadataLayerPath(layerName: string, layerProperty: TypeJsonObject, pathToTheParentLayer: number[] = []): number[] { const newLayerPath = [...pathToTheParentLayer]; if (Array.isArray(layerProperty)) { @@ -294,7 +294,7 @@ export class WMS extends AbstractGeoViewRaster { * @param {TypeJsonObject} layerToAdd The layer property to add * @private */ - // GV Layers Refactoring - Obsolete (in config?) + // GV Layers Refactoring - Obsolete (in config) #addLayerToMetadataInstance( metadataLayerPathToAdd: number[], metadataLayer: TypeJsonObject | undefined, @@ -323,7 +323,7 @@ export class WMS extends AbstractGeoViewRaster { * @returns {TypeLayerEntryConfig[]} The array of layer configurations. * @private */ - // GV Layers Refactoring - Obsolete (in config?) + // GV Layers Refactoring - Obsolete (in config) #getLayersToQuery(): TypeLayerEntryConfig[] { const arrayOfLayerIds: TypeLayerEntryConfig[] = []; const gatherLayerIds = (listOfLayerEntryConfig = this.listOfLayerEntryConfig): void => { @@ -345,7 +345,7 @@ export class WMS extends AbstractGeoViewRaster { * @param {TypeJsonObject | undefined} layer The layer property from the metadata that will inherit the values * @private */ - // GV Layers Refactoring - Obsolete (in config?) + // GV Layers Refactoring - Obsolete (in config) #processMetadataInheritance(parentLayer?: TypeJsonObject, layer: TypeJsonObject | undefined = this.metadata?.Capability?.Layer): void { if (parentLayer && layer) { // Table 7 — Inheritance of Layer properties specified in the standard with 'replace' behaviour. @@ -438,11 +438,11 @@ export class WMS extends AbstractGeoViewRaster { * @param {AbstractBaseLayerEntryConfig} layerConfig The layer configurstion associated to the dynamic group. * @private */ - // TODO: Refactor - Layers Refactoring - Check here for layer metadata config vs layers - // GV Layers Refactoring - Obsolete (in config? in layers?) + // GV Layers Refactoring - Obsolete (in config) #createGroupLayer(layer: TypeJsonObject, layerConfig: AbstractBaseLayerEntryConfig): void { // TODO: Refactor - createGroup is the same thing for all the layers type? group is a geoview structure. // TO.DOCONT: Should it be handle upper in abstract class to loop in structure and launch the creation of a leaf? + // TODO: The answer is no. Even if the final structure is the same, the input structure is different for each geoview layer types. const newListOfLayerEntryConfig: TypeLayerEntryConfig[] = []; const arrayOfLayerMetadata = Array.isArray(layer.Layer) ? layer.Layer : ([layer.Layer] as TypeJsonArray); @@ -483,7 +483,7 @@ export class WMS extends AbstractGeoViewRaster { * @private */ // TODO: Refactor - Layers Refactoring - Check here for layer metadata config vs layers - // GV Layers Refactoring - Obsolete (in config? in layers?) + // GV Layers Refactoring - Obsolete (in config) #getLayerMetadataEntry(layerId: string, layer: TypeJsonObject | undefined = this.metadata?.Capability?.Layer): TypeJsonObject | null { if (!layer) return null; if ('Name' in layer && (layer.Name as string) === layerId) return layer; @@ -657,7 +657,7 @@ export class WMS extends AbstractGeoViewRaster { * @param {TypeJsonObject} wmsTimeDimension The WMS time dimension object * @param {OgcWmsLayerEntryConfig} layerConfig The layer entry to configure */ - // GV Layers Refactoring - Obsolete (in config?) + // GV Layers Refactoring - Obsolete (in config) protected processTemporalDimension(wmsTimeDimension: TypeJsonObject, layerConfig: OgcWmsLayerEntryConfig): void { if (wmsTimeDimension !== undefined) { this.setTemporalDimension(layerConfig.layerPath, DateMgt.createDimensionFromOGC(wmsTimeDimension)); diff --git a/packages/geoview-core/src/geo/layer/gv-layers/utils.ts b/packages/geoview-core/src/geo/layer/gv-layers/utils.ts index 5de9a4a6227..82235deecff 100644 --- a/packages/geoview-core/src/geo/layer/gv-layers/utils.ts +++ b/packages/geoview-core/src/geo/layer/gv-layers/utils.ts @@ -61,6 +61,8 @@ export function esriGetFieldType( * @param {string} fieldName field name for which we want to get the domain. * @returns {codedValueType | rangeDomainType | null} The domain of the field. */ +// TODO: ESRI domains are translated to GeoView domains in the configuration. Any GeoView layer that support geoview domains can +// TODO.CONT: call a method getFieldDomain that use config.source.featureInfo.outfields to find a field domain. export function esriGetFieldDomain( layerConfig: EsriDynamicLayerEntryConfig | EsriFeatureLayerEntryConfig | EsriImageLayerEntryConfig, fieldName: string