diff options
author | Guilhem Moulin <guilhem@fripost.org> | 2025-05-27 12:27:16 +0200 |
---|---|---|
committer | Guilhem Moulin <guilhem@fripost.org> | 2025-06-04 17:14:05 +0200 |
commit | f2c3e05955bec324b7e2a011fdde3894aa53ebd5 (patch) | |
tree | 1bd0b4ce2a3c4a4c4a1c8ad1fc1a0edff87298ae | |
parent | 4c2dbeecd7bafa5e55893c939c800edc9eb5f4a7 (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.html | 73 | ||||
-rw-r--r-- | main.js | 229 | ||||
-rw-r--r-- | style.css | 61 |
3 files changed, 310 insertions, 53 deletions
@@ -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 - © <a href="https://sametinget.se" target="_blank">Sametingets</a> - Rennäringens markanvändningsdatabas (IRENMARK). - </li> - <li>Bakgrund kartor från - © <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: - © <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: - © <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>© <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>© 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>© 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> @@ -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 */ @@ -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; |