36 Commits
wms ... search

Author SHA1 Message Date
cb39695e6c Search adjustments + building techs 2026-05-24 16:21:33 +02:00
487995366e Add markers action to search results 2026-05-24 12:29:57 +02:00
f9daaefbdd Search results handling (WIP) 2026-05-23 23:43:48 +02:00
743bbc2f3b First search request attempt 2026-05-23 22:55:04 +02:00
0de6158652 Add publication in home page + minor changes 2026-05-23 17:41:37 +02:00
692ef13574 Search sidebar (WIP) 2026-05-22 21:57:23 +02:00
f83f95a51a Stub form controller 2026-05-22 18:39:40 +02:00
c313194e15 Bibliography for prehistoric 2026-05-22 16:07:18 +02:00
1a3c95487f Temp search form 2025-11-21 17:54:50 +01:00
b0915d0973 Simplify marker regex + link CSS 2025-11-12 17:16:53 +01:00
ebea10059e Prevent null in captions... 2025-11-10 10:15:11 +01:00
249a31d11a More marker changes... 2025-11-07 11:41:35 +01:00
4d95aa7fce More markers in site sheet... 2025-11-03 10:11:09 +01:00
e7f1ed7a98 Markers in localization 2025-10-30 15:27:25 +01:00
5cf42ff4be Update marker parsing regex 2025-10-30 09:36:53 +01:00
0f16af95e7 Update for internal links 2025-10-30 08:52:16 +01:00
13550078cb Open modal for internal links (WIP) 2025-10-29 18:23:41 +01:00
262540e735 Bloody whitespace... 2025-10-24 15:31:47 +02:00
c8de489ead Parse markers for other record types 2025-10-24 15:24:39 +02:00
952dc3f841 Parse marker shortcodes (WIP) 2025-10-24 10:27:28 +02:00
00cedfeb85 Fix undefined bug... 2025-09-03 15:54:34 +02:00
53c3f6c6b2 More refactoring components (minus Site) 2025-09-02 09:59:02 +02:00
77d42a2c27 Refactor components (WIP) 2025-09-01 17:20:47 +02:00
40cc2ef88b Update main menu 2025-08-25 11:15:22 +02:00
e6578225ec Populate biblio state for reuse data 2025-08-08 16:28:22 +02:00
986c4b0a75 Images for Reuse 2025-08-08 14:17:01 +02:00
f849f885f9 Add Reuse asset type 2025-08-08 12:06:38 +02:00
9196653c0d Another feature popup fix... 2025-07-15 16:11:44 +02:00
e3a3b30ade Merge branch 'master' of https://git.electricmandarine.cloud/nicolo/caprigis 2025-07-15 16:08:38 +02:00
065e49ccb2 Update GeoJSON layer and feature popup 2025-07-15 16:07:27 +02:00
106c8f60bc Update GeoJSON layers 2025-07-14 10:17:13 +02:00
e3d98c2e5d Move boundaries to base map control 2025-07-08 14:44:01 +02:00
377447f63a Fix stupid bug... 2025-07-08 12:21:10 +02:00
cfcd1e8e80 Render 3D reconstructions for sites 2025-07-08 12:07:08 +02:00
378e14d56a Add Villa Bismarck geojson; refactor layer options 2025-07-07 13:19:06 +02:00
f68216c84c WMS in layers control 2025-06-30 11:18:13 +02:00
26 changed files with 1189 additions and 405 deletions

View File

@@ -59,4 +59,4 @@ Photo Sphere Viewer, Three.js (da cui dipende Photo Sphere Viewer) e Stimulus.
## TODO
- [ ] Auto-discovery per Stimulus?
- [ ] Refactor con app state per evitare oggetti globali
- [x] Refactor con app state per evitare oggetti globali

View File

@@ -186,4 +186,9 @@ a:visited {
.marker-cluster-small span {
color: #fff;
}
/* Apply Bulma's is-danger color based on state */
input:invalid {
border-color: #ff6685;
}

BIN
img/pub/AC_36-1.jpg Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

View File

@@ -498,6 +498,22 @@
</div>
</div>
</article>
<article class="media mt-4 pb-6">
<figure class="media-left">
<p class="image is-128x128">
<img src="img/pub/AC_36-1.jpg" />
</p>
</figure>
<div class="media-content">
<div class="content">
<p>
Caratelli G., Giorgi C., Paraciani N. 2025,
<em>From archaeological survey to data accessibility: a WebGIS for the island of Capri</em>,
«Archeologia e Calcolatori», 36.1, 65-86 (<a href="https://doi.org/10.19282/ac.36.1.2025.04">https://doi.org/10.19282/ac.36.1.2025.04</a>)
</p>
</div>
</div>
</article>
</section>
<footer class="modal-card-foot">
</footer>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

@@ -26,7 +26,7 @@
<script src="js/index.js" type="module"></script>
<title>WebGIS Isola di Capri</title>
</head>
<body data-controller="menu"
<body data-controller="menu modal"
data-action="menu-ready@document->menu#buildMenu menu-ready@document->menu#buildCartographyMenu">
<nav class="navbar mb-0" role="navigation">
<div class="navbar-brand">
@@ -61,11 +61,13 @@
<span class="icon mr-2">
<i class="fa fa-map"></i>
</span>
Cartografia
Catasto storico
</button>
<button class="button is-outlined is-rounded is-link mr-4 mt-1" id="howto" title="Istruzioni">
<span class="icon is-large has-text-link">
<i class="fas fa-question fa-lg"></i>
<button class="button is-white mr-2 mt-1" title="Cerca"
data-id="search"
data-action="menu#toggleMenu">
<span class="icon" data-id="search" data-action="menu#toggleMenu">
<i data-id="search" class="fa fa-search"></i>
</span>
</button>
</div>
@@ -77,7 +79,74 @@
<div class="main columns">
<div class="column mb-0 pb-0 is-full is-relative">
<div class="pb-0 is-relative" id="map" aria-describedby="map-progress" aria-busy="true">
<progress id="map-progress" class="p-2 progress is-medium is-link" aria-label="Map loading..." />
<progress id="map-progress" class="p-2 progress is-medium is-link" aria-label="Caricamento mappa..." />
</div>
</div>
<div class="menu-overlay column is-hidden is-4 is-4-desktop is-5-mobile is-pulled-right is-overlay has-background-white-ter"
data-menu-target="search" data-controller="layer search">
<button title="Chiudi ricerca" class="delete is-pulled-right" data-action="menu#closeSearch"></button>
<h1 class="is-size-5">Ricerca</h1>
<form id="search-form" method="POST" data-search-target="search" data-action="submit->search#submitSearch">
<div class="field">
<label class="label">Testo libero</label>
<div class="control is-full-width">
<input class="input" type="text" minlength="3" name="text" placeholder="Inserire parole chiave" />
</div>
</div>
<div class="field">
<label class="label">Categoria sito</label>
<div class="control">
<div class="select">
<select name="category">
<option default value="">-- Scegliere la categoria del sito --</option>
<option value="site">Sito conservato</option>
<!--<option value="not_conserved">Sito non conservato</option>-->
</select>
</div>
</div>
</div>
<div class="field">
<label class="label">Tecnica muraria</label>
<div class="control">
<div class="select">
<select name="technique">
<option default value="">-- Scegliere tecnica --</option>
<option>Opera poligonale</option>
<option>Opera incerta</option>
<option>Opera reticolata</option>
<option>Opera laterizia</option>
<option>Opera mista</option>
<option>Opera cementizia</option>
</select>
</div>
</div>
</div>
<div class="field is-grouped mt-5 mb-0 pb-0">
<div class="control">
<button class="button is-link" type="submit">
<span>Cerca</span>
<span class="icon is-small">
<i class="fa fa-search"></i>
</span>
</button>
</div>
<div class="control">
<button class="button is-link is-light" type="reset" data-action="search#clearSearch">
<span>Cancella filtri</span>
<span class="icon is-small">
<i class="fa fa-times"></i>
</span>
</button>
</div>
</div>
</form>
<div class="content pt-2 mt-3 is-hidden" data-search-target="container">
<table class="table is-fullwidth is-striped">
<thead>
<tr><td class="has-text-centered has-text-weight-bold is-size-5" colspan="2">Risultati</td></tr>
</thead>
<tbody data-search-target="results"></tbody>
</table>
</div>
</div>
<div class="menu-overlay column is-hidden is-4 is-4-desktop is-5-mobile is-pulled-right is-overlay has-background-white-ter"
@@ -192,6 +261,30 @@
</span>
</li>
</ul>
<p class="menu-label is-size-5 is-clickable" data-id="reuse">
<span class="icon pl-1 mr-2 is-small" data-layer-target="reuse" data-action="click->layer#toggle">
<i class="fa fa-xs fa-eye-slash" data-layer-target="icon"></i>
</span>
<span class="icon pr-1">
<img class="image" src="img/icons/reimpiego_menu.png"/>
</span>
<span role="button" data-action="click->menu#toggle" data-id="reuse">
Reimpiego
<span class="icon pl-2">
<i class="fa fa-chevron-right" data-menu-target="icon" data-id="reuse"></i>
</span>
</span>
</p>
<ul class="menu-list is-hidden" id="reuse-list" data-menu-target="list" data-controller="marker">
<li data-list-id="reuse-capri-sub">
<span role="button" class="is-clickable" data-action="click->menu#openSubList" data-list-id="reuse-capri-sub">
Capri
<span class="icon ml-2">
<i class="fa fa-chevron-right" data-menu-target="icon"></i>
</span>
</span>
</li>
</ul>
<p class="menu-label is-size-5 is-clickable" data-id="prehistoric">
<span class="icon pl-1 mr-2 is-small" title="Nascondi" data-layer-target="prehistoric" data-action="click->layer#toggle">
<i class="fa fa-xs fa-eye-slash" data-layer-target="icon"></i>
@@ -284,23 +377,10 @@
</template>
<aside class="menu ml-4 mt-3" data-id="cartography-aside">
<button title="Chiudi menu" class="delete is-pulled-right" data-action="menu#closeCartography"></button>
<ul class="menu-list">
<li>
<input type="checkbox" data-controller="layer" data-action="layer#toggleCadastral" />
Catasto Agenzia delle Entrate (particelle e fabbricati)
</li>
</ul>
<p class="menu-label is-size-5 mt-4 is-clickable" data-id="historic">
<span role="button" data-action="click->menu#toggle" data-id="historic">
Catasto storico
<span class="icon pl-2">
<i class="fa fa-chevron-right" data-menu-target="icon" data-id="historic"></i>
</span>
</span>
</p>
</aside>
</div>
</div>
</div>
<!-- Bibliography citations template -->
<template id="biblio-item-template">
<span class="is-clickable has-text-link"
@@ -452,12 +532,63 @@
<button class="modal-close is-large" aria-label="close" data-action="modal#close tabs#reset"></button>
</div>
<!-- Underwater modal -->
<div class="modal" id="underwater-data" data-controller="modal biblio marker" data-modal-target="modal">
<div class="modal-background" data-action="click->modal#close"></div>
<div class="modal" id="underwater-data" data-controller="modal tabs marker" data-modal-target="modal">
<div class="modal-background" data-action="click->modal#close click->tabs#reset"></div>
<div class="modal-content has-background-white">
<div id="underwater-sheet"></div>
<div class="tabs is-centered">
<ul>
<li class="is-active" id="for-underwater-sheet" data-tabs-target="tab active" data-action="click->tabs#activate">
<a>
<span class="icon is-small"><i class="fas fa-info-circle" aria-hidden="true"></i></span>
<span>Scheda</span>
</a>
</li>
<li id="for-photos" data-tabs-target="tab" data-action="click->tabs#activate">
<a>
<span class="icon is-small"><i class="fas fa-image" aria-hidden="true"></i></span>
<span>Immagini</span>
</a>
</li>
<li id="for-documents" data-tabs-target="tab" data-action="click->tabs#activate">
<a>
<span class="icon is-small"><i class="fas fa-book" aria-hidden="true"></i></span>
<span>Documenti</span>
</a>
</li>
</ul>
</div>
<div class="data-tabs" id="underwater-sheet" data-tabs-target="content"></div>
<div class="data-tabs is-hidden" id="photos" data-tabs-target="content"></div>
<div class="data-tabs is-hidden" id="documents" data-tabs-target="content"></div>
</div>
<button class="modal-close is-large" aria-label="close" data-action="modal#close"></button>
<button class="modal-close is-large" aria-label="close" data-action="modal#close tabs#reset"></button>
</div>
<!-- Reuse modal -->
<div class="modal" id="reuse-data" data-controller="modal biblio tabs marker" data-modal-target="modal">
<div class="modal-background" data-action="click->modal#close click->tabs#reset"></div>
<div class="modal-content has-background-white">
<div class="tabs is-centered">
<ul>
<li class="is-active" id="for-reuse-sheet" data-tabs-target="tab active" data-action="click->tabs#activate">
<a>
<span class="icon is-small"><i class="fas fa-info-circle" aria-hidden="true"></i></span>
<span>Scheda</span>
</a>
</li>
<li id="for-photos" data-tabs-target="tab" data-action="click->tabs#activate">
<a>
<span class="icon is-small"
><i class="fas fa-image" aria-hidden="true"></i
></span>
<span>Immagini</span>
</a>
</li>
</ul>
</div>
<div class="data-tabs" id="reuse-sheet" data-tabs-target="content"></div>
<div class="data-tabs is-hidden" id="photos" data-tabs-target="content"></div>
</div>
<button class="modal-close is-large" aria-label="close" data-action="modal#close tabs#reset"></button>
</div>
<!-- Spherical photo modal -->
<div class="modal" id="spherical-modal">

View File

@@ -1,4 +1,4 @@
import { GisState } from "../state.js";
import Utils from "./utils.js";
/**
* @class Finding
*/
@@ -23,7 +23,7 @@ export class Finding {
<strong>Misure:</strong> ${this._data.measurements}
</p>
<p class="p-2">
<strong>Luogo e anno rinvenimento:</strong> ${this._data.place}. ${this._data.year}
<strong>Luogo e anno rinvenimento:</strong> ${Utils.parseMarkers(this._data.place)}. ${this._data.year}
</p>
<p class="p-2">
<strong>Datazione:</strong> ${this._data.dating}
@@ -36,7 +36,7 @@ export class Finding {
</p>
<p class="mt-4 pl-2 pr-5">
<strong class="pb-3">Descrizione</strong></br>
${this._data.description}
${Utils.parseMarkers(this._data.description)}
</p>
<p class="mt-4 pl-2 pr-5">
<span class="icon has-text-link">
@@ -50,64 +50,25 @@ export class Finding {
</p>
</div>`;
}
renderImages() {
let content = `<div class="content has-text-centered">
<p class="is-size-5 mt-3">Immagini</p>`;
content += `
<div style="max-width: 70%; margin: 0 auto">
<p class="is-size-6 has-text-centered">Cliccare sull'immagine per aprire la gallery</p>
<figure class="is-relative is-clickable has-text-centered" id="finding-gallery">
<img src="img/${this.images[0].filename}" width="300"/>
<div class="icon overlay is-flex is-justify-content-center is-align-items-center">
<i class="is-flex fa fa-2x fa-play-circle"></i>
</div>
</figure>
</div>
</div>`;
return content;
}
/**
* @param {HTMLElement} imageContainer
* @param {Function} gallery
*/
async setImages(imageContainer, gallery) {
let record = await this.fetchData(`${GisState.apiUrl}/finding/${this._data.id}`)
if (record.images.length) {
this.images = record.images;
imageContainer.innerHTML = this.renderImages();
gallery('finding-gallery', this.images);
}
if (this._data.images?.length) {
imageContainer.innerHTML = Utils.renderImages('finding-gallery', this._data.images);
gallery('finding-gallery', this._data.images);
} else
imageContainer.innerHTML = '<p class="has-text-centered">Nessuna risorsa visuale disponibile</p>';
}
/**
* @param {number} recordId
*/
async biblio(recordId) {
let finding = await this.fetchData(`${GisState.apiUrl}/finding/${recordId}`);
let {citations, biblioElements} = await Utils.buildBibliography('finding', recordId);
this.biblioElements = biblioElements;
let citations = '';
if (finding.bibliography.length) {
finding.bibliography.forEach(record => {
citations += `
<span class="is-clickable has-text-link"
data-action="click->biblio#open"
id="cit-${record.id}">
${record.citation}</span>`;
citations += record.pages?.length ? `, ${record.pages};` : ';';
this.biblioElements.push(`
<div class="p-2 mt-2" id="ref-${record.id}">
<p class="p-3">${record.reference}</p>
</div>
`);
});
}
return citations.trim().slice(0, -1);
return citations;
}
getReference(id) {
@@ -116,8 +77,4 @@ export class Finding {
return ref.match(regex);
});
}
async fetchData(url) {
return await fetch(url).then(res => res.json());
}
}

View File

@@ -1,4 +1,4 @@
import { GisState } from "../state.js";
import Utils from "./utils.js";
/**
* Component to render data for not conserved assets
@@ -32,11 +32,11 @@ export class NotConserved {
<span class="icon has-text-link">
<i class="fa fa-map"></i>
</span>
<strong>Località generica:</strong> ${this._data.genericLocation}
<strong>Località generica:</strong> ${Utils.parseMarkers(this._data.genericLocation)}
</p>
<p class="mt-4 pl-2 pr-5">
<strong class="pb-3">Descrizione</strong></br>
${this._data.shortDescription}
${Utils.parseMarkers(this._data.shortDescription)}
</p>
<p class="mt-4 pl-2 pr-5">
<span class="icon has-text-link">
@@ -51,116 +51,27 @@ export class NotConserved {
</div>`;
}
renderImages() {
let content = `<div class="content has-text-centered">
<p class="is-size-5 mt-3">Immagini</p>`;
content += `
<div style="max-width: 70%; margin: 0 auto">
<p class="is-size-6 has-text-centered">Cliccare sull'immagine per aprire la gallery</p>
<figure class="is-relative is-clickable has-text-centered" id="not-conserved-gallery">
<img src="img/${this.images[0].filename}" width="300"/>
<div class="icon overlay is-flex is-justify-content-center is-align-items-center">
<i class="is-flex fa fa-2x fa-play-circle"></i>
</div>
</figure>
</div>
</div>`;
return content;
}
/**
* @param {HTMLElement} imageContainer
* @param {Function} gallery
*/
async setImages(imageContainer, gallery) {
if (this._data.images.length) {
this.images = this._data.images;
imageContainer.innerHTML = this.renderImages();
gallery('not-conserved-gallery', this.images);
setImages(imageContainer, gallery) {
if (this._data.images?.length) {
imageContainer.innerHTML = Utils.renderImages('not-conserved-gallery', this._data.images);
gallery('not-conserved-gallery', this._data.images);
} else
imageContainer.innerHTML = '<p class="has-text-centered">Nessuna risorsa visuale disponibile</p>';
}
async renderDocs() {
let record = await this.fetchData(`${GisState.apiUrl}/not_conserved/${this._data.id}`);
this.documentation = record.documents.filter(d => d.type === 'documentazione')
this.publications = record.documents.filter(d => d.type === 'pubblicazione');
let content = `
<div class="has-bottom-border">
<div class="p-2">
<table class="p-4 table is-fullwidth is-striped">
<thead>
<tr><th colspan=3 class="p-2 has-text-centered is-size-5">Documentazione di archivio</th>
<tr><th>Titolo</th><th>Luogo di conservazione</th><th>Download</th></tr>
</thead>
<tbody>
`;
for (const doc of this.documentation) {
content += `
<tr><td>${doc.title}</td><td>${doc.conservationPlace}</td><td><a class="button is-link has-text-white" href="docs/${doc.filename}">
<i class="fa fa-download mr-2"></i> PDF
</a></td></tr>
`;
}
if (this.publications.length) {
content += `
</tbody>
<thead>
<tr><th colspan=3 class="p-2 has-text-centered is-size-5">Pubblicazioni del progetto Carta Archeologica</th>
<tr><th>Titolo</th><th>Autori</th><th>Download</th></tr>
</thead>
<tbody>
`;
for (const doc of this.publications) {
content += `
<tr><td>${doc.title}</td><td>${doc.authors}</td><td><a class="button is-link has-text-white" href="docs/${doc.filename}">
<i class="fa fa-download mr-2"></i> PDF
</a></td></tr>
`;
}
}
content += `
</tbody>
</table>
</div>
</div>
`;
if (!this.publications.length && !this.documentation.length) {
content = '<p class="has-text-centered">Nessun documento disponibile.</p>';
}
return content;
return await Utils.generateDocsTable(this._data, 'not_conserved');
}
async biblio(recordId) {
let record = await this.fetchData(`${GisState.apiUrl}/not_conserved/${recordId}`);
let {citations, biblioElements} = await Utils.buildBibliography('not_conserved', recordId);
this.biblioElements = biblioElements;
let citations = '';
if (record.bibliography.length) {
record.bibliography.forEach(record => {
citations += `
<span class="is-clickable has-text-link"
data-action="click->biblio#open"
id="cit-${record.id}">
${record.citation.trim()}</span>`;
citations += record.pages?.length ? `, ${record.pages};` : ';';
this.biblioElements.push(`
<div class="p-2 mt-2" id="ref-${record.id}">
<p class="p-3">${record.reference}</p>
</div>
`
);
});
}
return citations.trim().slice(0, -1);
return citations;
}
getReference(id) {
@@ -169,8 +80,4 @@ export class NotConserved {
return ref.match(regex);
});
}
async fetchData(url) {
return await fetch(url).then(res => res.json());
}
}

View File

@@ -1,4 +1,4 @@
import { GisState } from "../state.js";
import Utils from "./utils.js";
/**
* @class Prehistoric
*/
@@ -32,11 +32,11 @@ export class Prehistoric {
<span class="icon has-text-link">
<i class="fa fa-map"></i>
</span>
<strong>Località generica:</strong> ${this._data.genericPlace}
<strong>Località generica:</strong> ${Utils.parseMarkers(this._data.genericPlace)}
</p>
<p class="mt-4 pl-2 pr-5">
<strong class="pb-3">Descrizione breve</strong></br>
${this._data.description}
${Utils.parseMarkers(this._data.description)}
</p>
<p class="p-2">
<strong>Conservazione:</strong> ${this._data.conservation}
@@ -44,10 +44,16 @@ export class Prehistoric {
<p class="p-2 mb-4">
<strong>Autore scheda:</strong> ${this._data.author}
</p>
<p class="mt-4 pl-2 pr-5">
<span class="icon has-text-link">
<i class="fa fa-book"></i>
</span>
<strong>Bibliografia:</strong> ${await this.biblio(this._data.id)}
</p>
<div class="notification is-light mx-3 mt-4 mb-0 p-2 is-hidden" data-biblio-target="biblio"></div>
</div>`;
/*
<p class="mt-4 pl-2 pr-5">
<span class="icon has-text-link">
<i class="fa fa-book"></i>
@@ -58,32 +64,14 @@ export class Prehistoric {
*/
}
renderImages() {
let content = `<div class="content has-text-centered">
<p class="is-size-5 mt-3">Immagini</p>`;
content += `
<div style="max-width: 70%; margin: 0 auto">
<p class="is-size-6 has-text-centered">Cliccare sull'immagine per aprire la gallery</p>
<figure class="is-relative is-clickable has-text-centered" id="prehist-gallery">
<img src="img/${this.images[0].filename}" width="300"/>
<div class="icon overlay is-flex is-justify-content-center is-align-items-center">
<i class="is-flex fa fa-2x fa-play-circle"></i>
</div>
</figure>
</div>
</div>`;
return content;
}
/**
* @param {HTMLElement} imageContainer
* @param {Function} gallery
*/
async setImages(imageContainer, gallery) {
if (this._data.images.length) {
this.images = this._data.images;
imageContainer.innerHTML = this.renderImages();
gallery('prehist-gallery', this.images);
if (this._data.images?.length) {
imageContainer.innerHTML = Utils.renderImages('prehist-gallery', this._data.images);
gallery('prehist-gallery', this._data.images);
} else
imageContainer.innerHTML = '<p class="has-text-centered">Nessuna risorsa visuale disponibile</p>';
}
@@ -91,29 +79,10 @@ export class Prehistoric {
* @param {number} recordId
*/
async biblio(recordId) {
let finding = await this.fetchData(`${GisState.apiUrl}/prehistoric/${recordId}`);
let {citations, biblioElements} = await Utils.buildBibliography('prehistoric', recordId);
this.biblioElements = biblioElements;
let citations = '';
if (finding.bibliography.length) {
finding.bibliography.forEach(record => {
citations += `
<span class="is-clickable has-text-link"
data-action="click->biblio#open"
id="cit-${record.id}">
${record.citation}</span>`;
citations += record.pages?.length ? `, ${record.pages};` : ';';
this.biblioElements.push(`
<div class="p-2 mt-2" id="ref-${record.id}">
<p class="p-3">${record.reference}</p>
</div>
`);
});
}
return citations.trim().slice(0, -1);
return citations;
}
getReference(id) {
@@ -122,8 +91,4 @@ export class Prehistoric {
return ref.match(regex);
});
}
async fetchData(url) {
return await fetch(url).then(res => res.json());
}
}

View File

@@ -0,0 +1,81 @@
import Utils from "./utils.js";
/**
* @class Reuse
*/
export class Reuse {
biblioElements = [];
images = null;
set data(data) {
this._data = data;
}
async render() {
return `
<div class="container px-4 pt-4">
<p class="p-2">
<strong>Denominazione:</strong> ${this._data.denomination}
</p>
<p class="p-2">
<strong>Materia:</strong> ${this._data.material}
</p>
<p class="p-2">
<strong>Misure:</strong> ${this._data.measurements}
</p>
<p class="p-2">
<strong>Luogo di conservazione:</strong> ${this._data.conservationPlace}
</p>
<p class="p-2">
<strong>Stato di conservazione:</strong> ${this._data.conservationState}
</p>
<p class="p-2">
<strong>Luogo e anno rinvenimento:</strong> ${Utils.parseMarkers(this._data.finding)}
</p>
<p class="p-2">
<strong>Datazione:</strong> ${this._data.dating}
</p>
<p class="mt-4 pl-2 pr-5">
<strong class="pb-3">Descrizione</strong></br>
${Utils.parseMarkers(this._data.description)}
</p>
<p class="mt-4 pl-2 pr-5">
<span class="icon has-text-link">
<i class="fa fa-book"></i>
</span>
<strong>Bibliografia:</strong> ${await this.biblio(this._data.id)}
</p>
<div class="notification is-light mx-3 mt-4 mb-0 p-2 is-hidden" data-biblio-target="biblio"></div>
<p class="p-2 mb-4">
<strong>Autore scheda:</strong> ${this._data.author}
</p>
</div>`;
}
/**
* @param {HTMLElement} imageContainer
* @param {Function} gallery
*/
async setImages(imageContainer, gallery) {
if (this._data.images?.length) {
imageContainer.innerHTML = Utils.renderImages('reuse-gallery', this._data.images);
gallery('reuse-gallery', this._data.images);
} else
imageContainer.innerHTML = '<p class="has-text-centered">Nessuna risorsa visuale disponibile</p>';
}
/**
* @param {Number} recordId
*/
async biblio(recordId) {
let {citations, biblioElements} = await Utils.buildBibliography('reuse', recordId);
this.biblioElements = biblioElements;
return citations;
}
getReference(id) {
return this.biblioElements.find(ref => {
let regex = new RegExp('ref-'+id+'"');
return ref.match(regex);
});
}
}

View File

@@ -43,4 +43,21 @@ export class SiteMedia {
</div>
`;
}
renderReconstructions() {
return `
<div class="content has-text-centered mb-5 pb-5">
<p class="is-size-5 mt-3">Ricostruzioni 3D</p>
<div style="max-width: 70%; margin: 0 auto">
<p class="is-size-6 has-text-centered">Gallery ricostruzioni 3D</p>
<figure class="is-relative is-clickable has-text-centered" id="gallery-3d">
<img src="img/${this._siteData.filename}" width="300"/>
<div class="icon overlay is-flex is-justify-content-center is-align-items-center">
<i class="is-flex fa fa-2x fa-play-circle"></i>
</div>
</figure>
</div>
</div>
`;
}
}

View File

@@ -1,14 +1,10 @@
import Utils from "./utils.js";
/**
* Component to render data for site sheet
* @class SiteSheet
*/
export class SiteSheet {
biblioElements = [];
/*
constructor(data) {
this._siteData = data;
}
*/
/**
* @param {object} data
*/
@@ -16,9 +12,13 @@ export class SiteSheet {
this._siteData = data;
}
/**
* @todo Refactor!
* @returns {string} HTML
*/
render() {
const description = Utils.parseMarkers(this._siteData.description);
const localization = Utils.parseMarkers(this._siteData.localization);
return `<div class="container has-bottom-border">
<table class="table is-fullwidth is-striped">
<tr class="is-link"><th class="is-size-5 has-text-centered" colspan=2>Identificazione</th></tr>
@@ -28,7 +28,7 @@ export class SiteSheet {
<tr><th>Comune</th><td>${this._siteData.municipality}</td></tr>
<tr><th>Indirizzo</th><td>${this._siteData.address}</td></tr>
<tr><th>Località</th><td>${this._siteData.place}</td></tr>
<tr><th>Localizzazione</th><td>${this._siteData.localization}</td></tr>
<tr><th>Localizzazione</th><td>${localization}</td></tr>
<tr class="is-link"><th class="is-size-5 has-text-centered" colspan=2>Cronologia</th></tr>
<tr><th>Periodo</th><td>${this._siteData.period}</td></tr>
<tr><th>Fase</th><td>${this._siteData.phase}</td></tr>
@@ -38,13 +38,13 @@ export class SiteSheet {
<tr><th>Stato di conservazione</th><td>${this._siteData.conservationState}</td></tr>
<tr><th>Tecniche edilizie impiegate:</th><td>${this._siteData.techniques}</td></tr>
<tr class="is-link"><th class="is-size-5 has-text-centered" colspan=2>Ritrovamento e materiali</th></tr>
<tr><th>Anno di ritrovamento</th><td>${this._siteData.finding}</td></tr>
<tr><th>Modalità e anno di ritrovamento</th><td>${Utils.parseMarkers(this._siteData.finding)}</td></tr>
<tr><th>Materiali rinvenuti</th><td>${this._siteData.materials}</td></tr>
<tr><th>Luogo custodia materiali</th><td>${this._siteData.conservationPlace}</td></tr>
<tr><th>Luogo custodia materiali</th><td>${Utils.parseMarkers(this._siteData.conservationPlace)}</td></tr>
<tr class="is-link"><th class="is-size-5 has-text-centered" colspan=2>Rilievi</th></tr>
<tr><td colspan=2>${this._siteData.surveys ?? 'Nessun rilievo'}</td></tr>
<tr class="is-link"><th class="is-size-5 has-text-centered" colspan=2>Descrizione</th></tr>
<tr><td class="pr-6 pl-6 pt-3" colspan="2">${this._siteData.description}</td></tr>
<tr><td class="pr-6 pl-6 pt-3" colspan="2">${description}</td></tr>
<tr class="is-link"><th class="is-size-5 has-text-centered" colspan=2>Bibliografia</th></tr>
<tr>
<td colspan=2>
@@ -60,6 +60,8 @@ export class SiteSheet {
}
renderShort() {
const shortDesc = Utils.parseMarkers(this._siteData.shortDescription);
return `
<div class="container p-3">
<p class="p-2">
@@ -81,7 +83,7 @@ export class SiteSheet {
<strong>Località generica:</strong> ${this._siteData.genericPlace}
</p>
<p class="mt-4 pl-2 pr-5">
${this._siteData.shortDescription}
${shortDesc}
</p>
<p class="mt-4 pl-2 pr-5">
<span class="icon has-text-link">

View File

@@ -1,3 +1,4 @@
import Utils from "./utils.js";
/**
* @class Underwater
*/
@@ -27,15 +28,26 @@ export class Underwater {
</p>
<p class="mt-4 pl-2 pr-5">
<strong class="pb-3">Descrizione breve</strong></br>
${this._data.shortDescription}
${Utils.parseMarkers(this._data.shortDescription)}
</p>
<p class="p-2 mb-4">
<strong>Autore scheda:</strong> ${this._data.author}
</p>
</div>`;
}
/**
* @param {HTMLElement} imageContainer
* @param {Function} gallery
*/
setImages(imageContainer, gallery) {
if (this._data.images?.length) {
imageContainer.innerHTML = Utils.renderImages('underwater-gallery', this._data.images);
gallery('underwater-gallery', this._data.images);
} else
imageContainer.innerHTML = '<p class="has-text-centered">Nessuna risorsa visuale disponibile</p>';
}
async fetchData(url) {
return await fetch(url).then(res => res.json());
async renderDocs() {
return await Utils.generateDocsTable(this._data, 'underwater');
}
}

View File

@@ -0,0 +1,175 @@
import { GisState } from "../state.js";
/**
* @namespace Utils
*/
const Utils = {};
/**
*
* @param {string} galleryId The image gallery's id
* @param {Object} imagesData
* @returns {string}
*/
Utils.renderImages = function (galleryId, imagesData) {
let content = `<div class="content has-text-centered">
<p class="is-size-5 mt-3">Immagini</p>`;
content += `
<div style="max-width: 70%; margin: 0 auto">
<p class="is-size-6 has-text-centered">Cliccare sull'immagine per aprire la gallery</p>
<figure class="is-relative is-clickable has-text-centered" id="${galleryId}">
<img src="img/${imagesData[0].filename}" width="300"/>
<div class="icon overlay is-flex is-justify-content-center is-align-items-center">
<i class="is-flex fa fa-2x fa-play-circle"></i>
</div>
</figure>
</div>
</div>`;
return content;
}
/**
* @param {Object} imagesData
* @param {HTMLElement} imageContainer
* @param {Function} galleryGenerator The function that creates the image gallery
* @param {string} galleryId The image gallery's id
*/
Utils.setImages = function(imagesData, imageContainer, galleryGenerator, galleryId) {
imageContainer.innerHTML = Utils.renderImages(galleryId);
galleryGenerator(galleryId, imagesData);
}
/**
*
* @param {Object} data The component's data
* @param {String} resourceUri The resource URI to be used for API calls
* @returns {String} The table HTML
*/
Utils.generateDocsTable = async function(data, resourceUri) {
let record = await Utils.fetchData(`${GisState.apiUrl}/${resourceUri}/${data.id}`);
// TODO Horrible??
if (record instanceof Error) return '<p class="has-text-centered">Nessun documento disponibile.</p>';
const documentation = record.documents.filter(d => d.type === 'documentazione')
const publications = record.documents.filter(d => d.type === 'pubblicazione');
let content = `
<div class="has-bottom-border">
<div class="p-2">
<table class="p-4 table is-fullwidth is-striped">
<thead>
<tr><th colspan=3 class="p-2 has-text-centered is-size-5">Documentazione di archivio</th>
<tr><th>Titolo</th><th>Luogo di conservazione</th><th>Download</th></tr>
</thead>
<tbody>
`;
for (const doc of documentation) {
content += `
<tr><td>${doc.title}</td><td>${doc.conservationPlace}</td><td><a class="button is-link has-text-white" href="docs/${doc.filename}">
<i class="fa fa-download mr-2"></i> PDF
</a></td></tr>
`;
}
if (publications.length) {
content += `
</tbody>
<thead>
<tr><th colspan=3 class="p-2 has-text-centered is-size-5">Pubblicazioni del progetto Carta Archeologica</th>
<tr><th>Titolo</th><th>Autori</th><th>Download</th></tr>
</thead>
<tbody>
`;
for (const doc of publications) {
content += `
<tr><td>${doc.title}</td><td>${doc.authors}</td><td><a class="button is-link has-text-white" href="docs/${doc.filename}">
<i class="fa fa-download mr-2"></i> PDF
</a></td></tr>
`;
}
}
content += `
</tbody>
</table>
</div>
</div>
`;
if (publications.length === 0 && documentation.length === 0) {
content = '<p class="has-text-centered">Nessun documento disponibile.</p>';
}
return content;
}
/**
*
* @param {String} recordUri The record URI used for API calls
* @param {Number} recordId This record's ID
* @returns {{citations:String,biblioElements:String[]}}
*/
Utils.buildBibliography = async function(recordUri, recordId) {
let record = await Utils.fetchData(`${GisState.apiUrl}/${recordUri}/${recordId}`);
let biblioElements = [];
let citations = '';
if (record.bibliography.length) {
record.bibliography.forEach(record => {
citations += `
<span class="is-clickable has-text-link"
data-action="click->biblio#open"
id="cit-${record.id}">
${record.citation.trim()}
</span>
`;
citations += record.pages?.length ? `, ${record.pages};` : ';';
biblioElements.push(`
<div class="p-2 mt-2" id="ref-${record.id}">
<p class="p-3">${record.reference}</p>
</div>
`
);
});
}
const bibliography = {
citations: citations.trim().slice(0, -1),
biblioElements
}
return bibliography;
}
/**
* Parse marker strings (pseudo-shortcodes) and convert them
* to Stimulus links
* @param {String} text - The content text from database
*/
Utils.parseMarkers = function(text) {
const regex = /(?<marker>\[marker coords=\"(?<coords>[\d\s\.]+)\"\ ?(group=\"(?<group>\w+)\")?](?<content>[\s\S]+?)\[\/marker\])/mig;
if (!text) return text;
let matches = [...text.matchAll(regex)];
if (matches.length) {
matches.forEach(match => {
const replacement = `<a class="has-text-link" data-action="marker#go modal#close tabs#reset marker#goAndOpen" data-controller="marker"
data-marker-coords-value="${match.groups.coords}"
data-marker-group-value="${match.groups.group}">${match.groups.content.trim()}</a>`;
text = text.replace(match.groups.marker, replacement.trim());
});
}
return text;
}
Utils.fetchData = async function(url) {
return await fetch(url).then(res => res.ok ? res.json() : new Error())
.catch(err => console.log(err));
}
export default Utils;

View File

@@ -1,5 +1,6 @@
import { Controller } from "@hotwired/stimulus";
import { GisState, getMarkerByCoords } from "../state.js";
import UI from "../ui.js";
export default class extends Controller {
static values = {
@@ -7,18 +8,17 @@ export default class extends Controller {
'group': String,
'id': String,
};
END_ZOOM = 19;
// Animation breaks automatic tooltip opening...
mapAnimate = {
animate: true,
duration: 1,
easeLinearity: 0.25
uiModals = {
sites: '#site-data',
notConserved: '#not-conser-data',
finding: '#finding-data',
prehist: '#prehist-data',
reuse: '#reuse-data',
};
/**
* @param {Event} event
*/
END_ZOOM = 19;
go() {
let map = GisState.map;
const coords = this.coordsValue.split(' ');
@@ -31,8 +31,40 @@ export default class extends Controller {
let marker = this.getMarker(map, coords);
// DEBUG for sites
if (this.groupValue) marker = getMarkerByCoords(coords, this.groupValue);
//if (this.groupValue) marker = getMarkerByCoords(coords, this.groupValue);
marker?.openTooltip();
return marker;
}
/**
* Go to a marker location on the map
* and open its modal
*/
goAndOpen() {
const marker = this.go();
const selector = this.uiModals[this.groupValue];
const data = marker.options.data;
switch(this.groupValue) {
case 'sites':
UI.openSiteModal(data, selector);
break;
case 'notConserved':
UI.openNotConserModal(data, selector);
break;
case 'finding':
UI.openFindingModal(data, selector);
break;
case 'prehist':
UI.openPrehistModal(data, selector);
break;
case 'reuse':
UI.openReuseModal(data, selector);
break;
default:
console.log('Cannot open modal...');
break;
}
}
/**
* @param {L.Map} map

View File

@@ -6,6 +6,7 @@ export default class extends Controller {
'list',
'menu',
'cartography',
'search',
'icon'
];
@@ -86,12 +87,17 @@ export default class extends Controller {
toggleMenu(event) {
const menuId = event.target.dataset.id;
console.debug(menuId, event.target);
// Stupid...
if (menuId === 'main') {
this.menuTarget.classList.toggle('is-hidden');
if (!this.cartographyTarget.classList.contains('is-hidden')) {
this.cartographyTarget.classList.add('is-hidden');
}
if (!this.searchTarget.classList.contains('is-hidden')) {
this.searchTarget.classList.add('is-hidden');
}
}
if (menuId === 'cartography') {
@@ -99,6 +105,18 @@ export default class extends Controller {
if (!this.menuTarget.classList.contains('is-hidden')) {
this.menuTarget.classList.add('is-hidden');
}
if (!this.searchTarget.classList.contains('is-hidden')) {
this.searchTarget.classList.add('is-hidden');
}
}
if (menuId === 'search') {
this.searchTarget.classList.toggle('is-hidden');
if (!this.menuTarget.classList.contains('is-hidden')) {
this.menuTarget.classList.add('is-hidden');
}
if (!this.cartographyTarget.classList.contains('is-hidden')) {
this.cartographyTarget.classList.add('is-hidden');
}
}
}
@@ -110,6 +128,10 @@ export default class extends Controller {
this.cartographyTarget.classList.add('is-hidden');
}
closeSearch() {
this.searchTarget.classList.add('is-hidden');
}
toggleList(id) {
document.querySelector(`#${id}`).classList.toggle('is-hidden');
}

View File

@@ -0,0 +1,139 @@
import { Controller } from "@hotwired/stimulus";
import { GisState } from "../state.js";
import UI from "../ui.js";
const html = String.raw;
export default class extends Controller {
static targets = [
'search',
'results',
'clear',
'container',
];
/**
*
* @param {Event} event
*/
async submitSearch(event) {
event.preventDefault();
const data = new FormData(event.target);
const body = {};
const map = GisState.map;
const techs = GisState.layers.buildingTechs;
const techsMarkers = GisState.markers.buildingTechs;
// Reset search for building techs...
for (const key of Object.keys(techsMarkers)) {
map.removeLayer(techsMarkers[key]);
}
for (const entry of data.entries()) {
body[entry[0]] = entry[1];
}
const response = await fetch(`${GisState.apiUrl}/search?` + new URLSearchParams(body));
const results = await response.json();
console.warn(body);
this.containerTarget.classList.remove('is-hidden');
this.#injectResults(results);
if (results.length) {
this.#filterMap(results);
// Should technique always be shown after a search?
for (const key of Object.keys(techsMarkers)) {
if (techsMarkers[key].options.label === body.technique)
map.addLayer(techsMarkers[key]);
}
}
}
clearSearch() {
const map = GisState.map;
// Restore layer groups in map
for (const key of Object.keys(GisState.layers)) {
map.addLayer(GisState.layers[key]);
}
// Empty result set
this.resultsTarget.innerHTML = '';
this.containerTarget.classList.add('is-hidden');
}
#injectResults(results) {
/**
* @type {HTMLOutputElement} output
*/
const output = this.resultsTarget;
output.innerHTML = '';
if (results.length === 0) {
output.innerHTML = html`
<p class="has-background-white-bis p-4 mt-0 has-text-centered">
Nessun risultato trovato per i parametri di ricerca
</p>
`;
}
const sites = GisState.markers.sites;
for (const result of results) {
let coordinates = ''
for (let key of Object.keys(sites)) {
if (sites[key].options.data.label === result.label) {
coordinates = key;
}
}
// TODO The group value should be dynamic!!
const item = html`
<tr>
<td class="pt-4">${result.label}</td>
<td>
<button class="button is-link"
data-controller="marker"
data-action="marker#go"
data-marker-coords-value="${coordinates}"
data-marker-group-value="sites">
Vai al sito
<span class="ml-1 icon">
<i class="fa fa-chevron-right"></i>
</span>
</button>
</td>
</tr>
`;
output.innerHTML += item;
}
}
/**
*
* @param {Array<Object>} results
*/
#filterMap(results) {
const map = GisState.map;
const labels = [];
results.forEach(r => labels.push(r.label));
// Remove all layer groups first
for (const key of Object.keys(GisState.layers)) {
map.removeLayer(GisState.layers[key]);
}
const sites = GisState.markers.sites;
for (let key of Object.keys(sites)) {
// If map has layers from previous search results...
map.removeLayer(sites[key]);
for (const label of labels) {
if (sites[key].options.data.label === label) {
map.addLayer(sites[key]);
}
}
}
}
}

View File

@@ -5,6 +5,7 @@ import API_CONFIG from "./config.js";
import Icons from "./icons.js";
import { SphericalPhoto } from "./components/SphericalPhoto.js";
import { GisState } from "./state.js";
import Options from "./layer_options.js";
const MAPBOX_TOKEN = 'pk.eyJ1Ijoibmljb3BhIiwiYSI6ImNseWNwZjJjbjFidzcya3BoYTU0bHg4NnkifQ.3036JnCXZTEMt6jVgMzVRw';
const BASE_URL = location.href;
@@ -28,47 +29,6 @@ GIS.INIT_ZOOM = 14;
GIS.MIN_ZOOM = 11;
GIS.MAX_ZOOM = 24;
const optionsVincoli = {
color: '#222',
opacity: 0.8,
weight: 1,
fillColor: '#fa7861',
fillOpacity: 0.8
};
const optionsSiti = {
color: '#800040',
opacity: 1,
weight: 1.5,
fillColor: '#800040',
fillOpacity: 0.8
};
const optionsGrotta = {
color: '#205dac',
opacity: 1,
weight: 1.5,
fillColor: '#205dac',
fillOpacity: 0.8
}
const optionsPaesistici = {
color: '#222',
opacity: 1,
weight: 1.5,
fillColor: '#88d28d',
fillOpacity: 0.8
};
const optionsFabbricati = {
color: '#222',
opacity: 1,
weight: 1.5,
fillColor: '#5b5d5f',
fillOpacity: 0.8
};
const clusterOptions = {
spiderfyOnMaxZoom: false,
showCoverageOnHover: false,
disableClusteringAtZoom: 19,
};
/**
* Capitalize a text string
* @todo Move to utils
@@ -108,37 +68,38 @@ GIS.initMap = async function (mapId, zoomLevel = this.INIT_ZOOM) {
L.control.scale({imperial: false}).addTo(map);
L.control.graphicScale({fill: 'hollow', position: 'bottomright'}).addTo(map);
let layerVincoli = await this.loadGeoJSON('vincoli.geojson', optionsVincoli);
let layerPaesistici = await this.loadGeoJSON('paesistici.geojson', optionsPaesistici);
let layerVincoli = await this.loadGeoJSON('vincoli.geojson', Options.constraintsArch);
let layerPaesistici = await this.loadGeoJSON('paesistici.geojson', Options.constraintsLand);
let buildings = await this.loadGeoJSON('fabbricati.geojson', Options.buildings, false);
await this.addLayerGroups(map);
await this.fetchCartographyLayers();
const archeo = {
'Vincoli archeologici' : layerVincoli,
'Vincoli paesistici' : layerPaesistici,
//'Catasto storico' : historicCadastre,
};
L.control.layers(baseMap, archeo).addTo(map);
GisState.map = map;
const buildingTechs = await this.buildingTechs();
const reprojectedWMSLayer = GIS.reprojectWMS();
const wmsLayer = new reprojectedWMSLayer(
'https://wms.cartografia.agenziaentrate.gov.it/inspire/wms/ows01.php?',
{
layers: 'CP.CadastralParcel,fabbricati',
layers: 'CP.CadastralParcel,codice_plla,fabbricati',
transparent: true,
format: 'image/png',
version: '1.1.1',
minZoom: 15,
minZoom: this.INIT_ZOOM,
maxZoom: this.MAX_ZOOM,
tileSize: 1024,
opacity: 0.6,
}
);
const cartography = {
'Catasto Agenzia delle Entrate (zoom per visualizzare)' : wmsLayer,
'Fabbricati' : buildings,
'Vincoli archeologici' : layerVincoli,
'Vincoli archeologici indiretti' : layerPaesistici,
'Tecniche murarie' : buildingTechs,
};
L.control.layers(baseMap, cartography).addTo(map);
GisState.cartography.cadastral = wmsLayer;
GisState.map = map;
return map;
}
@@ -155,6 +116,35 @@ GIS.fetchCartographyLayers = async function () {
GisState.cartography.historic.push({id, label});
}
}
/**
* Create building techs layer
* @returns {L.Layer}
*/
GIS.buildingTechs = async function () {
let techsData = await fetch(`${API_URL}/building_techs`)
.then(data => data.json());
let techs = new L.LayerGroup();
for (let record of techsData) {
const marker = L.marker(
record.coordinates,
{icon: Icons.techs, label: record.technique}
)
.bindTooltip(record.technique)
.bindPopup(UI.createBuildingTechTable(record));
techs.addLayer(marker);
const markerLabel = `${record.coordinates[0]} ${record.coordinates[1]}`;
marker.options.data = record;
marker.options.site = record.site.label;
GisState.markers.buildingTechs[markerLabel] = marker;
}
GisState.layers.buildingTechs = techs;
return techs;
}
/**
* Load georeferenced image overlays layer group
* @param {Number} imageId - The API id of the georeferenced image
@@ -199,6 +189,7 @@ GIS.addLayerGroups = async function (map) {
this.findings(),
this.prehistoric(),
this.underwater(),
this.reuse(),
]
);
groups.forEach(group => group.addTo(map));
@@ -211,15 +202,20 @@ GIS.sites = async function () {
let sitesData = await fetch(`${API_URL}/sites`)
.then(data => data.json());
let sites = L.markerClusterGroup(clusterOptions);
let sites = L.markerClusterGroup(Options.cluster);
let geom = [];
for (let record of sitesData) {
if (record.geojson) {
const options = record.gisId === 'grotta_azzurra' ?
optionsGrotta : optionsSiti;
Options.grotta : Options.site;
geom.push(await this.loadSiteLayer(record, options));
}
if (record.area) {
const options = record.gisId === 'villa_bismarck' ?
Options.bismarck : Options.site;
geom.push(await this.loadSiteLayer(record, options, false, true));
}
const marker = L.marker(
record.coordinates,
@@ -235,6 +231,7 @@ GIS.sites = async function () {
// Populate app state for reuse and avoid window.Sites etc.
// Municipality (Capri, Anacapri) added for reuse in dynamic menu
marker.options.municipality = record.municipality;
marker.options.data = record;
const markerIndex = `${record.coordinates[0]} ${record.coordinates[1]}`;
GisState.markers.sites[markerIndex] = marker;
}
@@ -251,7 +248,7 @@ GIS.notConserved = async function () {
let notConserData = await fetch(`${API_URL}/not_conserved`)
.then(data => data.json());
let notConserved = L.markerClusterGroup(clusterOptions);
let notConserved = L.markerClusterGroup(Options.cluster);
for (let record of notConserData.records) {
const marker = L.marker(
@@ -265,6 +262,7 @@ GIS.notConserved = async function () {
// Populate app state for reuse and avoid window.Sites etc.
const markerLabel = `${record.coordinates[0]} ${record.coordinates[1]}`;
marker.options.municipality = record.municipality;
marker.options.data = record;
GisState.markers.notConserved[markerLabel] = marker;
}
@@ -281,7 +279,7 @@ GIS.findings = async function () {
.then(data => data.json());
let findings = L.markerClusterGroup(
clusterOptions
Options.cluster
);
for (let record of findingsData) {
@@ -297,6 +295,7 @@ GIS.findings = async function () {
findings.addLayer(marker);
const markerLabel = `${record.coordinates[0]} ${record.coordinates[1]}`;
marker.options.municipality = record.municipality;
marker.options.data = record;
GisState.markers.findings[markerLabel] = marker;
}
@@ -313,7 +312,7 @@ GIS.prehistoric = async function () {
.then(data => data.json());
let prehistoric = L.markerClusterGroup(
clusterOptions
Options.cluster
);
for (let record of data.records) {
@@ -328,6 +327,7 @@ GIS.prehistoric = async function () {
const markerLabel = `${record.coordinates[0]} ${record.coordinates[1]}`;
marker.options.municipality = record.municipality;
marker.options.data = record;
GisState.markers.prehistoric[markerLabel] = marker;
prehistoric.addLayer(marker);
@@ -345,7 +345,7 @@ GIS.underwater = async function () {
let underwaterData = await fetch(`${API_URL}/underwater`)
.then(data => data.json());
let underwater = L.markerClusterGroup(clusterOptions);
let underwater = L.markerClusterGroup(Options.cluster);
for (let record of underwaterData.records) {
const marker = L.marker(
@@ -358,7 +358,8 @@ GIS.underwater = async function () {
);
const markerLabel = `${record.coordinates[0]} ${record.coordinates[1]}`;
GisState.markers.prehistoric[markerLabel] = marker;
marker.options.data = record;
GisState.markers.underwater[markerLabel] = marker;
underwater.addLayer(marker);
}
@@ -367,10 +368,42 @@ GIS.underwater = async function () {
return underwater;
}
/**
* Create reused sites group ('reimpiego')
* @returns {L.MarkerClusterGroup}
*/
GIS.reuse = async function () {
let reuseData = await fetch(`${API_URL}/reuse`)
.then(data => data.json());
let reuse = L.markerClusterGroup(Options.cluster);
for (let record of reuseData.records) {
const marker = L.marker(
record.coordinates,
{icon: Icons.reuse}
).bindTooltip(record.label)
.on(
'click',
() => UI.openReuseModal(record, '#reuse-data')
);
const markerLabel = `${record.coordinates[0]} ${record.coordinates[1]}`;
marker.options.municipality = record.municipality;
marker.options.label = record.label;
marker.options.data = record;
GisState.markers.reuse[markerLabel] = marker;
reuse.addLayer(marker);
}
GisState.layers.reuse = reuse;
return reuse;
}
/**
* Adds layers to map and returns an object
* with {baseMap, archeoLayers, sitesLayerGroup}
* @todo Load areas for sites that have them!!
* @param {L.Map} map
* @returns {{baseMap: {"OpenStreetMap": L.TileLayer}}}
*/
@@ -389,16 +422,15 @@ GIS.initLayers = async function(map) {
maxZoom: GIS.MAX_ZOOM,
attribution: '&copy; Mapbox'
});
let boundaries = await this.loadGeoJSON('confini.geojson', {}, false);
let buildings = await this.loadGeoJSON('fabbricati.geojson', optionsFabbricati, false);
let baseCatasto = new L.LayerGroup([buildings, boundaries]);
const baseGroup = new L.LayerGroup([osmap]);
baseGroup.addTo(map);
const baseMap = {
"OpenStreetMap" : osmap,
"Satellite" : mapbox,
"Cartografia catastale" : baseCatasto,
'Limiti amministrativi' : boundaries,
};
return baseMap;
@@ -479,9 +511,10 @@ GIS.loadGeoJSON = async function (geoJSON, options, popup = true) {
* @param {boolean} popup Should the features have a popup?
* @returns {L.Layer}
*/
GIS.loadSiteLayer = async function (site, options, popup = true) {
const geoJSON = `${site.gisId}.geojson`;
const geo = await fetch(`${BASE_URL}/geojson/${geoJSON}`)
GIS.loadSiteLayer = async function (site, options, popup = true, area = false) {
let geoJSON = site.gisId;
geoJSON += area ? '_area.geojson' : '.geojson';
const geo = await fetch(`${BASE_URL}geojson/${geoJSON}`)
.then(res => res.json())
.catch(error => console.error(`Can't load layer ${geoJSON}. Reason: ${error}`));
@@ -560,11 +593,13 @@ GIS.cacheDBData = async function (layerId, dbId) {
GIS.featurePopup = function (layerName, feature) {
const html = `
<table class="table is-striped is-size-6 m-2">
<tr><th>Oggetto</th><td>${feature.properties.OGGETTO}</td></tr>
<tr><th>Oggetto</th><td>${feature.properties.OGGETTO ?? 'n.d.'}</td></tr>
<tr><th>Anno</th><td>${feature.properties.ANNO}</td></tr>
<tr><th>Comune</th><td>${capitalize(feature.properties.COMUNE)}</td></tr>
<tr><th>Località</th><td>${capitalize(feature.properties.LOCALITA)}</td></tr>
<tr><th>Proprietà</th><td>${capitalize(feature.properties.PROPRIETA)}</td></tr>
<tr><th>Località</th><td>${capitalize(feature.properties.LOCALITA) ?? 'n.d.'}</td></tr>
<tr><th>Proprietà</th><td>${capitalize(feature.properties.PROPRIETA) ?? 'n.d.'}</td></tr>
<tr><th>Foglio</th><td>${feature.properties.FOGLIO}</td></tr>
<tr><th>Particella</th><td>${feature.properties.PART_BIS ?? 'n.d.'}</td></tr>
</table>
`;
const content = {
@@ -586,10 +621,8 @@ GIS.reprojectWMS = function (crs = 'EPSG:4258') {
const reprojectedWMSLayer = L.TileLayer.WMS.extend({
getTileUrl(tilePoint) {
const map = GisState.map;
//const crs = map.options.crs;
const tileSize = this.getTileSize();
//const geoPoint = L.point(tilePoint.x, tilePoint.y);
const nwPoint = L.point(
tilePoint.x * tileSize.x,
tilePoint.y * tileSize.y,

View File

@@ -48,6 +48,17 @@ Icons.underwater = L.icon(
}
);
Icons.reuse = L.icon(
{
iconUrl: 'img/icons/reimpiego.png',
iconSize: [18, 27],
iconAnchor: [10, 24],
tooltipAnchor: [0, -22],
}
);
Icons.camera = L.divIcon({className: 'fa fa-camera'});
Icons.techs = L.divIcon({className: 'fa fa-circle has-text-primary-25'});
export default Icons;

View File

@@ -1,5 +1,6 @@
import GIS from './gis.js';
import UI from './ui.js';
import { GisState } from './state.js';
import { Application } from '@hotwired/stimulus';
import MenuController from './controllers/menu_controller.js';
import ModalController from './controllers/modal_controller.js';
@@ -7,6 +8,7 @@ import MarkerController from './controllers/marker_controller.js';
import BiblioController from './controllers/biblio_controller.js';
import TabsController from './controllers/tabs_controller.js';
import LayerController from './controllers/layer_controller.js';
import SearchController from './controllers/search_controller.js';
document.addEventListener('DOMContentLoaded', async () => {
// Register Stimulus controllers
@@ -14,13 +16,13 @@ document.addEventListener('DOMContentLoaded', async () => {
let progress = document.querySelector('progress');
const map = await GIS.initMap('map');
progress.classList.add('is-hidden');
map._container.setAttribute('aria-busy', false);
// Trigger Stimulus buildMenu method...
const menuEvent = new Event('menu-ready');
document.dispatchEvent(menuEvent);
progress.classList.add('is-hidden');
map._container.setAttribute('aria-busy', false);
GIS.toggleSpherical(map);
@@ -36,4 +38,5 @@ function initStimulus() {
Stimulus.register("biblio", BiblioController);
Stimulus.register("tabs", TabsController);
Stimulus.register("layer", LayerController);
Stimulus.register("search", SearchController);
}

View File

@@ -0,0 +1,54 @@
/**
* @namespace Options
*/
let Options = {};
Options.constraintsArch = {
color: '#222',
opacity: 0.8,
weight: 1,
fillColor: '#fa7861',
fillOpacity: 0.8
};
Options.site = {
color: '#800040',
opacity: 1,
weight: 1.5,
fillColor: '#800040',
fillOpacity: 0.8
};
Options.grotta = {
color: '#205dac',
opacity: 1,
weight: 1.5,
fillColor: '#205dac',
fillOpacity: 0.8
}
Options.bismarck = {
color: '#a4a79a',
opacity: 1,
weight: 1.5,
fillColor: '#a4a79a',
fillOpacity: 0.8
}
Options.constraintsLand = {
color: '#222',
opacity: 1,
weight: 1.5,
fillColor: '#88d28d',
fillOpacity: 0.8
};
Options.buildings = {
color: '#222',
opacity: 1,
weight: 1.5,
fillColor: '#5b5d5f',
fillOpacity: 0.8
};
Options.cluster = {
spiderfyOnMaxZoom: false,
showCoverageOnHover: false,
disableClusteringAtZoom: 19,
};
export default Options;

View File

@@ -27,6 +27,8 @@ export const GisState = {
findings: {},
prehistoric: {},
underwater: {},
reuse: {},
buildingTechs: {},
},
layers: {
sites: {},
@@ -34,6 +36,8 @@ export const GisState = {
findings: {},
prehistoric: {},
underwater: {},
reuse: {},
buildingTechs: {},
},
bibliography: null,
apiUrl : null,

View File

@@ -10,6 +10,9 @@ import { Finding } from './components/Finding.js';
import { Prehistoric } from './components/Prehistoric.js';
import { Underwater } from './components/Underwater.js';
import { GisState } from "./state.js";
import { Reuse } from './components/Reuse.js';
const html = String.raw;
/**
* @namespace UI
@@ -100,8 +103,14 @@ UI.openSiteModal = function (data, selector) {
let surveys = data.images.filter(i => i.type === 'Survey');
let photos = data.images.filter(i => i.type === 'Photo');
let videos = data.images.filter(i => i.type === 'Video');
let reconstructions = data.images.filter(i => i.type === 'Reconstruction');
if (surveys.length === 0 && photos.length === 0 && videos.length === 0) {
const noMedia = surveys.length === 0
&& photos.length === 0
&& reconstructions.length === 0
&& videos.length === 0;
if (noMedia) {
images.innerHTML = '<p class="has-text-centered">Nessuna risorsa visuale disponibile</p>';
}
@@ -114,6 +123,9 @@ UI.openSiteModal = function (data, selector) {
siteMedia.siteData = photos[0] ?? undefined;
images.innerHTML += photos[0] ? siteMedia.renderPhotos() : '';
siteMedia.siteData = reconstructions[0] ?? undefined;
images.innerHTML += reconstructions[0] ? siteMedia.renderReconstructions() : '';
siteMedia.siteData = videos[0] ?? undefined;
images.innerHTML += videos[0] ? siteMedia.renderVideos() : '';
}
@@ -122,6 +134,7 @@ UI.openSiteModal = function (data, selector) {
this.imageGallery('gallery-1', surveys);
this.imageGallery('gallery-2', photos);
this.imageGallery('gallery-3d', reconstructions);
this.imageGallery('gallery-video', videos, true);
}
/**
@@ -182,7 +195,7 @@ UI.openPrehistModal = function (data, selector) {
prehistoric.data = data;
// For Stimulus biblio_controller
//GisState.bibliography = prehistoric;
GisState.bibliography = prehistoric;
prehistoric.render().then(html => modal.querySelector('#prehist-sheet').innerHTML = html);
prehistoric.setImages(modal.querySelector('#photos'), this.imageGallery);
modal.classList.add('is-active');
@@ -201,6 +214,27 @@ UI.openUnderwaterModal = function (data, selector) {
// For Stimulus biblio_controller
//GisState.bibliography = underwater;
underwater.render().then(html => modal.querySelector('#underwater-sheet').innerHTML = html);
underwater.renderDocs().then(html => modal.querySelector('#documents').innerHTML = html);
underwater.setImages(modal.querySelector('#photos'), this.imageGallery);
modal.classList.add('is-active');
}
/**
* @todo Biblio?
* @param {object} data The data retrieved from the DB to display as modal content
* @param {string} selector The modal selector
*/
UI.openReuseModal = function (data, selector) {
const modal = document.querySelector(selector);
let reuse = new Reuse();
reuse.data = data;
GisState.bibliography = reuse;
// For Stimulus biblio_controller
//GisState.bibliography = underwater;
reuse.render().then(html => modal.querySelector('#reuse-sheet').innerHTML = html);
reuse.setImages(modal.querySelector('#photos'), this.imageGallery);
modal.classList.add('is-active');
}
/**
@@ -216,8 +250,9 @@ UI.imageGallery = function (galleryId, items, video = false) {
let gallery = [];
for (let media of items) {
let author = media.author ? ` (${media.author})` : '';
let caption = media.caption ?? '';
let mediaObj = {
description: media.caption + author
description: caption + author
};
if (video) {
@@ -237,5 +272,23 @@ UI.imageGallery = function (galleryId, items, video = false) {
});
}
}
/**
* @param {Object} record
* @returns {string}
*/
UI.createBuildingTechTable = function(record) {
return html`
<table class="table is-striped is-size-6 m-2">
<tbody>
<tr><th>Tecnica</th><td>${record.technique}</td></tr>
<tr><th>Descrizione</th><td>${record.description}</td></tr>
<tr><th>Funzione</th><td>${record.function}</td></tr>
<tr><th>Materiale</th><td>${record.material}</td></tr>
<tr><th>Sito</th><td>${record.site.label}</td></tr>
<tr><th>Comune</th><td>${record.municipality}</td></tr>
</tbody>
</table>
;`
}
export default UI;
export default UI;