diff --git a/Docs/ELI-overview.md b/Docs/ELI-overview.md
index ac5753bdd..bbf552170 100644
--- a/Docs/ELI-overview.md
+++ b/Docs/ELI-overview.md
@@ -20,11 +20,9 @@ This table gives a summary of ids, names and other metainformation. [See the onl
| osmfj-inuyama-2023 | Inuyama city and around Imagery 2023 | photo | | OSMFJ_Ortho |
| Arenda_OAM | Lupang Arenda, Taytay Drone Imagery | photo | ⭐ | |
| Pangasinan_Bulacan_HiRes | Pangasinán/Bulacan (Philippines HiRes) | photo | | |
-| gaza_pleiades_20140706_nir | Gaza Strip - Pléiades - 2014/07/06 (NIR) | photo | | Copyright CNES 2014, Distribution Airbus Defence and Space |
-| gaza_pleiades_20140706 | Gaza Strip - Pléiades - 2014/07/06 | photo | | Copyright CNES 2014, Distribution Airbus Defence and Space |
| Singapore-Landlot | Singapore Landlot | map | | ©OneMap Singapore ODL v1.0 |
| Singapore-OneMap | Singapore OneMap | map | | ©OneMap Singapore ODL v1.0 |
-| Singapore-Orthophoto | Singapore OneMap Orthophoto | map | | ©OneMap Singapore ODL v1.0 |
+| Singapore-Orthophoto | Singapore OneMap Orthophoto | photo | | ©OneMap Singapore ODL v1.0 |
| TW_NLSC_WMS_LANDSECT | Taiwan Land-Section Data | other | | © National Land Surveying and Mapping Center, Taiwan OGDL 1.0 |
| TW_NLSC_WMS_Village | Taiwan Village Boundaries | other | | © National Land Surveying and Mapping Center, Taiwan OGDL 1.0 |
| AL_DPGJC_ASIG_SistemiAdresave | Address System Albania (ASIG) | map | | |
@@ -162,6 +160,7 @@ This table gives a summary of ids, names and other metainformation. [See the onl
| PrahaIPRlatestorthophoto | Praha IPR latest orthophoto | photo | | |
| PrahaIPRlow-vegetationorthophoto | Praha IPR low-vegetation orthophoto | photo | ⭐ | IPR Praha |
| AktuelleLuftbilderDerLandeshauptstadtMuenchen20cm | Aktuelle Luftbilder der Landeshauptstadt München 20cm | photo | | Datenquelle: dl-de/by-2-0: Landeshauptstadt München – Kommunalreferat – GeodatenService – www.geodatenservice-muenchen.de |
+| Baden_Wuerrtemberg-DOP20 | Baden-Würrtemberg DOP20 | photo | ⭐ | © LGL-BW (2025) - dl-de/by-2-0 (https://www.govdata.de/dl-de/by-2-0) - Verwendung unter besonderer Erlaubnis |
| Berlin-2020-TrueDOP | Berlin/Geoportal TrueDOP20RGB (2020) | photo | | Geoportal Berlin/Digitale farbige TrueOrthophotos 2020 (TrueDOP20RGB) (codefor.de mirror) |
| Berlin-Alkis | Berlin/Geoportal ALKIS | other | | Geoportal Berlin/ALKIS Berlin (Amtliches Liegenschaftskatasterinformationssystem) (codefor.de proxy) |
| Berlin-Baumbestand_Alkis | Berlin/Geoportal Baumbestand, Alkis s/w | other | | Geoportal Berlin/Straßen- und Anlagenbaumbestand Berlin, ALKIS s/w (codefor.de proxy) |
@@ -172,11 +171,12 @@ This table gives a summary of ids, names and other metainformation. [See the onl
| Berlin-2017 | Berlin/Geoportal DOP20RGB (2017) | historicphoto | | Geoportal Berlin/Digitale farbige Orthophotos 2017 (DOP20RGB) (codefor.de mirror) |
| Berlin-2018 | Berlin/Geoportal DOP20RGB (2018) | historicphoto | | Geoportal Berlin/Digitale farbige Orthophotos 2018 (DOP20RGB) (codefor.de mirror) |
| Berlin-2019 | Berlin/Geoportal DOP20RGB (2019) | historicphoto | | Geoportal Berlin/Digitale farbige Orthophotos 2019 (DOP20RGB) (codefor.de mirror) |
-| Berlin-2020-infrared | Berlin/Geoportal DOP20CIR (2020 infrared) | historicphoto | | Geoportal Berlin/Digitale Color-Infrarot-Orthophotos 2020 (DOP20CIR) (codefor.de mirror) |
-| Berlin-2021 | Berlin/Geoportal DOP20RGB (2021) | photo | | Geoportal Berlin/Digitale farbige Orthophotos 2021 (DOP20RGBI) (codefor.de mirror) |
-| Berlin-2022 | Berlin/Geoportal DOP20RGBI (2022) | photo | | Geoportal Berlin/Digitale farbige TrueOrthophotos 2022 (DOP20RGBI) (codefor.de mirror) |
-| Berlin-2023 | Berlin/Geoportal DOP20RGBI (2023) | photo | | Geoportal Berlin/Digitale farbige TrueOrthophotos 2023 (DOP20RGBI) (codefor.de mirror) |
-| Berlin-2024 | Berlin/Geoportal DOP20RGBI (2024) | photo | ⭐ | Geoportal Berlin/Digitale farbige TrueOrthophotos 2024 (DOP20RGBI) (codefor.de mirror) |
+| Berlin-2020 | Berlin/Geoportal DOP20RGB (2020) | historicphoto | | Geoportal Berlin/Digitale farbige TrueOrthophotos 2020 (DOP20RGB) (codefor.de mirror) |
+| Berlin-2021 | Berlin/Geoportal DOP20RGB (2021) | photo | | Geoportal Berlin/Digitale farbige Orthophotos 2021 (DOP20RGB) (codefor.de mirror) |
+| Berlin-2022 | Berlin/Geoportal DOP20RGB (2022) | photo | | Geoportal Berlin/Digitale farbige TrueOrthophotos 2022 (DOP20RGB) (codefor.de mirror) |
+| Berlin-2023 | Berlin/Geoportal DOP20RGB (2023) | photo | | Geoportal Berlin/Digitale farbige TrueOrthophotos 2023 (DOP20RGB) (codefor.de mirror) |
+| Berlin-2024 | Berlin/Geoportal DOP20RGB (2024) | photo | ⭐ | Geoportal Berlin/Digitale farbige TrueOrthophotos 2024 (DOP20RGB) (codefor.de mirror) |
+| Berlin-2025 | Berlin/Geoportal DOP20RGB (2025) | photo | | Geoportal Berlin/Digitale farbige Orthophotos 2025 (DOP20RGB) (codefor.de mirror) |
| Brandenburg-Alkis | Brandenburg GeoBasis-DE/LGB / Alkis | other | | GeoBasis-DE/LGB / Alkis, dl-de/by-2-0 |
| Brandenburg-DGM | Brandenburg GeoBasis-DE/LGB (latest) / DGM 1m | elevation | | GeoBasis-DE/LGB / BB-BE DGM 1m, dl-de/by-2-0; Geoportal Berlin / DGM, dl-de/by-2-0 |
| Brandenburg-DOP20c | Brandenburg GeoBasis-DE/LGB (latest) / DOP20c | photo | ⭐ | GeoBasis-DE/LGB / BB-BE DOP20c, dl-de/by-2-0; Geoportal Berlin / DOP20, dl-de/by-2-0 |
@@ -188,8 +188,10 @@ This table gives a summary of ids, names and other metainformation. [See the onl
| Frankfurt-am-Main-2017 | Frankfurt am Main Luftbild 2017 | photo | | Stadtvermessungsam Frankfurt am Main |
| Frankfurt-am-Main-2018 | Frankfurt am Main Luftbild 2018 | photo | | Stadtvermessungsamt Frankfurt am Main |
| Frankfurt-am-Main-2019 | Frankfurt am Main Luftbild 2019 | photo | | Stadtvermessungsamt Frankfurt am Main |
-| hamburg-20cm | Hamburg 20cm (HH LGV DOP20 2022) | photo | ⭐ | Freie und Hansestadt Hamburg, Landesbetrieb Geoinformation und Vermessung |
-| Hamburg-DK5 | Hamburg DK5 (HH LGV DK5 2021) | map | | Freie und Hansestadt Hamburg, Landesbetrieb Geoinformation und Vermessung |
+| Hamburg-Alkis-Farbig | Hamburg ALKIS Basiskarte (farbig) (HH LGV ALKIS 2025) | map | | Freie und Hansestadt Hamburg, Landesbetrieb Geoinformation und Vermessung |
+| Hamburg-DK5 | Hamburg DK5 (HH LGV DK5 2024) | map | | Freie und Hansestadt Hamburg, Landesbetrieb Geoinformation und Vermessung |
+| Hamburg-DOP20-belaubt | Hamburg 20cm (HH LGV DOP20 belaubt 2024) | photo | | Freie und Hansestadt Hamburg, Landesbetrieb Geoinformation und Vermessung |
+| Hamburg-DOP20-unbelaubt | Hamburg 20cm (HH LGV DOP20 unbelaubt 2025) | photo | ⭐ | Freie und Hansestadt Hamburg, Landesbetrieb Geoinformation und Vermessung |
| Hessen-ALKIS | Hesse ALKIS | other | | Geobasisdaten @ Hessisches Landesamt für Bodenmanagement und Geoinformation |
| Hessen-DOP20 | Hesse DOP20 | photo | ⭐ | Geobasisdaten © Hessische Verwaltung für Bodenmanagement und Geoinformation: Digitale Orthophotos |
| Hessen-DTK | Hesse DTK | map | | Geobasisdaten @ Hessisches Landesamt für Bodenmanagement und Geoinformation |
@@ -206,14 +208,20 @@ This table gives a summary of ids, names and other metainformation. [See the onl
| nrw_ortho_wms | NRW Orthophoto (RGB) | photo | ⭐ | |
| nrw_vdop_wms | NRW vDOP | photo | | |
| Saarland-DOP20 | Saarland DOP20 | photo | ⭐ | © Saarländer Landesamt für Vermessung, Geoinformation und Landentwicklung - dl-de/by-2-0 (https://www.govdata.de/dl-de/by-2-0) |
-| GEOSN-DOP-RGB | Saxony latest aerial imagery | photo | ⭐ | Staatsbetrieb Geobasisinformation und Vermessung Sachsen |
-| LSA-DOP20 | © GeoBasis-DE/LVermGeo LSA, DOP20 | photo | ⭐ | © GeoBasis-DE/LVermGeo LSA |
-| GEOSN-WebAtlas | Saxony WebAtlasSN | map | | Staatsbetrieb Geobasisinformation und Vermessung Sachsen |
-| GEOSN-DGM | Saxony digital terrain model | elevation | | Staatsbetrieb Geobasisinformation und Vermessung Sachsen |
| GEOSN-DOP-2005 | Saxony historical aerial imagery 2005 | historicphoto | | Staatsbetrieb Geobasisinformation und Vermessung Sachsen |
+| GEOSN-DOP-2006_2008 | Saxony historical aerial imagery 2006-2008 | historicphoto | | Staatsbetrieb Geobasisinformation und Vermessung Sachsen |
+| GEOSN-DOP-2009_2011 | Saxony historical aerial imagery 2009-2011 | historicphoto | | Staatsbetrieb Geobasisinformation und Vermessung Sachsen |
| GEOSN-DOP-2012_2014 | Saxony historical aerial imagery 2012-2014 | historicphoto | | Staatsbetrieb Geobasisinformation und Vermessung Sachsen |
+| GEOSN-DOP-2015_2017 | Saxony historical aerial imagery 2015-2017 | historicphoto | | Staatsbetrieb Geobasisinformation und Vermessung Sachsen |
+| GEOSN-DOP-2018_2020 | Saxony historical aerial imagery 2018-2020 | historicphoto | | Staatsbetrieb Geobasisinformation und Vermessung Sachsen |
+| GEOSN-DOP-2021_2022 | Saxony historical aerial imagery 2021-2022 | historicphoto | | Staatsbetrieb Geobasisinformation und Vermessung Sachsen |
| GEOSN-DOP-CIR | Saxony latest aerial imagery infrared | photo | | Staatsbetrieb Geobasisinformation und Vermessung Sachsen |
+| GEOSN-DOP-RGB | Saxony latest aerial imagery | photo | ⭐ | Staatsbetrieb Geobasisinformation und Vermessung Sachsen |
+| GEOSN-ROHDOP-RGB | Saxony raw aerial imagery | photo | ⭐ | Staatsbetrieb Geobasisinformation und Vermessung Sachsen |
+| GEOSN-WebAtlas | Saxony WebAtlasSN | map | | Staatsbetrieb Geobasisinformation und Vermessung Sachsen |
+| GEOSN-DGM-SG | Saxony shaded ground | elevation | | Staatsbetrieb Geobasisinformation und Vermessung Sachsen |
| GEOSN-DTK | Saxony topographic map | map | | Staatsbetrieb Geobasisinformation und Vermessung Sachsen |
+| LSA-DOP20 | © GeoBasis-DE/LVermGeo LSA, DOP20 | photo | ⭐ | © GeoBasis-DE/LVermGeo LSA |
| Stuttgart-latest | Stuttgart Luftbild Stadtmessungsamt | photo | ⭐ | |
| Thuringia-DOP20 | Thüringen DOP20 | photo | ⭐ | © Thüringer Landesamt für Bodenmanagement und Geoinformation - dl-de/by-2-0 (https://www.govdata.de/dl-de/by-2-0) |
| Worms-2003 | Worms 2003 | historicphoto | | © Nibelungenstadt Worms |
@@ -249,6 +257,7 @@ This table gives a summary of ids, names and other metainformation. [See the onl
| mml-orto | MML Orthophoto | photo | ⭐ | © Maanmittauslaitos |
| mml-tausta | MML Background Map | map | | © Maanmittauslaitos |
| mml-topo | MML Topographic Map | map | | © Maanmittauslaitos |
+| tampere-aerial-2022 | Tampere aerial imagery 2022 | photo | ⭐ | © City of Tampere |
| turku-orto-2018-true | City of Turku ortophoto - 2018 True ortho | photo | | © Turun kaupunki |
| turku-orto | City of Turku ortophoto - 2018 | photo | | © Turun kaupunki |
| turku-orto-2021 | City of Turku ortophoto - 2021 | photo | ⭐ | © Turun kaupunki |
@@ -257,7 +266,7 @@ This table gives a summary of ids, names and other metainformation. [See the onl
| CRAIG-Auvergne-2013 | Auvergne 2013 25cm CRAIG | historicphoto | | Orthophotographie CRAIG/Sintegra/IGN 2013 |
| CRAIG-Auvergne-2016_mirror1 | Auvergne 2016 25cm CRAIG | historicphoto | | CRAIG - IGN -TopoGEODIS - Feder Auvergne-Rhône-Alpes 2016 |
| Craig_2019 | CRAIG - 2019 | photo | | CRAIG - IGN - Feder Auvergne-Rhône-Alpes 2019 |
-| fr.ign.bdortho | BDOrtho IGN | photo | ⭐ | BDOrtho IGN |
+| fr.ign.bdortho | BD Ortho IGN | photo | ⭐ | BD Ortho IGN |
| SIBA_2018 | Bassin d Arcachon 2018 | photo | | SIBA Vues aeriennes 2018 |
| Bayonne_2016 | Bayonne 2016 | photo | | Ville de Bayonne, Communauté d'Agglomtération Pays Basque - 2016 |
| Bayonne_2019 | Bayonne - Petit et Grand Bayonne 2019 | | | Ville de Bayonne - 2019 |
@@ -283,8 +292,9 @@ This table gives a summary of ids, names and other metainformation. [See the onl
| Mulhouse_2018 | Mulhouse - 2018 | photo | | Mulhouse Alsace Agglomération 2018 |
| GrandNancy_Orthophotographie_2016 | Nancy - Orthophoto - 2016 | photo | | GrandNancy Orthophotographie 2016 |
| GrandNancy_Orthophotographie | Nancy - Orthophoto | photo | ⭐ | GrandNancy Orthophotographie 2016 |
-| fr.ign.orthoexpress.2023 | Ortho Express 2023 (20 cm) | photo | | IGN |
-| fr.ign.orthoexpress.2024 | Ortho Express 2024 (20 cm) | photo | | IGN |
+| fr.ign.orthoexpress.2023 | Ortho Express 2023 | photo | | IGN |
+| fr.ign.orthoexpress.2024 | Ortho Express 2024 | photo | | IGN |
+| fr.ign.orthoexpress.2025 | Ortho Express 2025 | photo | | IGN |
| fr.orthohr | Ortho HR | photo | | IGN, CRAIG, Mégalis Bretagne |
| fr.orthohr.2013 | Ortho HR 2013 | historicphoto | | IGN, CRAIG, Mégalis Bretagne |
| fr.orthohr.2014 | Ortho HR 2014 | historicphoto | | IGN, Mégalis Bretagne |
@@ -546,7 +556,6 @@ This table gives a summary of ids, names and other metainformation. [See the onl
| NOAA_Southeast_2025 | NOAA NGS Southeast Coast Imagery (2025) | photo | | NOAA National Geodetic Survey |
| NOAA_West_2023 | NOAA NGS West Coast Imagery (2023) | photo | | NOAA National Geodetic Survey |
| USDA-NAIP-PR | National Agriculture Imagery Program (PR/USVI) | photo | | U.S. Department of Agriculture |
-| USDA-NAIP | National Agriculture Imagery Program | photo | | U.S. Department of Agriculture |
| US_Forest_Service_roads | U.S. Forest Service roads | map | | |
| USGS-3DEP | USGS 3D Elevation Program | elevation | | |
| USGS-Imagery | USGS Imagery | photo | | |
@@ -704,7 +713,6 @@ This table gives a summary of ids, names and other metainformation. [See the onl
| Volusia_Ortho_2024 | Volusia County Orthoimagery (2024) | photo | | Volusia County Property Appraiser’s Office |
| ACC_2018 | Athens-Clarke County Imagery (2018) | historicphoto | | Athens-Clarke County GIS |
| Maui_2023 | Maui County Orthoimagery (2023) | photo | ⭐ | Maui County GIS |
-| USDA-NAIP-HI | National Agriculture Imagery Program (HI) | photo | | U.S. Department of Agriculture |
| USDA_Hawaii_2022 | USDA Hawaii Imagery (2022) | photo | | U.S. Department of Agriculture |
| USDA_Northwest_Hawaiian_Islands_2022 | USDA Northwest Hawaiian Islands Imagery (2022) | photo | | U.S. Department of Agriculture |
| Cook_IL_2020 | Cook County Orthoimagery 2020 | photo | | Cook County GIS |
@@ -767,7 +775,8 @@ This table gives a summary of ids, names and other metainformation. [See the onl
| MRCOG_Ortho_2020 | MRCOG Orthoimagery (2020) | photo | | Mid-Region Council of Governments, Bohannan Huston, Inc. |
| San_Juan_NM_2019 | San Juan County Orthoimagery (2019) | historicphoto | | San Juan County GIS |
| San_Juan_NM_2021 | San Juan County Orthoimagery (2021) | historicphoto | | San Juan County GIS |
-| San_Juan_NM_2023 | San Juan County Orthoimagery (2023) | photo | ⭐ | San Juan County GIS |
+| San_Juan_NM_2023 | San Juan County Orthoimagery (2023) | historicphoto | | San Juan County GIS |
+| San_Juan_NM_2025 | San Juan County Orthoimagery (2025) | photo | ⭐ | San Juan County GIS |
| Cattaraugus_NY_2020 | Cattaraugus County Orthoimagery (2020) | historicphoto | | Cattaraugus County Real Property Services |
| Cattaraugus_NY_2021 | Cattaraugus County Orthoimagery (2021) | historicphoto | | Cattaraugus County Real Property Services |
| Cattaraugus_NY_2023 | Cattaraugus County Orthoimagery (2023) | photo | | Cattaraugus County Real Property Services |
diff --git a/Docs/OnlineServicesOverview.md b/Docs/OnlineServicesOverview.md
new file mode 100644
index 000000000..3de1740f4
--- /dev/null
+++ b/Docs/OnlineServicesOverview.md
@@ -0,0 +1,481 @@
+[//]: # (WARNING: this file is automatically generated. Please find the sources at the bottom and edit those sources)
+
+# Overview of used online services
+
+## Table of contents
+
+1. [core](#core)
+ - [About displayed images](#about-displayed-images)
+ - [geofabrik.de](#geofabrikde)
+ - [komoot.io](#komootio)
+ - [mapcomplete.org](#mapcompleteorg)
+ - [mapillary.com](#mapillarycom)
+ - [openstreetmap.org](#openstreetmaporg)
+ - [osm.jp](#osmjp)
+ - [overpass-api.de](#overpass-apide)
+ - [panoramax.xyz](#panoramaxxyz)
+ - [private.coffee](#privatecoffee)
+ - [wikimedia.org](#wikimediaorg)
+2. [feature](#feature)
+ - [ovh.net](#ovhnet)
+ - [plantnet.org](#plantnetorg)
+ - [velopark.be](#veloparkbe)
+ - [wikidata.org](#wikidataorg)
+3. [maplayer](#maplayer)
+4. [No category](#no-category)
+
+## core
+
+20 items
+
+Core features are always active and will be contacted as soon as you visit any map
+
+### About displayed images
+
+MapComplete will read the 'image' attribute from OpenStreetMap-data when a POI is opened and will attempt to display this image (and thus download it). Those 'images' can be spread all over the internet and thus leak the IP address of the visitor
+
+### geofabrik.de
+
+Nominatim search engine endpoint, used when searching
+
+| source | license;selfhosting;more info |
+-----|----- |
+| https://geocoding.geofabrik.de/b75350b1cfc34962ac49824fe5b582dc/ | OpenData, source available, self hostable, https://wiki.openstreetmap.org/wiki/Nominatim |
+
+### komoot.io
+
+Endpoint for search with photon
+
+| source | license;selfhosting;more info |
+-----|----- |
+| https://photon.komoot.io/ | OpenData, source available, self hostable, https://wiki.openstreetmap.org/wiki/Photon |
+
+### mapcomplete.org
+
+| source | description | license;selfhosting;more info |
+-----|-----|----- |
+| https://cache.mapcomplete.org/ | This server indicates how much items there are (according to OpenStreetMap) at a given slippy tile coordinate | OpenData, source available, self hostable, |
+| https://cache.mapcomplete.org/public.{type}_{layer}/{z}/{x}/{y}.pbf | The vectortileserver is a cache of OSM data and can be used as an alternative for overpass to actually show data, esp on low zoom levels | OpenData, source available, self hostable, https://source.mapcomplete.org/MapComplete/MapComplete/src/branch/develop/Docs/SettingUpPSQL.md |
+| https://ipinfo.mapcomplete.org/ | When opening MapComplete for the first time, we try to set the map at a relevant location. For this, we try to determine the location based on the IP-address; this is done by this service. | source available, self hostable, |
+| https://report.mapcomplete.org/report | If a severe error occurs in MapComplete, this is logged on this server - this mostly concerns errors where making a change to OpenStreetMap failed. Data is handled confidentially and _only_ to replay the change and fix the root cause. | OpenData, yes, |
+| https://countrycoder.mapcomplete.org | For quite some functions, we need to know in what _country_ a feature is located. LatLon2Country is a static dataset, which, by cleverly encoding the data, can quickly tell in what country a feature is located. | OpenData, source available, self hostable, https://source.mapcomplete.org/MapComplete/latlon2country |
+| https://data.mapcomplete.org/nsi | Contains a copy and the logos of the Name Suggestion Index | |
+| https://panoramax.mapcomplete.org | The panoramax-server that MapComplete uploads to | OpenData, source available, self hostable, https://wiki.openstreetmap.org/wiki/Panoramax |
+| https://lod.mapcomplete.org/extractgraph?url={url} | This proxy queries websites to detect if they contain linked open data and gives this data back. Triggered by opening a feature | OpenData, source available, self hostable, https://source.mapcomplete.org/MapComplete/MapComplete/src/branch/develop/scripts/serverLdScrape.ts |
+
+### mapillary.com
+
+Mapillary is an online service which hosts streetview-imagery. It is used to query and show nearby images. Owned by Meta Inc. (Facebook). MapComplete does only use data, but does not recommend contributing data to Mapillary (instead, we recommend uploading to a panoramax-instance)
+
+| source | license;selfhosting;more info |
+-----|----- |
+| https://mapillary.com | OpenData, proprietary server, https://www.mapillary.com/about , https://wiki.openstreetmap.org/wiki/Mapillary |
+| https://www.mapillary.com | OpenData, proprietary server, https://www.mapillary.com/about , https://wiki.openstreetmap.org/wiki/Mapillary |
+| https://graph.mapillary.com | OpenData, proprietary server, https://www.mapillary.com/about , https://wiki.openstreetmap.org/wiki/Mapillary |
+
+### openstreetmap.org
+
+Login service, by OpenStreetMap.org
+
+| source | license;selfhosting;more info |
+-----|----- |
+| https://www.openstreetmap.org | OpenData, source available, partially - a copy can be hosted, but this would be useless, https://www.openstreetmap.org/copyright , https://www.openstreetmap.org/about , https://osmfoundation.org/wiki/Privacy_Policy |
+
+### osm.jp
+
+Overpass is a query service where OpenStreetMap-data can be retrieved. Various overpass-servers are used to query this data
+
+| source | license;selfhosting;more info |
+-----|----- |
+| https://overpass.osm.jp/api/interpreter | OpenData, source available, self hostable, https://wiki.openstreetmap.org/wiki/Overpass_turbo |
+
+### overpass-api.de
+
+Overpass is a query service where OpenStreetMap-data can be retrieved. Various overpass-servers are used to query this data
+
+| source | license;selfhosting;more info |
+-----|----- |
+| https://overpass-api.de/api/interpreter | OpenData, source available, self hostable, https://wiki.openstreetmap.org/wiki/Overpass_turbo |
+
+### panoramax.xyz
+
+The federation instance of Panoramax, which knows all panoramax-servers and can query all servers
+
+| source | license;selfhosting;more info |
+-----|----- |
+| https://panoramax.xyz | OpenData, source available, self hostable, https://wiki.openstreetmap.org/wiki/Panoramax |
+
+### private.coffee
+
+Overpass is a query service where OpenStreetMap-data can be retrieved. Various overpass-servers are used to query this data
+
+| source | license;selfhosting;more info |
+-----|----- |
+| https://overpass.private.coffee/api/interpreter | OpenData, source available, self hostable, https://wiki.openstreetmap.org/wiki/Overpass_turbo |
+
+### wikimedia.org
+
+Wikimedia contains various images
+
+| source | license;selfhosting;more info |
+-----|----- |
+| https://commons.wikimedia.org/wiki/ | OpenData, source available, self hostable, |
+| https://upload.wikimedia.org | OpenData, source available, self hostable, |
+
+## feature
+
+15 items
+
+These are only enabled for certain maps or certain features
+
+### ovh.net
+
+The image data store for a French Panoramax-server. As the photosphere fetches this, must be listed in the CSP
+
+| source | license;selfhosting;more info |
+-----|----- |
+| https://panoramax-storage-public-fast.s3.gra.perf.cloud.ovh.net | OpenData, source available, self hostable, http://panoramax.fr/ |
+
+### plantnet.org
+
+Planet provides an API that, based on images, detects a plant species
+
+| source | license;selfhosting;more info |
+-----|----- |
+| https://my-api.plantnet.org/v2/identify/all?api-key=2b10AAsjzwzJvucA5Ncm5qxe | OpenData, unknown, https://plantnet.org , https://github.com/plantnet |
+
+### velopark.be
+
+Only needed for the velopark-theme
+
+| source | license;selfhosting;more info |
+-----|----- |
+| https://data.velopark.be | OpenData, |
+
+### wikidata.org
+
+MapComplete checks wikidata to find images to show and displays information from wikidata on some objects (e.g. it shows basic information on artwork, etymology, ...)
+
+| source | license;selfhosting;more info |
+-----|----- |
+| https://www.wikidata.org/ | OpenData, source available, yes, but useless due to network effects, |
+| https://wikidata.org/ | OpenData, source available, yes, but useless due to network effects, |
+| https://query.wikidata.org | OpenData, source available, yes, but useless due to network effects, |
+| https://m.wikidata.org | OpenData, source available, yes, but useless due to network effects, |
+| https://www.wikidata.org/ | OpenData, source available, yes, but useless due to network effects, |
+| https://wikidata.org/ | OpenData, source available, yes, but useless due to network effects, |
+| https://query.wikidata.org | OpenData, source available, yes, but useless due to network effects, |
+| https://m.wikidata.org | OpenData, source available, yes, but useless due to network effects, |
+| https://www.wikidata.org/ | OpenData, source available, yes, but useless due to network effects, |
+| https://wikidata.org/ | OpenData, source available, yes, but useless due to network effects, |
+| https://query.wikidata.org | OpenData, source available, yes, but useless due to network effects, |
+| https://m.wikidata.org | OpenData, source available, yes, but useless due to network effects, |
+
+## maplayer
+
+1055 items
+
+MapLayers are integrated from the [Editor Layer Index](https://github.com/osmlab/editor-layer-index). A map layer listed here will only be contacted if the user decides to use this map background. This list changes over time, as new background layers are published and old background layers go offline. For all map layers, we have permission to use them to improve OpenStreetMap
+
+A full listing can be found in [ELI-overview](ELI-overview.md)
+
+ - admin.ch
+ - ak.us
+ - allencountyohio.com
+ - amazonaws.com
+ - americanamap.org
+ - ancgis.com
+ - aragon.es
+ - arapahoegov.com
+ - arcgis.com
+ - arcgisonline.com
+ - arkansas.gov
+ - b-cdn.net
+ - bceo.org
+ - bcpa.net
+ - be.ch
+ - bern.ch
+ - bologna.it
+ - bozeman.net
+ - brest-metropole.fr
+ - brevardfl.gov
+ - bs.ch
+ - burbankca.gov
+ - bytom.pl
+ - bz.it
+ - ca.gov
+ - ca.us
+ - caib.es
+ - calaverascounty.gov
+ - cartocdn.com
+ - cattco.org
+ - charlottecountyfl.gov
+ - chronica.md
+ - citruspa.org
+ - citymb.info
+ - cityofberkeley.info
+ - cityofirvine.org
+ - cityofmarcoisland.com:6443
+ - cnv.at
+ - codefor.de
+ - coj.net
+ - colliercountyfl.gov
+ - columbus.gov
+ - com.ua
+ - cookcountyil.gov
+ - craig.fr
+ - ctmetro.org
+ - cuyahogacounty.us
+ - cuzk.cz
+ - cycle.travel
+ - datafordeler.dk
+ - dataforsyningen.dk
+ - datagrandest.fr
+ - dc.gov
+ - delaware.gov
+ - dgu.hr
+ - erlangen.de
+ - euskadi.eus
+ - fairfaxcounty.gov
+ - fairfield-city.org
+ - fau.de
+ - fl.gov
+ - fl.us
+ - fnsb.gov
+ - frankfurt.de
+ - freemap.sk
+ - gc.ca
+ - gdi-niederrhein-geodienste.de
+ - ge.ch
+ - geo.ca
+ - geobasis-bb.de
+ - geonorge.no
+ - geopf.fr
+ - geoportail.lu
+ - geoportal-th.de
+ - geoportal-worms.de
+ - geoportal2.pl
+ - geosphere.fr
+ - github.io
+ - gliwice.eu
+ - gob.ar
+ - goteborg.se
+ - gouv.fr
+ - gov.al
+ - gov.au
+ - gov.br
+ - gov.cz
+ - gov.lv
+ - gov.pl
+ - gov.pt
+ - gov.sg
+ - gov.tw
+ - gov.ua
+ - gov.uk
+ - gov.za
+ - govt.nz
+ - grafcan.es
+ - grandlyon.com
+ - greenecountyohio.gov
+ - gv.at
+ - hamburg.de
+ - hennepin.us
+ - henrico.us
+ - hessen.de
+ - hilliardohio.gov
+ - hillsboroughcounty.org
+ - hsy.fi
+ - icgc.cat
+ - ide.uy
+ - idee.es
+ - in.gov
+ - intrinsical.de
+ - irisnet.be
+ - kalmar.se
+ - kansasgis.org
+ - kapsi.fi
+ - kingcounty.gov
+ - kms.dk
+ - kontur.io
+ - ky.gov
+ - lacounty.gov
+ - lantmateriet.se
+ - leepa.org
+ - level2.si
+ - lgl-bw.de
+ - lickingcounty.gov
+ - lidingo.se
+ - lodz.pl
+ - losalamosnm.us
+ - ludwigshafen.de
+ - lvm.lv
+ - maaamet.ee
+ - madrid.es
+ - maine.gov
+ - mainz.de
+ - makina-corpus.net
+ - mansfieldcity.com
+ - mapbox.com
+ - mapcomplete.org
+ - mapwarper.net
+ - marionfl.org
+ - matsugov.us
+ - mcohio.org
+ - md.gov
+ - mecklenburgcountync.gov
+ - meh.es
+ - mercercountyohio.org
+ - mesacounty.us
+ - metropoleruhr.de
+ - miamidade.gov
+ - mil.br
+ - mn.gov
+ - mn.us
+ - modestogov.com
+ - monroecounty-fl.gov
+ - morgantownwv.gov
+ - muenchen.de
+ - mymanatee.org
+ - myokaloosa.com
+ - naplesgov.com
+ - nassauflpa.com
+ - nationalmap.gov
+ - navarra.es
+ - nconemap.gov
+ - ne.ch
+ - nj.gov
+ - nls.uk
+ - noaa.gov
+ - nrw.de
+ - ny.gov
+ - oakgov.com
+ - ocfl.net
+ - ocgis.com
+ - oh.us
+ - ohio.gov
+ - on.ca
+ - openaerialmap.org
+ - openstreetmap.bzh
+ - openstreetmap.de
+ - openstreetmap.fr
+ - openstreetmap.hu
+ - openstreetmap.lu
+ - openstreetmap.org
+ - openstreetmap.rs
+ - or.us
+ - oregon.gov
+ - oregonexplorer.info
+ - org.br
+ - org.mx
+ - org.uk
+ - org.za
+ - orka-mv.de
+ - osceola.org
+ - osm-hr.org
+ - osm-tools.org
+ - osm.ch
+ - osmf.jp
+ - ourmap.us
+ - pagis.org
+ - pdok.nl
+ - pinellas.gov
+ - polk-county.net
+ - portlandmaps.com
+ - praha.eu
+ - protomaps.com
+ - psu.edu
+ - putnamcountygis.com
+ - rennesmetropole.fr
+ - roktech.net
+ - saarland.de
+ - saccounty.gov
+ - sachsen-anhalt.de
+ - sachsen.de
+ - sanjuancountywa.gov
+ - sbcounty.gov
+ - sccgov.org
+ - scgov.net
+ - sciotocountyengineer.org
+ - seminolecountyfl.gov:6443
+ - septima.dk
+ - servizirl.it
+ - sf.gov
+ - sg.ch
+ - sh.ch
+ - sjcfl.us
+ - sjcounty.net
+ - smcgov.org
+ - snoco.org
+ - so.ch
+ - sollentuna.se
+ - srcity.org
+ - stadiamaps.com
+ - stadt-zuerich.ch
+ - staedteregion-aachen.de
+ - statkart.no
+ - stockholm.se
+ - stocktonca.gov
+ - stuttgart.de
+ - tampere.fi
+ - tg.ch
+ - tn.gov
+ - tnris.org
+ - torokbalint.hu
+ - toronto.ca
+ - townlands.ie
+ - turku.fi
+ - uconn.edu
+ - ujbuda.hu
+ - unh.edu
+ - unm.edu
+ - uri.edu
+ - usda.gov
+ - vcgov.org
+ - vermont.gov
+ - virginia.gov
+ - virtualearth.net
+ - vlaanderen.be
+ - wallonie.be
+ - webatlas.no
+ - wi.gov
+ - woodcountyohio.gov
+ - wroc.pl
+ - zby.cz
+ - zg.ch
+ - zh.ch
+
+## No category
+
+27 items
+
+ - https://www.openstreetmap.org
+ - https://api.openstreetmap.org
+ - https://panoramax.mapcomplete.org
+ - https://api.flickr.com
+ - https://api.imgur.com/3/image
+ - https://i.imgur.com
+ - *.fbcdn.net
+ - https://graph.mapillary.com
+ - https://panoramax.openstreetmap.fr
+ - https://api.panoramax.xyz
+ - https://panoramax.mapcomplete.org
+ - https://api.imgur.com/3/image
+ - https://panoramax.mapcomplete.org
+ - https://www.openstreetmap.org
+ - https://www.openstreetmap.org
+ - https://www.openstreetmap.org
+ - https://api.imgur.com/3/image
+ - https://i.imgur.com
+ - https://www.openstreetmap.org
+ - https://www.openstreetmap.org
+ - https://api.mangrove.reviews
+ - https://api.mangrove.reviews
+ - https://api.mangrove.reviews
+ - https://maproulette.org/api/v2
+ - *.wikipedia.org
+ - https://countrycoder.mapcomplete.org
+ - https://www.openstreetmap.org
+
+
+
+This document is autogenerated from [src/Models/SourceOverview.ts](https://source.mapcomplete.org/MapComplete/MapComplete/src/branch/develop/src/Models/SourceOverview.ts)
diff --git a/Docs/SpecialRenderings.md b/Docs/SpecialRenderings.md
index 9cda1c239..559da31a9 100644
--- a/Docs/SpecialRenderings.md
+++ b/Docs/SpecialRenderings.md
@@ -302,7 +302,7 @@ Gives an interactive element which shows a tag comparison between the OSM-object
| host | _undefined_ | The domain name(s) where data might be fetched from - this is needed to set the CSP. A domain must include 'https', e.g. 'https://example.com'. For multiple domains, separate them with ';'. If you don't know the possible domains, use '*'. |
| readonly | _undefined_ | If 'yes', will not show 'apply'-buttons |
-Defined in [/src/UI/SpecialVisualisations/DataImportSpecialVisualisations.ts#L239](/src/UI/SpecialVisualisations/DataImportSpecialVisualisations.ts#L239)
+Defined in [/src/UI/SpecialVisualisations/DataImportSpecialVisualisations.ts#L255](/src/UI/SpecialVisualisations/DataImportSpecialVisualisations.ts#L255)
#### Example usage of compare_data
@@ -509,7 +509,7 @@ Attempts to load (via a proxy) the specified website and parsed ld+json from the
| mode | _undefined_ | If `display`, only show the data in tabular and readonly form, ignoring already existing tags. This is used to explicitly show all the tags. If unset or anything else, allow to apply/import on OSM |
| collapsed | yes | If the containing accordion should be closed |
-Defined in [/src/UI/SpecialVisualisations/DataImportSpecialVisualisations.ts#L102](/src/UI/SpecialVisualisations/DataImportSpecialVisualisations.ts#L102)
+Defined in [/src/UI/SpecialVisualisations/DataImportSpecialVisualisations.ts#L103](/src/UI/SpecialVisualisations/DataImportSpecialVisualisations.ts#L103)
#### Example usage of linked_data_from_website
@@ -528,7 +528,7 @@ Change the status of the given MapRoulette task
| maproulette_id | mr_taskId | The property name containing the maproulette id |
| ask_feedback | _empty string_ | If not an empty string, this will be used as question to ask some additional feedback. A text field will be added |
-Defined in [/src/UI/SpecialVisualisations/DataImportSpecialVisualisations.ts#L19](/src/UI/SpecialVisualisations/DataImportSpecialVisualisations.ts#L19)
+Defined in [/src/UI/SpecialVisualisations/DataImportSpecialVisualisations.ts#L20](/src/UI/SpecialVisualisations/DataImportSpecialVisualisations.ts#L20)
#### Example usage of maproulette_set_status
@@ -558,7 +558,7 @@ Sends the images linked to the current object to plantnet.org and asks it what p
-----|-----|----- |
| image_key | image,mapillary,image,wikidata,wikimedia_commons,image,panoramax,image,image | The keys given to the images, e.g. if image is given, the first picture URL will be added as image, the second as image:0, the third as image:1, etc... Multiple values are allowed if ';'-separated |
-Defined in [/src/UI/Popup/PlantNetDetectionViz.ts#L13](/src/UI/Popup/PlantNetDetectionViz.ts#L13)
+Defined in [/src/UI/Popup/PlantNetDetectionViz.ts#L14](/src/UI/Popup/PlantNetDetectionViz.ts#L14)
#### Example usage of plantnet_detection
@@ -1086,7 +1086,7 @@ A collapsable group (accordion)
| labels | _undefined_ | A `;`-separated list of either identifiers or label names. All tagRenderings matching this value will be shown in the accordion |
| blacklist | _undefined_ | A `;`-separated list of either identifiers or label names. Matching tagrenderings will _not_ be included, even if they are in `labels` |
-Defined in [/src/UI/SpecialVisualisations/TagrenderingManipulationSpecialVisualisations.ts#L174](/src/UI/SpecialVisualisations/TagrenderingManipulationSpecialVisualisations.ts#L174)
+Defined in [/src/UI/SpecialVisualisations/TagrenderingManipulationSpecialVisualisations.ts#L175](/src/UI/SpecialVisualisations/TagrenderingManipulationSpecialVisualisations.ts#L175)
#### Example usage of group
@@ -1102,7 +1102,7 @@ Given an embedded tagRendering (read only) and a key, will read the keyname as a
| tagrendering | _undefined_ | An entire tagRenderingConfig |
| classes | _undefined_ | CSS-classes to apply on every individual item. Seperated by `space` |
-Defined in [/src/UI/SpecialVisualisations/TagrenderingManipulationSpecialVisualisations.ts#L90](/src/UI/SpecialVisualisations/TagrenderingManipulationSpecialVisualisations.ts#L90)
+Defined in [/src/UI/SpecialVisualisations/TagrenderingManipulationSpecialVisualisations.ts#L91](/src/UI/SpecialVisualisations/TagrenderingManipulationSpecialVisualisations.ts#L91)
#### Example usage of multi
@@ -1124,7 +1124,7 @@ Defined in [/src/UI/SpecialVisualisations/TagrenderingManipulationSpecialVisuali
Opens the current view in the iD-editor
-Defined in [/src/UI/SpecialVisualisations/TagrenderingManipulationSpecialVisualisations.ts#L216](/src/UI/SpecialVisualisations/TagrenderingManipulationSpecialVisualisations.ts#L216)
+Defined in [/src/UI/SpecialVisualisations/TagrenderingManipulationSpecialVisualisations.ts#L217](/src/UI/SpecialVisualisations/TagrenderingManipulationSpecialVisualisations.ts#L217)
#### Example usage of open_in_iD
@@ -1134,7 +1134,7 @@ Defined in [/src/UI/SpecialVisualisations/TagrenderingManipulationSpecialVisuali
Opens the current view in the JOSM-editor
-Defined in [/src/UI/SpecialVisualisations/TagrenderingManipulationSpecialVisualisations.ts#L230](/src/UI/SpecialVisualisations/TagrenderingManipulationSpecialVisualisations.ts#L230)
+Defined in [/src/UI/SpecialVisualisations/TagrenderingManipulationSpecialVisualisations.ts#L231](/src/UI/SpecialVisualisations/TagrenderingManipulationSpecialVisualisations.ts#L231)
#### Example usage of open_in_josm
@@ -1149,7 +1149,7 @@ Shows a tagRendering from a different object as if this was the object itself
| featureId | _undefined_ | The key of the attribute which contains the id of the feature from which to use the tags |
| tagRenderingId | _undefined_ | The layer-id and tagRenderingId to render. Can be multiple value if ';'-separated (in which case every value must also contain the layerId, e.g. `layerId.tagRendering0; layerId.tagRendering1`). Note: this can cause layer injection |
-Defined in [/src/UI/SpecialVisualisations/TagrenderingManipulationSpecialVisualisations.ts#L17](/src/UI/SpecialVisualisations/TagrenderingManipulationSpecialVisualisations.ts#L17)
+Defined in [/src/UI/SpecialVisualisations/TagrenderingManipulationSpecialVisualisations.ts#L18](/src/UI/SpecialVisualisations/TagrenderingManipulationSpecialVisualisations.ts#L18)
#### Example usage of steal
diff --git a/Docs/URL_Parameters.md b/Docs/URL_Parameters.md
index 214b17bf7..a820096b2 100644
--- a/Docs/URL_Parameters.md
+++ b/Docs/URL_Parameters.md
@@ -438,7 +438,7 @@ The default value is _false_
The mode the application starts in, e.g. 'map', 'dashboard' or 'statistics'
-This documentation is defined in the source code at [generateDocs.ts](ervdvn/git/MapComplete/scripts/generateDocs.ts#L468)
+This documentation is defined in the source code at [generateDocs.ts](ervdvn/git/MapComplete/scripts/generateDocs.ts#L470)
The default value is _map_
diff --git a/scripts/SourceOverview.ts b/scripts/SourceOverview.ts
deleted file mode 100644
index a77591225..000000000
--- a/scripts/SourceOverview.ts
+++ /dev/null
@@ -1,168 +0,0 @@
-import { RasterLayerProperties } from "../src/Models/RasterLayerProperties"
-import { AvailableRasterLayers, RasterLayerPolygon } from "../src/Models/RasterLayers"
-import { Utils } from "../src/Utils"
-import * as eli from "../public/assets/data/editor-layer-index.json"
-import * as layers_global from "../src/assets/global-raster-layers.json"
-import eli_global from "../src/assets/generated/editor-layer-index-global.json"
-import bing from "../src/assets/bing.json"
-import Constants from "../src/Models/Constants"
-import ThemeConfig from "../src/Models/ThemeConfig/ThemeConfig"
-import { ThemeConfigJson } from "../src/Models/ThemeConfig/Json/ThemeConfigJson"
-import SpecialVisualizations from "../src/UI/SpecialVisualizations"
-import ValidationUtils from "../src/Models/ThemeConfig/Conversion/ValidationUtils"
-import { QuestionableTagRenderingConfigJson } from "../src/Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson"
-import { LayerConfigJson } from "../src/Models/ThemeConfig/Json/LayerConfigJson"
-import { ImmutableStore } from "../src/Logic/UIEventSource"
-
-export interface SourceInfo {
- host: string
-}
-
-/**
- * Generates what URLs the webapp might access, and why
- */
-export class SourceOverview {
- private eliUrlsCached: string[]
-
- public async getOverview( layout: ThemeConfig,
- layoutJson: ThemeConfigJson,
- options: {
- scriptSrcs: string[]
- }): Promise {
- const apiUrls: string[] = [
- ...Constants.allServers,
- "https://www.openstreetmap.org",
- "https://api.openstreetmap.org",
- "https://pietervdvn.goatcounter.com",
- "https://api.panoramax.xyz",
- "https://panoramax.mapcomplete.org",
- "https://data.velopark.be",
- "https://data.mapcomplete.org",
- ].concat(...(await this.eliUrls()))
-
- for (const sv of SpecialVisualizations.specialVisualizations) {
- if (typeof sv.needsUrls === "function") {
- // Handled below
- continue
- }
- apiUrls.push(...(sv.needsUrls ?? []))
- }
-
-
- const usedSpecialVisualisations = [].concat(
- ...layoutJson.layers.map((l) =>
- ValidationUtils.getAllSpecialVisualisations(
- (l).tagRenderings ?? []
- )
- )
- )
- for (const usedSpecialVisualisation of usedSpecialVisualisations) {
- if (typeof usedSpecialVisualisation === "string") {
- continue
- }
- const neededUrls = usedSpecialVisualisation.func.needsUrls ?? []
- if (typeof neededUrls === "function") {
- let needed: string | string[] = neededUrls(usedSpecialVisualisation.args)
- if (typeof needed === "string") {
- needed = [needed]
- }
- apiUrls.push(...needed)
- }
- }
-
- apiUrls.push("https://schema.org")
- const eliLayers: RasterLayerPolygon[] = AvailableRasterLayers.layersAvailableAt(
- new ImmutableStore({ lon: 0, lat: 0 })
- ).store.data
- {
- const vectorLayers = eliLayers.filter((l) => l.properties.type === "vector")
- const vectorSources = vectorLayers.map((l) => l.properties.url)
- vectorSources.push(...vectorLayers.map((l) => l.properties.style))
- apiUrls.push(
- ...vectorSources.map((url) => {
- if (url?.startsWith("pmtiles://")) {
- return url.substring("pmtiles://".length)
- }
- return url
- })
- )
- }
- const geojsonSources: string[] = layout.layers.map((l) => l.source?.geojsonSource)
-
- return apiUrls.concat(...geojsonSources)
-
- }
-
- private async eliUrls(): Promise {
- if (this.eliUrlsCached) {
- return this.eliUrlsCached
- }
- const urls: string[] = []
- const regex = /{switch:([^}]+)}/
- const rasterLayers: { properties: RasterLayerProperties }[] = [
- AvailableRasterLayers.defaultBackgroundLayer,
- ...eli.features,
- bing,
- ...eli_global.map((properties) => ({ properties })),
- ...layers_global.layers.map((properties) => ({ properties })),
- ]
- for (const feature of rasterLayers) {
- const f = feature
- const url = f.properties.url
- const match = url.match(regex)
- if (match) {
- const domains = match[1].split(",")
- const subpart = match[0]
- urls.push(...domains.map((d) => url.replace(subpart, d)))
- } else {
- urls.push(url)
- }
-
- if (f.properties.type === "vector") {
- // We also need to whitelist eventual sources
- let url = f.properties.url
- urls.push(...(f.properties["connect-src"] ?? []))
- if (url.startsWith("pmtiles://")) {
- url = url.substring("pmtiles://".length)
- }
- if (url.endsWith(".pmtiles")) {
- continue
- }
- try {
- console.log("Downloading ", url)
- const styleSpec = await Utils.downloadJsonCached(url, 1000 * 120, {
- Origin: "https://mapcomplete.org",
- })
- for (const key of Object.keys(styleSpec?.["sources"] ?? {})) {
- const url = styleSpec["sources"][key].url
- if (!url) {
- continue
- }
- let urlClipped = url
- if (url.indexOf("?") > 0) {
- urlClipped = url?.substring(0, url.indexOf("?"))
- }
- console.log("Source url ", key, url)
- urls.push(url)
- if (urlClipped.endsWith(".json")) {
- const tileInfo = await Utils.downloadJsonCached(url, 1000 * 120, {
- Origin: "https://mapcomplete.org",
- })
- urls.push(tileInfo["tiles"] ?? [])
- }
- }
- urls.push(...(styleSpec["tiles"] ?? []))
- urls.push(styleSpec["sprite"])
- urls.push(styleSpec["glyphs"])
- } catch (e) {
- console.error(
- "ERROR: could not download a resource, some sprites might not be whitelisted and thus not load"
- )
- }
- }
- }
- this.eliUrlsCached = urls
- return Utils.NoNull(urls).sort()
- }
-
-}
diff --git a/scripts/generateDocs.ts b/scripts/generateDocs.ts
index ac4f7aa6f..0423d3a5a 100644
--- a/scripts/generateDocs.ts
+++ b/scripts/generateDocs.ts
@@ -33,6 +33,7 @@ import { AvailableRasterLayers } from "../src/Models/RasterLayers"
import { ImmutableStore } from "../src/Logic/UIEventSource"
import * as unitUsage from "../Docs/Schemas/UnitConfigJson.schema.json"
import { ThemeConfigJson } from "../src/Models/ThemeConfig/Json/ThemeConfigJson"
+import { ServerSourceInfo, SourceOverview } from "../src/Models/SourceOverview"
/**
* Converts a markdown-file into a .json file, which a walkthrough/slideshow element can use
@@ -169,6 +170,7 @@ export class GenerateDocs extends Script {
this.generateOverviewsForAllSingleLayer()
this.generateLayerOverviewText()
this.generateBuiltinUnits()
+ await this.generateSourcesOverview()
Array.from(AllKnownLayouts.allKnownLayouts.values()).map((theme) => {
this.generateForTheme(theme)
@@ -189,7 +191,7 @@ export class GenerateDocs extends Script {
"src/Logic/Osm/Changes.ts",
"src/Logic/Osm/ChangesetHandler.ts",
])
- const eli = await AvailableRasterLayers.editorLayerIndex()
+ const eli = AvailableRasterLayers.editorLayerIndex()
this.WriteMarkdownFile(
"./Docs/ELI-overview.md",
[
@@ -536,6 +538,106 @@ export class GenerateDocs extends Script {
)
}
+ private async generateSourcesOverview() {
+ console.log("Generating the sources overview - this might take a bit")
+ const sources = await new SourceOverview().getOverview()
+ const md = [
+ "# Overview of used online services",
+ ]
+
+ const serverInfos = sources.filter(s => typeof s !== "string")
+ .filter(item => typeof item === "string" || item.url.startsWith("https://") || item.url.startsWith("pmtiles://"))
+ const titles = Utils.Dedup(Utils.NoEmpty(serverInfos.map(s => s.category)))
+ titles.sort()
+
+ function getHost(item: ServerSourceInfo) {
+ let url = item.url
+ if (url.startsWith("pmtiles://")) {
+ url = url.slice("pmtiles://".length)
+ }
+ const host = new URL(url).host.split(".")
+
+ return (host.at(-2) + "." + host.at(-1)).toLowerCase()
+ }
+
+ const categoryExplanation: Record = {
+ core: ["Core features are always active and will be contacted as soon as you visit any map",
+ "### About displayed images",
+ "MapComplete will read the 'image' attribute from OpenStreetMap-data when a POI is opened and will attempt to display this image (and thus download it). Those 'images' can be spread all over the internet and thus leak the IP address of the visitor",
+ ].join("\n\n"),
+ feature: "These are only enabled for certain maps or certain features",
+ maplayer: ["MapLayers are integrated from the [Editor Layer Index](https://github.com/osmlab/editor-layer-index). A map layer listed here will only be contacted if the user decides to use this map background. This list changes over time, as new background layers are published and old background layers go offline. For all map layers, we have permission to use them to improve OpenStreetMap",
+ "A full listing can be found in [ELI-overview](ELI-overview.md)",
+ ].join("\n\n"),
+ }
+
+ for (const title of titles) {
+ md.push("## " + title)
+ const items = serverInfos.filter(info => info.category.toLowerCase() === title.toLowerCase())
+ md.push(items.length + " items")
+ md.push(categoryExplanation[title])
+
+ const hosts = Utils.Dedup(items.map(getHost))
+ hosts.sort()
+ if (title === "maplayer") {
+ md.push(MarkdownUtils.list(hosts))
+ continue
+ }
+
+ for (const host of hosts) {
+ md.push("### " + host)
+ const itemsForHost = items.filter(info => getHost(info) === host)
+ const identicalDescription = itemsForHost.every(item => item.description === itemsForHost[0].description)
+ if (identicalDescription) {
+ md.push(itemsForHost[0].description)
+ }
+ const table = MarkdownUtils.table(
+ ["source", "description", "license;selfhosting;more info"],
+ itemsForHost.map(item => {
+ let selfHostable = ""
+ if (item.selfhostable) {
+ if (typeof item.selfhostable === "string") {
+ selfHostable = item.selfhostable
+ } else {
+ selfHostable = "self hostable"
+ }
+ }
+ let sourceAvailable = ""
+ if (item.sourceAvailable) {
+ if (typeof item.sourceAvailable === "string") {
+ sourceAvailable = item.sourceAvailable
+ } else {
+ sourceAvailable = "source available"
+ }
+ }
+ return [
+ item.url,
+ identicalDescription ? "" : item.description,
+ Utils.NoEmpty([(item.openData ? "OpenData" : ""),
+ sourceAvailable,
+ selfHostable,
+ item.moreInfo?.join(" , "),
+ ]).join(", "),
+ ]
+ }),
+ {
+ dropEmptyColumns: true,
+ },
+ )
+ md.push(table)
+ }
+
+
+ }
+
+ md.push("## No category")
+ const urls: string[] = sources.filter(s => typeof s === "string")
+ md.push(urls.length + " items")
+ md.push(MarkdownUtils.list(urls))
+
+ this.WriteMarkdownFile("./Docs/OnlineServicesOverview.md", md.join("\n\n"), ["src/Models/SourceOverview.ts"], { tocMaxDepth: 2 })
+ }
+
/**
* Generates the documentation for the layers overview page
* @constructor
diff --git a/scripts/generateLayouts.ts b/scripts/generateLayouts.ts
index 8f1813d39..9ff255a4d 100644
--- a/scripts/generateLayouts.ts
+++ b/scripts/generateLayouts.ts
@@ -10,7 +10,7 @@ import { Utils } from "../src/Utils"
import Constants from "../src/Models/Constants"
import Script from "./Script"
import crypto from "crypto"
-import { SourceOverview } from "./SourceOverview"
+import { SourceOverview } from "../src/Models/SourceOverview"
const sharp = require("sharp")
@@ -260,7 +260,8 @@ class GenerateLayouts extends Script {
scriptSrcs: string[]
}
): Promise {
- const urls = await this.sourceOverview.getOverview(layout, layoutJson, options)
+ const info = await this.sourceOverview.getOverview(layout, layoutJson)
+ const urls = info.map(info => info["url"] ?? info)
const hosts = new Set()
for (let connectSource of urls) {
diff --git a/src/Logic/Actors/BackgroundLayerResetter.ts b/src/Logic/Actors/BackgroundLayerResetter.ts
index 66343533a..0b59c79a3 100644
--- a/src/Logic/Actors/BackgroundLayerResetter.ts
+++ b/src/Logic/Actors/BackgroundLayerResetter.ts
@@ -26,7 +26,6 @@ export default class BackgroundLayerResetter {
(global) => global.properties.id !== l.properties.id
)
) {
- await AvailableRasterLayers.editorLayerIndex()
BackgroundLayerResetter.installHandler(
currentBackgroundLayer,
availableLayers.store
diff --git a/src/Logic/Actors/PreferredRasterLayerSelector.ts b/src/Logic/Actors/PreferredRasterLayerSelector.ts
index 2e4713aeb..ce83a9bac 100644
--- a/src/Logic/Actors/PreferredRasterLayerSelector.ts
+++ b/src/Logic/Actors/PreferredRasterLayerSelector.ts
@@ -72,7 +72,6 @@ export class PreferredRasterLayerSelector {
this._rasterLayerSetting.setData(global)
return
}
- await AvailableRasterLayers.editorLayerIndex()
const isCategory = eliCategory.indexOf(targetLayerId) >= 0
const available = this._availableLayers.store.data
const foundLayer = isCategory
diff --git a/src/Logic/ImageProviders/AllImageProviders.ts b/src/Logic/ImageProviders/AllImageProviders.ts
index 390d13dc9..7e324c439 100644
--- a/src/Logic/ImageProviders/AllImageProviders.ts
+++ b/src/Logic/ImageProviders/AllImageProviders.ts
@@ -7,6 +7,7 @@ import ImageProvider, { ProvidedImage } from "./ImageProvider"
import { WikidataImageProvider } from "./WikidataImageProvider"
import Panoramax from "./Panoramax"
import { Utils } from "../../Utils"
+import { ServerSourceInfo } from "../../Models/SourceOverview"
/**
* A generic 'from the interwebz' image picker, without attribution
@@ -33,9 +34,17 @@ export default class AllImageProviders {
Panoramax.singleton,
AllImageProviders.genericImageProvider,
]
- public static apiUrls: string[] = [].concat(
+ public static apiUrls: (string | ServerSourceInfo)[] = [].concat(
...AllImageProviders.imageAttributionSources.map((src) => src.apiUrls()),
- "https://panoramax-storage-public-fast.s3.gra.perf.cloud.ovh.net"
+ {
+ url: "https://panoramax-storage-public-fast.s3.gra.perf.cloud.ovh.net",
+ category: "feature",
+ description: "The image data store for a French Panoramax-server. As the photosphere fetches this, must be listed in the CSP",
+ moreInfo: ["http://panoramax.fr/"],
+ selfhostable: true,
+ openData: true,
+ sourceAvailable: true,
+ },
)
public static defaultKeys: string[] = [].concat(
...AllImageProviders.imageAttributionSources.map((provider) => provider.defaultKeyPrefixes)
diff --git a/src/Logic/ImageProviders/ImageProvider.ts b/src/Logic/ImageProviders/ImageProvider.ts
index ab3519651..e894eb7f5 100644
--- a/src/Logic/ImageProviders/ImageProvider.ts
+++ b/src/Logic/ImageProviders/ImageProvider.ts
@@ -3,6 +3,7 @@ import BaseUIElement from "../../UI/BaseUIElement"
import { LicenseInfo } from "./LicenseInfo"
import { Utils } from "../../Utils"
import { Feature, Point } from "geojson"
+import { ServerSourceInfo } from "../../Models/SourceOverview"
export interface ProvidedImage {
url: string
@@ -126,7 +127,7 @@ export default abstract class ImageProvider {
public abstract DownloadAttribution(providedImage: { id: string }): Promise
- public abstract apiUrls(): string[]
+ public abstract apiUrls(): (string | ServerSourceInfo)[]
/**
* URL to visit the image on the original website
diff --git a/src/Logic/ImageProviders/Mapillary.ts b/src/Logic/ImageProviders/Mapillary.ts
index 811d6a93c..08c1db659 100644
--- a/src/Logic/ImageProviders/Mapillary.ts
+++ b/src/Logic/ImageProviders/Mapillary.ts
@@ -7,6 +7,7 @@ import SvelteUIElement from "../../UI/Base/SvelteUIElement"
import MapillaryIcon from "./MapillaryIcon.svelte"
import { Feature, Point } from "geojson"
import { Store, UIEventSource } from "../UIEventSource"
+import { ServerSourceInfo } from "../../Models/SourceOverview"
export class Mapillary extends ImageProvider {
public static readonly singleton = new Mapillary()
@@ -117,8 +118,17 @@ export class Mapillary extends ImageProvider {
return undefined
}
- apiUrls(): string[] {
- return ["https://mapillary.com", "https://www.mapillary.com", "https://graph.mapillary.com"]
+ apiUrls(): ServerSourceInfo[] {
+ return ["https://mapillary.com", "https://www.mapillary.com", "https://graph.mapillary.com"].map(
+ url => ({
+ url,
+ category: "core",
+ trigger: ["always"],
+ sourceAvailable: "proprietary server", selfhostable: false, openData: true,
+ description: "Mapillary is an online service which hosts streetview-imagery. It is used to query and show nearby images. Owned by Meta Inc. (Facebook). MapComplete does only use data, but does not recommend contributing data to Mapillary (instead, we recommend uploading to a panoramax-instance)",
+ moreInfo: ["https://www.mapillary.com/about", "https://wiki.openstreetmap.org/wiki/Mapillary"],
+ }),
+ )
}
SourceIcon(
@@ -143,8 +153,6 @@ export class Mapillary extends ImageProvider {
/**
* Download data necessary for the 360°-viewer
- * @param pkey
- * @constructor
*/
public async getPanoramaInfo(image: {
id: number | string
@@ -171,7 +179,7 @@ export class Mapillary extends ImageProvider {
url: response.thumb_2048_url,
northOffset: response.computed_compass_angle,
provider: this,
- imageMeta: image,
+ imageMeta: image,
},
}
}
diff --git a/src/Logic/ImageProviders/Panoramax.ts b/src/Logic/ImageProviders/Panoramax.ts
index b66a8278d..511d55d41 100644
--- a/src/Logic/ImageProviders/Panoramax.ts
+++ b/src/Logic/ImageProviders/Panoramax.ts
@@ -12,6 +12,7 @@ import Panoramax_bw from "../../assets/svg/Panoramax_bw.svelte"
import Link from "../../UI/Base/Link"
import { Feature, Point } from "geojson"
import { AddImageOptions } from "panoramax-js/dist/Panoramax"
+import { ServerSourceInfo } from "../../Models/SourceOverview"
export default class PanoramaxImageProvider extends ImageProvider {
public static readonly singleton: PanoramaxImageProvider = new PanoramaxImageProvider()
@@ -218,8 +219,20 @@ export default class PanoramaxImageProvider extends ImageProvider {
}
}
- public apiUrls(): string[] {
- return ["https://panoramax.mapcomplete.org", "https://panoramax.xyz"]
+ public apiUrls(): ServerSourceInfo[] {
+ return [
+ Constants.panoramax,
+ {
+ url: "https://panoramax.xyz",
+ description: "The federation instance of Panoramax, which knows all panoramax-servers and can query all servers",
+ selfhostable: true,
+ openData: true,
+ sourceAvailable: true,
+ moreInfo: ["https://wiki.openstreetmap.org/wiki/Panoramax"],
+ trigger: ["always"],
+ category: "core",
+
+ }]
}
public static getPanoramaxInstance(host: string) {
diff --git a/src/Logic/ImageProviders/WikidataImageProvider.ts b/src/Logic/ImageProviders/WikidataImageProvider.ts
index 83708fd07..34c9e38f6 100644
--- a/src/Logic/ImageProviders/WikidataImageProvider.ts
+++ b/src/Logic/ImageProviders/WikidataImageProvider.ts
@@ -6,6 +6,7 @@ import SvelteUIElement from "../../UI/Base/SvelteUIElement"
import * as Wikidata_icon from "../../assets/svg/Wikidata.svelte"
import { Utils } from "../../Utils"
import { Feature, Point } from "geojson"
+import { ServerSourceInfo } from "../../Models/SourceOverview"
export class WikidataImageProvider extends ImageProvider {
public static readonly singleton = new WikidataImageProvider()
@@ -20,7 +21,7 @@ export class WikidataImageProvider extends ImageProvider {
super()
}
- public apiUrls(): string[] {
+ public apiUrls(): ServerSourceInfo[] {
return Wikidata.neededUrls
}
diff --git a/src/Logic/ImageProviders/WikimediaImageProvider.ts b/src/Logic/ImageProviders/WikimediaImageProvider.ts
index eee884024..dc71c3dfa 100644
--- a/src/Logic/ImageProviders/WikimediaImageProvider.ts
+++ b/src/Logic/ImageProviders/WikimediaImageProvider.ts
@@ -6,6 +6,8 @@ import Wikimedia from "../Web/Wikimedia"
import SvelteUIElement from "../../UI/Base/SvelteUIElement"
import Wikimedia_commons_white from "../../assets/svg/Wikimedia_commons_white.svelte"
import { Feature, Point } from "geojson"
+import { ServerSourceInfo } from "../../Models/SourceOverview"
+import { describe } from "vitest"
/**
* This module provides endpoints for wikimedia and others
@@ -96,8 +98,15 @@ export class WikimediaImageProvider extends ImageProvider {
return value
}
- apiUrls(): string[] {
- return WikimediaImageProvider.apiUrls
+ apiUrls(): ServerSourceInfo[] {
+ return WikimediaImageProvider.apiUrls.map(url => {
+ url,
+ description: "Wikimedia contains various images",
+ openData: true,
+ sourceAvailable: true,
+ category: "core",
+ selfhostable: true,
+ })
}
SourceIcon(): BaseUIElement {
diff --git a/src/Logic/Web/Wikidata.ts b/src/Logic/Web/Wikidata.ts
index 6a23371a6..306f0885a 100644
--- a/src/Logic/Web/Wikidata.ts
+++ b/src/Logic/Web/Wikidata.ts
@@ -1,6 +1,7 @@
import { Utils } from "../../Utils"
import { Store, UIEventSource } from "../UIEventSource"
import { SimplifiedClaims, WBK } from "wikibase-sdk"
+import { ServerSourceInfo } from "../../Models/SourceOverview"
export class WikidataResponse {
public readonly id: string
@@ -132,12 +133,20 @@ export default class Wikidata {
sparqlEndpoint: "https://query.wikidata.org/bigdata/namespace/wdq/sparql",
})
- public static readonly neededUrls = [
+ public static readonly neededUrls: ServerSourceInfo[] = [
"https://www.wikidata.org/",
"https://wikidata.org/",
"https://query.wikidata.org",
"https://m.wikidata.org", // Important: a mobile browser will request m.wikidata.org instead of www.wikidata.org ; this URL needs to be listed for the CSP
- ]
+ ].map(url => ({
+ url,
+ sourceAvailable: true,
+ openData: true,
+ selfhostable: "yes, but useless due to network effects",
+ trigger: ["always"],
+ category: "feature",
+ description: "MapComplete checks wikidata to find images to show and displays information from wikidata on some objects (e.g. it shows basic information on artwork, etymology, ...)"
+ }))
private static readonly _identifierPrefixes = ["Q", "L"].map((str) => str.toLowerCase())
private static readonly _prefixesToRemove = [
"https://www.wikidata.org/wiki/Lexeme:",
diff --git a/src/Models/Constants.ts b/src/Models/Constants.ts
index adab8b890..c2a0d4dab 100644
--- a/src/Models/Constants.ts
+++ b/src/Models/Constants.ts
@@ -1,7 +1,7 @@
import * as packagefile from "../../package.json"
import * as extraconfig from "../../config.json"
-import { Utils } from "../Utils"
import { AuthConfig } from "../Logic/Osm/AuthConfig"
+import { ServerSourceInfo } from "./SourceOverview"
export type PriviligedLayerType = (typeof Constants.priviliged_layers)[number]
export type DefaultPinIcon = (typeof Constants._defaultPinIcons)[number]
@@ -55,7 +55,14 @@ export default class Constants {
token: string
sequence: string
testsequence: string
- } = packagefile.config.panoramax
+ } & ServerSourceInfo = { ...packagefile.config.panoramax,
+ description: "The panoramax-server that MapComplete uploads to",
+ category: "core",
+ sourceAvailable: true,
+ openData: true,
+ selfhostable: true,
+ moreInfo: ["https://wiki.openstreetmap.org/wiki/Panoramax"]
+ }
// The user journey states thresholds when a new feature gets unlocked
public static userJourney = {
@@ -134,15 +141,68 @@ export default class Constants {
public static readonly mapillary_client_token_v4 = Constants.config.api_keys.mapillary_v4
public static defaultOverpassUrls = Constants.config.default_overpass_urls
public static countryCoderEndpoint: string = Constants.config.country_coder_host
+ public static countryCoderInfo: ServerSourceInfo = {
+ url: this.countryCoderEndpoint,
+ trigger: ["always"],
+ openData: true,
+ category: "core",
+ logging: "no",
+ selfhostable: true,
+ sourceAvailable: true,
+ moreInfo:["https://source.mapcomplete.org/MapComplete/latlon2country"],
+ description: "For quite some functions, we need to know in what _country_ a feature is located. LatLon2Country is a static dataset, which, by cleverly encoding the data, can quickly tell in what country a feature is located."
+ }
public static communityIndexHost: string = Constants.config.community_index_host
- public static osmAuthConfig: AuthConfig = Constants.config.oauth_credentials
+ private static osmServerInfo: Omit = { trigger: ["always"],
+ description: "Login service, by OpenStreetMap.org",
+ sourceAvailable: true,
+ openData: true,
+ selfhostable: "partially - a copy can be hosted, but this would be useless",
+ category: "core",
+ moreInfo: ["https://www.openstreetmap.org/copyright","https://www.openstreetmap.org/about", "https://osmfoundation.org/wiki/Privacy_Policy"]
+ }
+
+ public static osmAuthConfig: AuthConfig & ServerSourceInfo= {... Constants.config.oauth_credentials,
+ ...this.osmServerInfo
+ }
public static nominatimEndpoint: string = Constants.config.nominatimEndpoint
+ public static nominatimEndpointInfo: ServerSourceInfo = {
+ url: this.nominatimEndpoint,
+ description: "Nominatim search engine endpoint, used when searching",
+ selfhostable: true,
+ openData: true,
+ sourceAvailable: true,
+ category: "core",
+ trigger:["specific_feature"],
+ moreInfo: ["https://wiki.openstreetmap.org/wiki/Nominatim"]
+ }
public static photonEndpoint: string = Constants.config.photonEndpoint
+ public static photonEndpointInfo: ServerSourceInfo = {
+ url: this.photonEndpoint,
+ description: "Endpoint for search with photon",
+ sourceAvailable: true,
+ selfhostable: true,
+ openData: true,
+ category: "core",
+ trigger: ["specific_feature"],
+ moreInfo: ["https://wiki.openstreetmap.org/wiki/Photon"]
+ }
public static nsiLogosEndpoint: string = Constants.config.nsi_logos_server ?? null
public static weblate: string = "https://translate.mapcomplete.org/"
public static linkedDataProxy: string = Constants.config["jsonld-proxy"]
+ public static linkedDataProxyInfo: ServerSourceInfo = {
+ url: Constants.config["jsonld-proxy"],
+ trigger: ["specific_feature"],
+ category: "core",
+ openData: true,
+ description: "This proxy queries websites to detect if they contain linked open data and gives this data back. Triggered by opening a feature",
+ sourceAvailable: true,
+ selfhostable: true,
+ moreInfo: ["https://source.mapcomplete.org/MapComplete/MapComplete/src/branch/develop/scripts/serverLdScrape.ts"]
+ }
+
/**
* These are the values that are allowed to use as 'backdrop' icon for a map pin
*/
@@ -208,22 +268,65 @@ export default class Constants {
* This is a MapLibre/MapBox vector tile server which hosts vector tiles for every (official) layer
*/
public static VectorTileServer: string | undefined = Constants.config.mvt_layer_server
+ public static vectorTileServerInfo: ServerSourceInfo = {
+ url: this.VectorTileServer,
+ description: "The vectortileserver is a cache of OSM data and can be used as an alternative for overpass to actually show data, esp on low zoom levels",
+ selfhostable: true,
+ openData: true,
+ category: "core",
+ sourceAvailable: true,
+ trigger: ["always"],
+ moreInfo: ["https://source.mapcomplete.org/MapComplete/MapComplete/src/branch/develop/Docs/SettingUpPSQL.md"]
+ }
public static GeoIpServer: string | undefined = Constants.config.geoip_server
+ public static geoIpServerInfo: ServerSourceInfo = {
+ url: this.GeoIpServer,
+ category: "core",
+ description: "When opening MapComplete for the first time, we try to set the map at a relevant location. For this, we try to determine the location based on the IP-address; this is done by this service.",
+ selfhostable: true,
+ openData: false,
+ trigger: ["always"],
+ sourceAvailable: true
+ }
public static ErrorReportServer: string | undefined = Constants.config.error_server
-
+ public static errorReportServerInfo: ServerSourceInfo = {
+ url: this.ErrorReportServer,
+ logging: "yes",
+ category: "core",
+ selfhostable: "yes",
+ openData: "no (privacy)",
+ trigger: ["on_failure"],
+ description: "If a severe error occurs in MapComplete, this is logged on this server - this mostly concerns errors where making a change to OpenStreetMap failed. Data is handled confidentially and _only_ to replay the change and fix the root cause."
+ }
public static readonly SummaryServer: string = Constants.config.summary_server
-
- public static allServers: string[] = [
- Constants.SummaryServer,
- Constants.VectorTileServer,
- Constants.GeoIpServer,
- Constants.ErrorReportServer,
- Constants.countryCoderEndpoint,
- Constants.osmAuthConfig.url,
- Constants.nominatimEndpoint,
- Constants.photonEndpoint,
- Constants.linkedDataProxy,
- ...Constants.defaultOverpassUrls,
+ public static readonly summaryServerInfo: ServerSourceInfo = {
+ url: this.SummaryServer,
+ trigger: ["always"],
+ category: "core",
+ selfhostable: true,
+ sourceAvailable: true,
+ openData: true,
+ description: "This server indicates how much items there are (according to OpenStreetMap) at a given slippy tile coordinate"
+ }
+ public static allServers: ServerSourceInfo[] = [
+ Constants.summaryServerInfo,
+ Constants.vectorTileServerInfo,
+ Constants.geoIpServerInfo,
+ Constants.errorReportServerInfo,
+ Constants.osmAuthConfig,
+ Constants.countryCoderInfo,
+ Constants.nominatimEndpointInfo,
+ Constants.photonEndpointInfo,
+ ...Constants.defaultOverpassUrls.map(url => ({
+ url,
+ openData: true,
+ selfhostable: true,
+ trigger: ["always"],
+ sourceAvailable: true,
+ category: "core",
+ description: "Overpass is a query service where OpenStreetMap-data can be retrieved. Various overpass-servers are used to query this data",
+ moreInfo: ["https://wiki.openstreetmap.org/wiki/Overpass_turbo"]
+ })),
]
private static priviligedLayerSet = new Set(Constants.priviliged_layers)
diff --git a/src/Models/RasterLayers.ts b/src/Models/RasterLayers.ts
index 6ff3fb41a..3395bff44 100644
--- a/src/Models/RasterLayers.ts
+++ b/src/Models/RasterLayers.ts
@@ -9,25 +9,14 @@ import { Store, Stores, UIEventSource } from "../Logic/UIEventSource"
import { GeoOperations } from "../Logic/GeoOperations"
import { EliCategory, RasterLayerProperties } from "./RasterLayerProperties"
import { Utils } from "../Utils"
-
+import {default as ELI} from "../../public/assets/data/editor-layer-index.json"
export type EditorLayerIndex = (Feature & RasterLayerPolygon)[]
export class AvailableRasterLayers {
- private static _editorLayerIndex: EditorLayerIndex = undefined
- private static _editorLayerIndexStore: UIEventSource =
- new UIEventSource(undefined)
+ private static _editorLayerIndex: EditorLayerIndex = ELI.features
- public static async editorLayerIndex(): Promise {
- if (AvailableRasterLayers._editorLayerIndex !== undefined) {
- return AvailableRasterLayers._editorLayerIndex
- }
- console.debug("Downloading ELI")
- const eli = await Utils.downloadJson<{ features: EditorLayerIndex }>(
- "./assets/data/editor-layer-index.json"
- )
- this._editorLayerIndex = eli.features?.filter((l) => l.properties.id !== "Bing") ?? []
- this._editorLayerIndexStore.set(this._editorLayerIndex)
- return this._editorLayerIndex
+ public static editorLayerIndex(): EditorLayerIndex {
+ return AvailableRasterLayers._editorLayerIndex
}
public static readonly globalLayers: ReadonlyArray =
@@ -101,18 +90,13 @@ export class AvailableRasterLayers {
location: Store<{ lon: number; lat: number }>,
enableBing?: Store
): Store {
- this.editorLayerIndex() // start the download
const availableLayersBboxes = Stores.ListStabilized(
location.mapD(
(loc) => {
- const eli = AvailableRasterLayers._editorLayerIndexStore.data
- if (!eli) {
- return []
- }
+ const eli = AvailableRasterLayers._editorLayerIndex
const lonlat: [number, number] = [loc.lon, loc.lat]
return eli.filter((eliPolygon) => BBox.get(eliPolygon).contains(lonlat))
- },
- [AvailableRasterLayers._editorLayerIndexStore]
+ }
)
)
return Stores.ListStabilized(
diff --git a/src/Models/SourceOverview.ts b/src/Models/SourceOverview.ts
new file mode 100644
index 000000000..a8905c907
--- /dev/null
+++ b/src/Models/SourceOverview.ts
@@ -0,0 +1,186 @@
+import { RasterLayerProperties } from "./RasterLayerProperties"
+import { AvailableRasterLayers, RasterLayerPolygon } from "./RasterLayers"
+import { Utils } from "../Utils"
+import * as eli from "../../public/assets/data/editor-layer-index.json"
+import * as layers_global from "../../src/assets/global-raster-layers.json"
+import eli_global from "../../src/assets/generated/editor-layer-index-global.json"
+import bing from "../../src/assets/bing.json"
+import Constants from "../../src/Models/Constants"
+import ThemeConfig from "../../src/Models/ThemeConfig/ThemeConfig"
+import { ThemeConfigJson } from "../../src/Models/ThemeConfig/Json/ThemeConfigJson"
+import SpecialVisualizations from "../../src/UI/SpecialVisualizations"
+import ValidationUtils from "../../src/Models/ThemeConfig/Conversion/ValidationUtils"
+import {
+ QuestionableTagRenderingConfigJson,
+} from "../../src/Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson"
+import { LayerConfigJson } from "../../src/Models/ThemeConfig/Json/LayerConfigJson"
+import { ImmutableStore } from "../../src/Logic/UIEventSource"
+
+export interface ServerSourceInfo {
+ url: string
+ description: string
+ category: "core" | "feature" | "maplayer"
+ selfhostable?: boolean | string
+ sourceAvailable?: boolean | string
+ openData?: boolean | string
+ trigger?: ("always" | "specific_theme" | "specific_feature" | "clear_consent" | string)[]
+ moreInfo?: string[],
+ logging?: "yes" | "probably" | "no"
+}
+
+/**
+ * Generates what URLs the webapp might access, and why
+ */
+export class SourceOverview {
+ private eliUrlsCached: ServerSourceInfo[]
+
+ public async getOverview(layout?: ThemeConfig,
+ layoutJson?: ThemeConfigJson): Promise<(ServerSourceInfo | string)[]> {
+ const apiUrls: (ServerSourceInfo | string)[] = [
+ ...Constants.allServers,
+
+ "https://www.openstreetmap.org",
+ "https://api.openstreetmap.org",
+ "https://panoramax.mapcomplete.org",
+
+ {
+ url: "https://data.mapcomplete.org/nsi",
+ description: "Contains a copy and the logos of the Name Suggestion Index",
+ category: "core",
+ },
+ ]
+
+ apiUrls.push(...(await this.eliUrls()))
+
+ for (const sv of SpecialVisualizations.specialVisualizations) {
+ if (typeof sv.needsUrls === "function") {
+ // Handled below
+ continue
+ }
+ apiUrls.push(...(sv.needsUrls ?? []))
+ }
+
+
+ const usedSpecialVisualisations = layoutJson?.layers?.flatMap((l) =>
+ ValidationUtils.getAllSpecialVisualisations(
+ (l).tagRenderings ?? [],
+ ))
+
+ for (const usedSpecialVisualisation of usedSpecialVisualisations ?? []) {
+ if (typeof usedSpecialVisualisation === "string") {
+ continue
+ }
+ const neededUrls = usedSpecialVisualisation.func.needsUrls ?? []
+ if (typeof neededUrls === "function") {
+ let needed: string | string[] | ServerSourceInfo | ServerSourceInfo[] = neededUrls(usedSpecialVisualisation.args)
+ if (Array.isArray(needed)) {
+ apiUrls.push(...needed)
+ } else {
+ apiUrls.push(needed)
+ }
+ }
+ }
+
+ const geojsonSources: string[] = layout?.layers?.map((l) => l.source?.geojsonSource) ?? []
+
+ return Utils.NoNull(apiUrls.concat(...geojsonSources)).filter(item => {
+ if (typeof item === "string") {
+ return true
+ }
+ console.log(item)
+ return item.url?.trim()?.length > 0
+ })
+
+ }
+
+ private async eliUrls(): Promise {
+ if (this.eliUrlsCached) {
+ return this.eliUrlsCached
+ }
+ let urls: ServerSourceInfo[] = []
+ const regex = /{switch:([^}]+)}/
+ const rasterLayers: { properties: RasterLayerProperties }[] = [
+ AvailableRasterLayers.defaultBackgroundLayer,
+ ...eli.features,
+ bing,
+ ...eli_global.map((properties) => ({ properties })),
+ ...layers_global.layers.map((properties) => ({ properties })),
+ ]
+ for (const feature of rasterLayers) {
+ const f = feature
+ const url = f.properties.url
+ const match = url.match(regex)
+
+ const packageInInfo = (url: string) => {
+ if(typeof url !== "string"){
+ throw "invalid url"+url
+ }
+ return ({
+ url,
+ description: "Background layer source or supporting sources for " + f.properties.id,
+ trigger: ["specific_feature"],
+ category: "maplayer",
+ moreInfo: Utils.NoEmpty(["https://github.com/osmlab/editor-layer-index", f.properties?.attribution?.url]),
+ })
+ }
+
+ if (match) {
+ const domains = match[1].split(",")
+ const subpart = match[0]
+ const info: ServerSourceInfo[] = domains.map((d) => packageInInfo(url.replace(subpart, d)))
+ urls.push(...info)
+ } else {
+ urls.push(packageInInfo(url))
+ }
+
+ if (f.properties.type === "vector") {
+ // We also need to whitelist eventual sources
+ urls.push(...(f.properties["connect-src"] ?? []).map(packageInInfo))
+ let url = f.properties.url
+ if (url.startsWith("pmtiles://")) {
+ url = url.substring("pmtiles://".length)
+ }
+ if (url.endsWith(".pmtiles")) {
+ continue
+ }
+ try {
+ console.log("Downloading ", url)
+ const styleSpec = await Utils.downloadJsonCached(url, 1000 * 120, {
+ Origin: "https://mapcomplete.org",
+ })
+ for (const key of Object.keys(styleSpec?.["sources"] ?? {})) {
+ const url = styleSpec["sources"][key].url
+ if (!url) {
+ continue
+ }
+ let urlClipped = url
+ if (url.indexOf("?") > 0) {
+ urlClipped = url?.substring(0, url.indexOf("?"))
+ }
+ console.log("Source url ", key, url)
+ urls.push(packageInInfo(url))
+ if (urlClipped.endsWith(".json")) {
+ const tileInfo = await Utils.downloadJsonCached(url, 1000 * 120, {
+ Origin: "https://mapcomplete.org",
+ })
+ urls.push(packageInInfo(tileInfo["tiles"] ?? []))
+ }
+ }
+ urls.push(...(styleSpec["tiles"] ?? []).flatMap(ls => ls).map(url => packageInInfo(url)))
+ urls.push(packageInInfo(styleSpec["sprite"]))
+ urls.push(packageInInfo(styleSpec["glyphs"]))
+ } catch (e) {
+ console.error(
+ "ERROR: could not download a resource, some sprites might not be whitelisted and thus not load"
+ )
+ }
+ }
+ }
+ urls = urls.filter(item => !!item.url)
+ urls.sort((a, b) => a < b ? -1 : 1)
+ urls = Utils.DedupOnId(urls, item => item.url)
+ this.eliUrlsCached = urls
+ return urls
+ }
+
+}
diff --git a/src/Models/ThemeConfig/TagRenderingConfig.ts b/src/Models/ThemeConfig/TagRenderingConfig.ts
index 0d68c86ec..3387c4426 100644
--- a/src/Models/ThemeConfig/TagRenderingConfig.ts
+++ b/src/Models/ThemeConfig/TagRenderingConfig.ts
@@ -698,7 +698,7 @@ export default class TagRenderingConfig {
values,
}
} catch (e) {
- console.error("Could not create FreeformValues for tagrendering", this.id)
+ console.error("Could not create FreeformValues for tagrendering", this.id, e)
return undefined
}
}
diff --git a/src/UI/Popup/PlantNetDetectionViz.ts b/src/UI/Popup/PlantNetDetectionViz.ts
index 979f67c0b..694761a24 100644
--- a/src/UI/Popup/PlantNetDetectionViz.ts
+++ b/src/UI/Popup/PlantNetDetectionViz.ts
@@ -9,10 +9,18 @@ import { SpecialVisualization, SpecialVisualizationState } from "../SpecialVisua
import SvelteUIElement from "../Base/SvelteUIElement"
import PlantNet from "../PlantNet/PlantNet.svelte"
import { default as PlantNetCode } from "../../Logic/Web/PlantNet"
+import { ServerSourceInfo } from "../../Models/SourceOverview"
export class PlantNetDetectionViz extends SpecialVisualization {
funcName = "plantnet_detection"
- needsUrls = [PlantNetCode.baseUrl]
+ needsUrls: ServerSourceInfo[] = [{ url : PlantNetCode.baseUrl,
+ description: "Planet provides an API that, based on images, detects a plant species",
+ category: "feature",
+ openData: true,
+ selfhostable: "unknown",
+ trigger:["specific_feature", "specific_theme", "clear_consent"],
+ moreInfo: ["https://plantnet.org","https://github.com/plantnet"]
+ }]
group = "data_import"
docs =
diff --git a/src/UI/SpecialVisualisations/DataImportSpecialVisualisations.ts b/src/UI/SpecialVisualisations/DataImportSpecialVisualisations.ts
index 8a7677e39..ad30c8e82 100644
--- a/src/UI/SpecialVisualisations/DataImportSpecialVisualisations.ts
+++ b/src/UI/SpecialVisualisations/DataImportSpecialVisualisations.ts
@@ -15,6 +15,7 @@ import Toggle from "../Input/Toggle"
import ComparisonTool from "../Comparison/ComparisonTool.svelte"
import { Utils } from "../../Utils"
import TagApplyViz from "./TagApplyViz"
+import { ServerSourceInfo } from "../../Models/SourceOverview"
class MaprouletteSetStatus extends SpecialVisualizationSvelte {
funcName = "maproulette_set_status"
@@ -128,7 +129,22 @@ class LinkedDataFromWebsite extends SpecialVisualization {
doc: "If the containing accordion should be closed",
},
]
- needsUrls = [Constants.linkedDataProxy, "http://www.schema.org"]
+ needsUrls: ServerSourceInfo[] = [
+ Constants.linkedDataProxyInfo,
+ {
+ url: "http://www.schema.org",
+ description: "Only needed for the velopark-theme",
+ category: "feature",
+ openData: true,
+ },
+ {
+ url: "https://data.velopark.be",
+ description: "Only needed for the velopark-theme",
+ openData: true,
+ selfhostable: false,
+ category: "feature",
+ },
+ ]
constr(
state: SpecialVisualizationState,
diff --git a/src/UI/SpecialVisualisations/TagrenderingManipulationSpecialVisualisations.ts b/src/UI/SpecialVisualisations/TagrenderingManipulationSpecialVisualisations.ts
index 49fd6adc7..a8355b609 100644
--- a/src/UI/SpecialVisualisations/TagrenderingManipulationSpecialVisualisations.ts
+++ b/src/UI/SpecialVisualisations/TagrenderingManipulationSpecialVisualisations.ts
@@ -13,6 +13,7 @@ import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig"
import BaseUIElement from "../BaseUIElement"
import TagRenderingEditable from "../Popup/TagRendering/TagRenderingEditable.svelte"
import Combine from "../Base/Combine"
+import { ServerSourceInfo } from "../../Models/SourceOverview"
class StealViz extends SpecialVisualization {
funcName = "steal"
@@ -232,7 +233,14 @@ class OpenInJosm extends SpecialVisualizationSvelte {
group = "tagrendering_manipulation"
docs = "Opens the current view in the JOSM-editor"
args = []
- needsUrls = ["http://127.0.0.1:8111/load_and_zoom"]
+ needsUrls = [ { url: "http://127.0.0.1:8111/load_and_zoom",
+ sourceAvailable: true,
+ selfhostable: "not applicable",
+ category: "feature",
+ description: "JOSM is a desktop program to edit OpenStreetMap. If a user clicks the 'open here in JOSM'-button, JOSM will be contacted on localhost to open this location. This button is hidden on small screens (i.e. mobile)",
+ moreInfo: ["https://josm.openstreetmap.de/", "https://wiki.openstreetmap.org/wiki/JOSM"]
+
+ }]
constr(state): SvelteUIElement {
return new SvelteUIElement(OpenJosm, { state })
diff --git a/src/UI/SpecialVisualization.ts b/src/UI/SpecialVisualization.ts
index 48cb13e35..76efa97d5 100644
--- a/src/UI/SpecialVisualization.ts
+++ b/src/UI/SpecialVisualization.ts
@@ -27,6 +27,7 @@ import UserRelatedState from "../Logic/State/UserRelatedState"
import FeaturePropertiesStore from "../Logic/FeatureSource/Actors/FeaturePropertiesStore"
import SvelteUIElement from "./Base/SvelteUIElement"
import { Utils } from "../Utils"
+import { ServerSourceInfo } from "../Models/SourceOverview"
/**
* The state needed to render a special Visualisation.
@@ -94,7 +95,7 @@ export abstract class SpecialVisualization {
*/
readonly group?: string
readonly example?: string
- readonly needsUrls?: string[] | ((args: string[]) => string | string[])
+ readonly needsUrls?: (string | ServerSourceInfo)[] | ((args: string[]) => string | string[] | ServerSourceInfo | ServerSourceInfo[])
readonly definedIn = Utils.runningFromConsole ? Utils.getLocationInCode(2) : undefined;
/**
diff --git a/src/UI/SpecialVisualizations.ts b/src/UI/SpecialVisualizations.ts
index 4c603d038..d44b00aee 100644
--- a/src/UI/SpecialVisualizations.ts
+++ b/src/UI/SpecialVisualizations.ts
@@ -18,6 +18,7 @@ import {
} from "./SpecialVisualisations/WebAndCommunicationSpecialVisualisations"
import { DataVisualisations } from "./Popup/DataVisualisations"
import { DataExportVisualisations } from "./Popup/DataExportVisualisations"
+import { Utils } from "../Utils"
export default class SpecialVisualizations {
public static specialVisualizations: SpecialVisualization[] = SpecialVisualizations.initList()
@@ -30,7 +31,7 @@ export default class SpecialVisualizations {
for (const specialVisualization of SpecialVisualizations.specialVisualizations) {
SpecialVisualizations.specialVisualisationsDict.set(
specialVisualization.funcName,
- specialVisualization
+ specialVisualization,
)
}
}
@@ -47,7 +48,7 @@ export default class SpecialVisualizations {
"`{" + viz.funcName + "(" + viz.args.map((arg) => arg.defaultValue).join(",") + ")}`"
let definitionPlace = ""
- if(viz.definedIn){
+ if (viz.definedIn) {
const path = viz.definedIn
definitionPlace = `Defined in [${path.markdownLocation}](${path.markdownLocation})`
}
@@ -56,15 +57,15 @@ export default class SpecialVisualizations {
viz.docs,
viz.args.length > 0
? MarkdownUtils.table(
- ["name", "default", "description"],
- viz.args.map((arg) => {
- let defaultArg = arg.defaultValue ?? "_undefined_"
- if (defaultArg == "") {
- defaultArg = "_empty string_"
- }
- return [arg.name, defaultArg, arg.doc]
- })
- )
+ ["name", "default", "description"],
+ viz.args.map((arg) => {
+ let defaultArg = arg.defaultValue ?? "_undefined_"
+ if (defaultArg == "") {
+ defaultArg = "_empty string_"
+ }
+ return [arg.name, defaultArg, arg.doc]
+ }),
+ )
: undefined,
definitionPlace,
"#### Example usage of " + viz.funcName,
@@ -74,12 +75,12 @@ export default class SpecialVisualizations {
public static constructSpecification(
template: string,
- extraMappings: SpecialVisualization[] = []
+ extraMappings: SpecialVisualization[] = [],
): RenderingSpecification[] {
return SpecialVisualisationUtils.constructSpecification(
template,
SpecialVisualizations.specialVisualisationsDict,
- extraMappings
+ extraMappings,
)
}
@@ -112,7 +113,7 @@ export default class SpecialVisualizations {
"Special visualisations which reuse other tagRenderings to show data, but with a twist.",
web_and_communication:
"Tools to show data from external websites, which link to external websites or which link to external profiles",
- ui: "Elements to support the user interface, e.g. 'title', 'translated'"
+ ui: "Elements to support the user interface, e.g. 'title', 'translated'",
}
const helpTexts: string[] = []
@@ -161,7 +162,7 @@ export default class SpecialVisualizations {
},
},
null,
- " "
+ " ",
)
const firstPart = [
@@ -171,7 +172,7 @@ export default class SpecialVisualizations {
"# Using expanded syntax",
`Instead of using \`{"render": {"en": "{some_special_visualisation(some_arg, some other really long message, more args)} , "nl": "{some_special_visualisation(some_arg, een boodschap in een andere taal, more args)}}\`, one can also write`,
"```\n" + example + "\n```\n",
- 'In other words: use `{ "before": ..., "after": ..., "special": {"type": ..., "argname": ...argvalue...}`. The args are in the `special` block; an argvalue can be a string, a translation or another value. (Refer to class `RewriteSpecial` in case of problems)',
+ "In other words: use `{ \"before\": ..., \"after\": ..., \"special\": {\"type\": ..., \"argname\": ...argvalue...}`. The args are in the `special` block; an argvalue can be a string, a translation or another value. (Refer to class `RewriteSpecial` in case of problems)",
"# Overview of all special components",
].join("\n\n")
return firstPart + "\n\n" + helpTexts.join("\n\n")
@@ -196,28 +197,31 @@ export default class SpecialVisualizations {
specialVisualizations.push(new AutoApplyButtonVis(specialVisualizations))
- const regex = /[a-zA-Z_]+/
- const invalid = specialVisualizations
- .map((sp, i) => ({ sp, i }))
- .filter((sp) => sp.sp.funcName === undefined || !sp.sp.funcName.match(regex))
+ if (Utils.runningFromConsole) {
+ // Some sanity checks
+ const regex = /[a-zA-Z_]+/
+ const invalid = specialVisualizations
+ .map((sp, i) => ({ sp, i }))
+ .filter((sp) => sp.sp.funcName === undefined || !sp.sp.funcName.match(regex))
- if (invalid.length > 0) {
- throw (
- "Invalid special visualisation found: funcName is undefined or doesn't match " +
- regex +
- invalid.map((sp) => sp.i).join(", ") +
- '. Did you perhaps type \n funcName: "funcname" // type declaration uses COLON\ninstead of:\n funcName = "funcName" // value definition uses EQUAL'
- )
- }
-
- const allNames = specialVisualizations.map((f) => f.funcName)
- const seen = new Set()
- for (let name of allNames) {
- name = name.toLowerCase()
- if (seen.has(name)) {
- throw "Invalid special visualisations: detected a duplicate name: " + name
+ if (invalid.length > 0) {
+ throw (
+ "Invalid special visualisation found: funcName is undefined or doesn't match " +
+ regex +
+ invalid.map((sp) => sp.i).join(", ") +
+ ". Did you perhaps type \n funcName: \"funcname\" // type declaration uses COLON\ninstead of:\n funcName = \"funcName\" // value definition uses EQUAL"
+ )
+ }
+
+ const allNames = specialVisualizations.map((f) => f.funcName)
+ const seen = new Set()
+ for (let name of allNames) {
+ name = name.toLowerCase()
+ if (seen.has(name)) {
+ throw "Invalid special visualisations: detected a duplicate name: " + name
+ }
+ seen.add(name)
}
- seen.add(name)
}
return specialVisualizations
diff --git a/src/Utils/MarkdownUtils.ts b/src/Utils/MarkdownUtils.ts
index db5eeccce..8a454f708 100644
--- a/src/Utils/MarkdownUtils.ts
+++ b/src/Utils/MarkdownUtils.ts
@@ -1,5 +1,23 @@
export default class MarkdownUtils {
- public static table(header: string[], contents: string[][]) {
+ public static table(header: string[], contents: string[][], options?:{dropEmptyColumns?: boolean}) {
+
+ if(options?.dropEmptyColumns){
+ const emptyCols = new Set()
+ for (let i = 0; i < contents[0].length; i++) {
+ if(contents.every(row => row[i] === undefined || row[i].trim().length === 0) ){
+ emptyCols.add(i)
+ }
+ }
+ for (let i = header.length-1; i >= 0; i--) {
+ if(emptyCols.has(i)){
+ header.splice(i, 1)
+ for (const row of contents) {
+ row.splice(i, 1)
+ }
+ }
+ }
+ }
+
let result = ""
result += "\n\n| " + header.join(" | ") + " |\n"