aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGuilhem Moulin <guilhem@fripost.org>2025-05-27 12:27:16 +0200
committerGuilhem Moulin <guilhem@fripost.org>2025-06-04 17:14:05 +0200
commitf2c3e05955bec324b7e2a011fdde3894aa53ebd5 (patch)
tree1bd0b4ce2a3c4a4c4a1c8ad1fc1a0edff87298ae
parent4c2dbeecd7bafa5e55893c939c800edc9eb5f4a7 (diff)
info-modal: Fetch source copyright and mtime remotely from metadata.json.
Source URL and copyright information are now defined tools' config.yml. This change exposes MVT creation time as well as source mtime to the info modal. See 052536f62d2e58f6b9b142e035c49cb033458d7f in tools for the generation logic behind metadata.json.
-rw-r--r--index.html73
-rw-r--r--main.js229
-rw-r--r--style.css61
3 files changed, 310 insertions, 53 deletions
diff --git a/index.html b/index.html
index 4b4804e..e9c330b 100644
--- a/index.html
+++ b/index.html
@@ -21,58 +21,31 @@
<h5 class="my-0">Källor och licensinformation</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" title="Stäng" aria-label="Stäng"></button>
</div>
- <div class="modal-body pt-2">
- <ul class="mb-2">
- <li><i>Transmissionsnät för el</i> från
- <a href="https://svk.se" target="_blank">Svenska Kraftnät (SvK)</a>.
- </li>
- <li><i>Dammregistret</i> från
- <a href="https://smhi.se" target="_blank">Sveriges meteorologiska och hydrologiska institut (SMHI)</a>,
- CC-BY-4.0 (<a href="https://www.smhi.se/data/oppna-data/villkor-for-anvandning-1.30622" target="_blank">öppna data</a>).
- </li>
- <li><i>Mineralrättsregistret</i> från
- <a href="https://www.sgu.se/bergsstaten/" target="_blank">Bergsstaten</a>,
- CC0 (<a href="https://creativecommons.org/publicdomain/zero/1.0/deed.sv" target="_blank">öppna data</a>).
- </li>
- <li><i>Vindbruk</i> från
- <a href="https://www.energimyndigheten.se/energisystem-och-analys/elproduktion/vindkraft/vindbrukskollen/" target="_blank">Länsstyrelserna och Energimyndigheten</a>,
- CC0 (<a href="https://ext-geodatakatalog-forv.lansstyrelsen.se/GeodataKatalogen/codelist/metadata/anvandningsrestriktioner.xml#CC01.0" target="_blank">öppna data</a>).
- </li>
- <li><i>Skogsbruk</i>, <i>Skogliga biotopskyddsområden</i> och <i>Naturvårdsavtal</i> från
- <a href="https://skogsstyrelsen.se" target="_blank">Skogsstyrelsen</a>,
- CC0 (<a href="https://www.skogsstyrelsen.se/sjalvservice/karttjanster/geodatatjanster/villkor-for-nyttjande-av-skogsstyrelsens-kartdatabaser/" target="_blank">öppna data</a>).
- </li>
- <li><i>Naturvårdsregistret</i> och <i>Naturvårdsavtal</i> från
- <a href="https://www.naturvardsverket.se/" target="_blank">Naturvårdsverket</a>,
- CC0 (<a href="https://geodata.naturvardsverket.se/nedladdning/naturvardsregistret/Naturvardsregistret_beskrivning_av_oppna_data.pdf" target="_blank">öppna data</a>).
- </li>
- <li><i>Riksintresse naturvård</i> och <i>frilufsliv</i> från
- <a href="https://www.naturvardsverket.se/" target="_blank">Naturvårdsverket</a>
- och
- <a href="https://www.lansstyrelsen.se/" target="_blank">Länsstyrelsen</a>,
- CC-BY-4.0 (<a href="https://ext-geodatakatalog-forv.lansstyrelsen.se/GeodataKatalogen/codelist/metadata/anvandningsrestriktioner.xml#CCby4.0" target="_blank">öppna data</a>).
- </li>
- <li><i>Riksintresse Rennäringen</i> skikt från
- <a href="https://sametinget.se" target="_blank">Sametinget</a>,
- CC-BY-4.0 (<a href="https://ext-geodatakatalog-forv.lansstyrelsen.se/GeodataKatalogen/codelist/metadata/anvandningsrestriktioner.xml#CCby4.0" target="_blank">öppna data</a>).
- <i>Samebyarnas betesområden</i> och <i>flyttled</i> från
- &copy; <a href="https://sametinget.se" target="_blank">Sametingets</a>
- Rennäringens markanvändningsdatabas (IRENMARK).
- </li>
- <li>Bakgrund kartor från
- &copy; <a href="https://lantmateriet.se" target="_blank">Lantmäteriet</a>, CC0
- (<a href="https://www.lantmateriet.se/sv/geodata/vara-produkter/oppna-data/#anchor-1" target="_blank">öppna data</a>).
- </li>
- <li>Webbkartan:
- &copy; <a href="https://guilhem.se" target="_blank">Guilhem Moulin</a>, AGPLv3+.
- <a href="https://git.guilhem.org/KlimatanalysNorr/webmap" target="_blank">Källkod</a>.
- </li>
- <li>Backend-verktyg:
- &copy; <a href="https://guilhem.se" target="_blank">Guilhem Moulin</a>, GPLv3+.
- <a href="https://git.guilhem.org/KlimatanalysNorr/tools" target="_blank">Källkod</a>.
+ <div id="info-body" class="modal-body">
+ <ul class="list-group list-group-flush mb-2">
+ <li class="list-group-item">
+ <h6>Bakgrund kartor</h6>
+ <p>&copy; <a href="https://lantmateriet.se" target="_blank">Lantmäteriet</a></p>
+ <p>Licensvillkor: <a href="https://www.lantmateriet.se/sv/geodata/vara-produkter/oppna-data/#anchor-1" target="_blank">CC0 1.0 Universiell</a></p>
+ </li>
+ <li class="list-group-item">
+ <h6>Webbkartan</h6>
+ <p>&copy; Guilhem Moulin</p>
+ <p>Licensvillkor: <a href="https://www.gnu.org/licenses/agpl-3.0.en.html" target="_blank">AGPLv3+</a></p>
+ <p class="small text-muted"><i class="bi bi-file-earmark-code"></i>
+ <a href="https://git.guilhem.org/KlimatanalysNorr/webmap" target="_blank">Källkod <i class="bi bi-box-arrow-up-right"></i></a></p>
+ </li>
+ <li class="list-group-item">
+ <h6>Backend verktyg</h6>
+ <p>&copy; Guilhem Moulin</p>
+ <p>Licensvillkor: <a href="https://www.gnu.org/licenses/gpl-3.0.en.html" target="_blank">GPLv3+</a> och
+ (endast CGI) <a href="https://www.gnu.org/licenses/agpl-3.0.en.html" target="_blank">AGPLv3+</a></p>
+ <p class="small text-muted"><i class="bi bi-file-earmark-code"></i>
+ <a href="https://git.guilhem.org/KlimatanalysNorr/tools" target="_blank">Källkod <i class="bi bi-box-arrow-up-right"></i></a></p>
</li>
</ul>
- <p class="small mb-0">Webbkartan är utvecklad av
+ <div id="info-accordion" class="accordion accordion-flush"></div>
+ <p class="small text-muted info-credits">Webbkartan är utvecklad av
<a href="https://guilhem.se" target="_blank">Guilhem Datakonsult</a> på uppdrag av
<a href="https://www.klimatanalysnorr.se" target="_blank">Klimatanalys Norr projektet</a>.</p>
</div>
diff --git a/main.js b/main.js
index 6808456..17ca1cc 100644
--- a/main.js
+++ b/main.js
@@ -483,10 +483,189 @@ if (window.location === window.parent.location) {
btn.classList.replace('btn-dark', 'btn-light');
btn.setAttribute('aria-expanded', 'false');
backdrop.classList.remove('modal-backdrop', 'show');
+ infoMetadataAccordions.forEach(function(x, idx) {
+ /* collapse all accordions */
+ const body = x.element.parentNode.parentNode;
+ const name = 'info-accordion-collapse-' + idx;
+ if (body.id === name) {
+ body.classList.remove('show');
+ }
+ if (body.parentNode !== null) {
+ const headers = body.parentNode.getElementsByClassName('accordion-header');
+ for (let i = 0; i < headers.length; i++) {
+ const buttons = headers[i].getElementsByClassName('accordion-button');
+ for (let j = 0; j < buttons.length; j++) {
+ const btn = buttons[j];
+ if (btn.getAttribute('data-bs-target') === '#' + name) {
+ btn.setAttribute('aria-expanded', 'false');
+ btn.classList.add('collapsed');
+ }
+ }
+ }
+ }
+ });
});
btn.onclick = function(event) {
- modal.toggle();
+ infoMetadataAccordions.forEach((x) => x.element.replaceChildren());
+ modal.show();
+ Promise.allSettled(Object.entries(vectorLayers).map(function([grp,lyr]) {
+ const url = lyr.getSource().getUrls()[0];
+ if (url === undefined || url.length <= 16 || url.substr(url.length - 16) !== '/{z}/{x}/{y}.pbf') {
+ throw new Error(`Invalid URL ${url}`);
+ }
+ return fetch(url.substr(0, url.length - 16) + '/metadata.json')
+ .then(function(resp0) {
+ if (resp0.status === 200) {
+ return resp0.json().then((x) => [grp,x]);
+ } else {
+ throw new Error(`${resp0.url} [${resp0.status}]`);
+ }
+ });
+ }))
+ .then(function(rs) {
+ const metadata = Object.fromEntries(rs.filter(function(r) {
+ if (r.status === 'fulfilled') {
+ return true;
+ } else if (r.status === 'rejected') {
+ console.log(r.reason);
+ }
+ return false;
+ }).map((r) => r.value));
+
+ infoMetadataAccordions.forEach(function(x) {
+ const ul = x.element;
+ const groupnames = new Set();
+ const last_updated = [];
+ x.items.forEach(function([groupname, _]) {
+ const layer_group = metadata[groupname];
+ if (layer_group === undefined) {
+ return;
+ }
+ if (!groupnames.has(groupname)) {
+ groupnames.add(groupname);
+ if (layer_group.last_updated !== undefined) {
+ last_updated.push(layer_group.last_updated);
+ }
+ }
+ });
+ if (last_updated.length > 0) {
+ /* show creation time of the MVT layers */
+ const li = document.createElement('li');
+ li.classList.add('list-group-item', 'text-muted');
+ ul.appendChild(li)
+ const i = document.createElement('i');
+ i.classList.add('bi', 'bi-clock');
+ li.appendChild(i);
+ const t = document.createTextNode(
+ ' Lokalt skikt (vectiler) genererades ' +
+ last_updated
+ .sort()
+ .map((ts) => new Date(ts).toLocaleDateString('sv-SE'))
+ .join('; ') + '.'
+ );
+ li.appendChild(t);
+ }
+
+ const source_files = new Set();
+ x.items.forEach(function([groupname, layername]) {
+ /* for each source file associated with the accordion header, show copyright, license and timing information */
+ const layer_group = metadata[groupname];
+ if (layer_group === undefined || layer_group.layers === undefined || layer_group.source_files === undefined) {
+ return;
+ }
+ const def = layer_group.layers[layername];
+ if (def === undefined || def.source_files === undefined) {
+ return;
+ }
+ def.source_files.forEach(function(source_file) {
+ if (source_files.has(source_file)) {
+ return;
+ }
+ const x = layer_group.source_files[source_file];
+ source_files.add(source_file);
+
+ const li = document.createElement('li');
+ li.classList.add('list-group-item');
+ ul.appendChild(li)
+ const h = document.createElement('h6');
+ li.appendChild(h)
+ if (x.description !== undefined && x.description !== null) {
+ const t = document.createTextNode(x.description);
+ h.appendChild(t);
+ }
+
+ if (x.copyright !== undefined && x.copyright !== null) {
+ let p = document.createElement('p');
+ li.appendChild(p)
+ const t = document.createTextNode(x.copyright);
+ p.appendChild(t);
+ }
+
+ if (x.license !== undefined && x.license !== null) {
+ let p = document.createElement('p');
+ li.appendChild(p)
+ p.appendChild(document.createTextNode('Licensvillkor: '));
+ const t = document.createTextNode(x.license.name);
+ if (x.license.url === undefined || x.license.url === null) {
+ p.appendChild(t);
+ } else {
+ const a = document.createElement('a');
+ a.href = x.license.url;
+ a.target = '_blank';
+ a.appendChild(t);
+ p.appendChild(a);
+ }
+ }
+
+ if (x.product_url !== undefined && x.product_url !== null) {
+ let p = document.createElement('p');
+ li.appendChild(p)
+ const t = document.createTextNode('Produktlänk ');
+ const i = document.createElement('i');
+ i.classList.add('bi', 'bi-box-arrow-up-right');
+ const a = document.createElement('a');
+ a.href = x.product_url;
+ a.target = '_blank';
+ a.appendChild(t);
+ a.appendChild(i);
+ p.appendChild(a);
+ }
+
+ if (x.last_modified !== undefined && x.last_modified !== null) {
+ const p = document.createElement('p');
+ p.classList.add('small', 'text-muted');
+ li.appendChild(p);
+ const i = document.createElement('i');
+ i.classList.add('bi', 'bi-file-earmark-code');
+ p.appendChild(i);
+ p.appendChild(document.createTextNode(' '));
+ const t0 = document.createTextNode('Källfil');
+ if (x.url === undefined || x.url === null) {
+ p.appendChild(t0);
+ } else {
+ const a = document.createElement('a');
+ p.appendChild(a);
+ const i = document.createElement('i');
+ i.classList.add('bi', 'bi-box-arrow-up-right');
+ a.appendChild(t0);
+ a.appendChild(document.createTextNode(' '));
+ a.appendChild(i);
+ a.href = x.url;
+ a.target = '_blank';
+ }
+ const t1 = document.createTextNode(' ändrades senast ');
+ p.appendChild(t1);
+ const d = new Date(x.last_modified);
+ const td = document.createTextNode(d.toLocaleDateString('sv-SE'));
+ p.appendChild(td);
+ const t2 = document.createTextNode('.')
+ p.appendChild(t2);
+ }
+ });
+ });
+ });
+ });
};
})();
@@ -3270,6 +3449,7 @@ const [vectorLayers, featureOverlayLayer] = (function() {
/* layer selection panel */
+const infoMetadataAccordions = [];
(function() {
const modal = document.getElementById('layer-selection-panel');
modal.classList.add('modal');
@@ -3502,6 +3682,53 @@ const [vectorLayers, featureOverlayLayer] = (function() {
location.hash = '#' + searchParams.toString();
};
})();
+
+ (function() {
+ const accordion = document.getElementById('info-accordion');
+ layerHierarchy.forEach(function(x, idx) {
+ const item = document.createElement('div');
+ accordion.appendChild(item);
+ item.classList.add('accordion-item');
+
+ const header = document.createElement('div');
+ item.appendChild(header);
+ header.classList.add('accordion-header');
+
+ const btn = document.createElement('button');
+ header.appendChild(btn);
+
+ const collapse = document.createElement('div');
+ item.appendChild(collapse);
+ collapse.id = 'info-accordion-collapse-' + idx;
+ collapse.classList.add('accordion-collapse', 'collapse');
+
+ btn.type = 'button';
+ btn.setAttribute('data-bs-toggle', 'collapse');
+ btn.setAttribute('data-bs-target', '#' + collapse.id);
+ btn.setAttribute('aria-expanded', 'false');
+ btn.setAttribute('aria-controls', collapse.id);
+ btn.classList.add('accordion-button', 'collapsed');
+
+ const t = document.createTextNode(x.text);
+ btn.appendChild(t);
+
+ const body = document.createElement('div');
+ body.classList.add('accordion-body');
+ collapse.appendChild(body);
+
+ const ul = document.createElement('ul');
+ ul.classList.add('list-group', 'list-group-flush');
+ body.appendChild(ul);
+
+ infoMetadataAccordions.push({
+ element: ul,
+ items: x._layers.map(function(k) {
+ const groupname = k.split('_', 1)[0];
+ return [ groupname, k.slice(groupname.length + 1) ];
+ }),
+ });
+ });
+ })();
})();
/* legend panel */
diff --git a/style.css b/style.css
index 273c213..cb675a3 100644
--- a/style.css
+++ b/style.css
@@ -278,9 +278,60 @@ body.inprogress {
-webkit-user-select: text;
-moz-user-select: text;
user-select: text;
+ --modal-info-padding-x: .5rem;
+ --modal-info-bg-light: rgba(0, 0, 0, .08);
}
-#modal-info .modal-body ul > li {
- padding: 0.05rem 0;
+#modal-info .list-group-item,
+#modal-info {
+ --bs-list-group-border-width: 1px;
+}
+#modal-info .accordion {
+ --bs-accordion-active-bg: var(--bs-accordion-bg);
+ --bs-accordion-active-color: var(--bs-body-color);
+ --bs-accordion-btn-padding-x: .5rem;
+ --bs-accordion-btn-padding-y: .025rem;
+ --bs-accordion-btn-focus-box-shadow: none;
+ --bs-accordion-body-padding-x: var(--modal-info-padding-x);
+ --bs-accordion-body-padding-y: 0;
+ --bs-accordion-btn-active-bg: var(--modal-info-bg-light);
+ margin: 0 calc(var(--bs-accordion-btn-padding-x)*-1);
+}
+#modal-info .accordion-item {
+ border: none;
+}
+#modal-info .accordion-header > .accordion-button[aria-expanded="false"]:hover {
+ background-color: rgb(from var(--modal-info-bg-light) r g b / calc(alpha*.4));
+}
+#modal-info .accordion-header > .accordion-button[aria-expanded="true"] {
+ background-color: var(--bs-accordion-btn-active-bg);
+}
+#modal-info ul.list-group > li.list-group-item {
+ padding: .3rem var(--modal-info-padding-x);
+ margin: 0 calc(var(--modal-info-padding-x)*-1);
+ border: none;
+}
+#modal-info ul.list-group > li.list-group-item:not(:first-child) {
+ border-top: var(--bs-list-group-border-width) solid var(--modal-info-bg-light);
+}
+#info-body > ul.list-group > li.list-group-item:last-child,
+#info-accordion > .accordion-item:not(:last-child) ul.list-group > li.list-group-item:last-child {
+ border-bottom: var(--bs-list-group-border-width) solid var(--modal-info-bg-light);
+}
+#modal-info .modal-body ul.list-group > li.list-group-item:not(.text-muted):hover {
+ background-color: rgb(from var(--modal-info-bg-light) r g b / calc(alpha*.4));
+}
+#info-body {
+ padding-top: 0;
+ padding-bottom: 0;
+}
+#info-body > ul.list-group > li.list-group-item p,
+#modal-info .accordion-body ul.list-group > li.list-group-item p {
+ margin: .05rem 0 0 0;
+}
+#info-body > ul.list-group > li.list-group-item h6,
+#modal-info .accordion-body ul.list-group > li.list-group-item h6 {
+ margin: .05rem 0 0 0;
+ font-size: 1.15rem
}
#modal-info .modal-body a {
color: inherit;
@@ -290,6 +341,12 @@ body.inprogress {
opacity: .8;
text-decoration: underline;
}
+#modal-info .modal-body .info-credits {
+ margin: 0 calc(var(--modal-info-padding-x)*-1);
+ padding: .3rem var(--modal-info-padding-x);
+ margin-top: .3rem;
+ border-top: var(--bs-list-group-border-width) solid var(--modal-info-bg-light);
+}
.ol-overlaycontainer-stopevent .modal-backdrop.show {
pointer-events: auto;