diff options
| -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;  | 
