@@ -28,6 +28,7 @@ const lmeState = {
drawControl : null ,
drawControl : null ,
drawingDeleteButton : null ,
drawingDeleteButton : null ,
drawingScopeMenu : null ,
drawingScopeMenu : null ,
geometryToolsControl : null ,
ignoreMapClickUntil : 0 ,
ignoreMapClickUntil : 0 ,
activeDrawHandler : null ,
activeDrawHandler : null ,
drawEventsBound : false ,
drawEventsBound : false ,
@@ -80,6 +81,7 @@ const lmeState = {
snapEnabled : true ,
snapEnabled : true ,
snapDistancePixels : 14 ,
snapDistancePixels : 14 ,
autoSaveEnabled : true ,
autoSaveEnabled : true ,
autoFitSelectedObject : false ,
gpsProximityEnabled : true ,
gpsProximityEnabled : true ,
gpsBoundaryAlertEnabled : false ,
gpsBoundaryAlertEnabled : false ,
lastBoundaryState : null ,
lastBoundaryState : null ,
@@ -251,6 +253,7 @@ function init(dotNetReference) {
refreshCadastreLayer ( ) ;
refreshCadastreLayer ( ) ;
bindFileImport ( ) ;
bindFileImport ( ) ;
bindDrawingEvents ( ) ;
bindDrawingEvents ( ) ;
addGeometryToolsControl ( ) ;
if ( ! lmeState . resizeBound ) {
if ( ! lmeState . resizeBound ) {
window . addEventListener ( "resize" , resize ) ;
window . addEventListener ( "resize" , resize ) ;
@@ -1186,7 +1189,7 @@ function layerManagerCategories() {
{ key : "gpsParcels" , label : "Particella rilevata dal GPS" , roots : [ lmeState . gpsParcelLayer ] . filter ( Boolean ) } ,
{ key : "gpsParcels" , label : "Particella rilevata dal GPS" , roots : [ lmeState . gpsParcelLayer ] . filter ( Boolean ) } ,
{ key : "selectedParcels" , label : "Particelle catastali evidenziate" , roots : [ lmeState . selectedParcelsLayer ] . filter ( Boolean ) } ,
{ key : "selectedParcels" , label : "Particelle catastali evidenziate" , roots : [ lmeState . selectedParcelsLayer ] . filter ( Boolean ) } ,
{ key : "importedSheets" , label : "Cornici e fogli importati" , roots : [ lmeState . importedSheets ] . filter ( Boolean ) , blocks : true } ,
{ key : "importedSheets" , label : "Cornici e fogli importati" , roots : [ lmeState . importedSheets ] . filter ( Boolean ) , blocks : true } ,
{ key : "imported" , label : "D ati importati" , roots : [ lmeState . imported ] . filter ( Boolean ) , blocks : true } ,
{ key : "imported" , label : "Progetti e d ati importati" , roots : [ lmeState . imported ] . filter ( Boolean ) , blocks : true } ,
{ key : "cadastre" , label : "Layer catastali AdE" , roots : [ lmeState . cadastre ] . filter ( Boolean ) }
{ key : "cadastre" , label : "Layer catastali AdE" , roots : [ lmeState . cadastre ] . filter ( Boolean ) }
] ;
] ;
}
}
@@ -1237,11 +1240,13 @@ function managedRecordsForCategory(category) {
function managedObjectLabel ( record , index ) {
function managedObjectLabel ( record , index ) {
const layer = record . layer ;
const layer = record . layer ;
const properties = layer ? . feature ? . properties || { } ;
const properties = layer ? . feature ? . properties || { } ;
const reference = properties . NATIONALCADASTRALREFERENCE
const reference = properties . _lmeDisplayName
|| properties . name
|| properties . Name
|| properties . NATIONALCADASTRALREFERENCE
|| properties . INSPIREID _LOCALID
|| properties . INSPIREID _LOCALID
|| properties . LABEL
|| properties . LABEL
|| properties . name
|| properties . label ;
|| properties . Name ;
if ( reference ) {
if ( reference ) {
return String ( reference ) ;
return String ( reference ) ;
}
}
@@ -1265,7 +1270,7 @@ function managedObjectLabel(record, index) {
function managedObjectEditableName ( record ) {
function managedObjectEditableName ( record ) {
const properties = record ? . layer ? . feature ? . properties || { } ;
const properties = record ? . layer ? . feature ? . properties || { } ;
return properties . name || properties . Name || properties . label || properties . LABEL || "" ;
return properties . _lmeDisplayName || properties . name || properties . Name || properties . label || properties . LABEL || "" ;
}
}
function managedObjectCanRename ( record ) {
function managedObjectCanRename ( record ) {
@@ -1273,16 +1278,15 @@ function managedObjectCanRename(record) {
&& record . layer ? . feature ? . properties ;
&& record . layer ? . feature ? . properties ;
}
}
function renameManagedObject ( record , index ) {
async function renameManagedObject ( record , index ) {
if ( lmeState . readOnly ) { setInfo ( "Il progetto e' in sola lettura." ) ; return ; }
if ( lmeState . readOnly ) { setInfo ( "Il progetto e' in sola lettura." ) ; return ; }
if ( ! managedObjectCanRename ( record ) ) {
if ( ! managedObjectCanRename ( record ) ) {
return ;
return ;
}
}
const currentName = String ( managedObjectEditableName ( record ) || managedObjectLabel ( record , index ) ) . trim ( ) ;
const currentName = String ( managedObjectEditableName ( record ) || managedObjectLabel ( record , index ) ) . trim ( ) ;
const requestedName = window . prompt ( "Nuovo nome oggetto" , currentName ) ;
const result = await showFormDialog ( "Rinomina oggetto" , [ { name : "name" , label : "Nome" , value : currentName , maxLength : 120 } ] , "Rinomina" ) ;
if ( requestedName === null ) {
if ( ! result ) return ;
return ;
const requestedName = result . name ;
}
const name = requestedName . trim ( ) . slice ( 0 , 120 ) ;
const name = requestedName . trim ( ) . slice ( 0 , 120 ) ;
if ( ! name ) {
if ( ! name ) {
setInfo ( "Nome non modificato: inserisci un testo." ) ;
setInfo ( "Nome non modificato: inserisci un testo." ) ;
@@ -1291,6 +1295,7 @@ function renameManagedObject(record, index) {
rememberMapChange ( ) ;
rememberMapChange ( ) ;
const properties = record . layer . feature . properties ;
const properties = record . layer . feature . properties ;
properties . name = name ;
properties . name = name ;
properties . _lmeDisplayName = name ;
record . layer . lmeManagerLabel = record . categoryKey === "drawings" && record . layer instanceof L . Marker
record . layer . lmeManagerLabel = record . categoryKey === "drawings" && record . layer instanceof L . Marker
? ` POI: ${ name } `
? ` POI: ${ name } `
: null ;
: null ;
@@ -1319,12 +1324,11 @@ function managedCategoryCanRename(category) {
return [ "drawings" , "imported" , "importedSheets" ] . includes ( category . key ) ;
return [ "drawings" , "imported" , "importedSheets" ] . includes ( category . key ) ;
}
}
function renameManagedCategory ( category , records ) {
async function renameManagedCategory ( category , records ) {
const currentName = managedCategoryName ( category , records ) ;
const currentName = managedCategoryName ( category , records ) ;
const requestedName = window . prompt ( "Nuovo nome gruppo" , currentName ) ;
const result = await showFormDialog ( "Rinomina gruppo" , [ { name : "name" , label : "Nome gruppo" , value : currentName , maxLength : 120 } ] , "Rinomina" ) ;
if ( requestedName === null ) {
if ( ! result ) return ;
return ;
const requestedName = result . name ;
}
const name = requestedName . trim ( ) . slice ( 0 , 120 ) ;
const name = requestedName . trim ( ) . slice ( 0 , 120 ) ;
if ( ! name ) {
if ( ! name ) {
setInfo ( "Nome non modificato: inserisci un testo." ) ;
setInfo ( "Nome non modificato: inserisci un testo." ) ;
@@ -1348,12 +1352,11 @@ function managedBlockName(source, records) {
. find ( Boolean ) || source ) ;
. find ( Boolean ) || source ) ;
}
}
function renameManagedBlock ( category , source , records ) {
async function renameManagedBlock ( category , source , records ) {
const currentName = managedBlockName ( source , records ) ;
const currentName = managedBlockName ( source , records ) ;
const requestedName = window . prompt ( "Nuovo nome gruppo" , currentName ) ;
const result = await showFormDialog ( "Rinomina gruppo" , [ { name : "name" , label : "Nome gruppo" , value : currentName , maxLength : 120 } ] , "Rinomina" ) ;
if ( requestedName === null ) {
if ( ! result ) return ;
return ;
const requestedName = result . name ;
}
const name = requestedName . trim ( ) . slice ( 0 , 120 ) ;
const name = requestedName . trim ( ) . slice ( 0 , 120 ) ;
if ( ! name ) {
if ( ! name ) {
setInfo ( "Nome non modificato: inserisci un testo." ) ;
setInfo ( "Nome non modificato: inserisci un testo." ) ;
@@ -1469,6 +1472,43 @@ function removeManagedRecords(records, subject) {
setInfo ( ` ${ records . length === 1 ? "Oggetto rimosso" : ` ${ records . length } oggetti rimossi ` } dal progetto. ` ) ;
setInfo ( ` ${ records . length === 1 ? "Oggetto rimosso" : ` ${ records . length } oggetti rimossi ` } dal progetto. ` ) ;
}
}
function moveManagedRecordsToCategory ( records , targetCategory ) {
if ( lmeState . readOnly ) { setInfo ( "Il progetto e' in sola lettura." ) ; return ; }
const movable = records . filter ( record => [ "drawings" , "imported" ] . includes ( record . categoryKey ) ) ;
if ( ! movable . length || ! [ "drawings" , "imported" ] . includes ( targetCategory ) ) {
setInfo ( "Questo gruppo non puo essere spostato." ) ;
return ;
}
if ( movable . some ( record => record . layer ? . feature ? . properties ? . _lmeLocked ) ) {
setInfo ( "Sblocca il gruppo prima di spostarlo." ) ;
return ;
}
rememberMapChange ( ) ;
const features = movable . map ( record => {
const feature = record . layer . toGeoJSON ( ) ;
feature . properties = {
... ( feature . properties || { } ) ,
_lmeSource : feature . properties ? . _lmeSource || "Progetto corrente"
} ;
return feature ;
} ) ;
movable . forEach ( record => {
record . parent ? . removeLayer ? . ( record . layer ) ;
clearManagedObjectState ( record . id ) ;
lmeState . managerSelectedObjectIds . delete ( record . id ) ;
} ) ;
if ( targetCategory === "drawings" ) {
features . forEach ( feature => addDrawingFeature ( feature ) ) ;
} else {
addImportedFeatures ( { type : "FeatureCollection" , features } ) ;
}
refreshImportedMeasurements ( ) ;
refreshDrawingMeasurements ( ) ;
refreshLayerManager ( ) ;
finishMapChange ( ` Spostati ${ features . length } oggetti in ${ targetCategory === "drawings" ? "Disegni e POI" : "Progetti e dati importati" } . ` ) ;
setInfo ( ` ${ features . length } oggetti spostati in ${ targetCategory === "drawings" ? "Disegni e POI" : "Progetti e dati importati" } . ` ) ;
}
function managedObjectDetails ( layer ) {
function managedObjectDetails ( layer ) {
const details = [ ] ;
const details = [ ] ;
const properties = layer ? . feature ? . properties || { } ;
const properties = layer ? . feature ? . properties || { } ;
@@ -1546,6 +1586,9 @@ function refreshLayerManager() {
applyManagedVisualOrder ( container ) ;
applyManagedVisualOrder ( container ) ;
bindLayerManagerDrag ( container ) ;
bindLayerManagerDrag ( container ) ;
bindLayerManagerMoveButtons ( container ) ;
bindLayerManagerMoveButtons ( container ) ;
if ( lmeState . managerHighlightedObjectId ) {
window . requestAnimationFrame ( ( ) => revealManagedRecordInLayerManager ( lmeState . managerHighlightedObjectId ) ) ;
}
if ( scrollHost ) {
if ( scrollHost ) {
scrollHost . scrollTop = scrollTop ;
scrollHost . scrollTop = scrollTop ;
window . requestAnimationFrame ( ( ) => {
window . requestAnimationFrame ( ( ) => {
@@ -1557,6 +1600,8 @@ function refreshLayerManager() {
function renderLayerManagerCategory ( category , records ) {
function renderLayerManagerCategory ( category , records ) {
const group = document . createElement ( "details" ) ;
const group = document . createElement ( "details" ) ;
group . className = "layer-manager-group" ;
group . className = "layer-manager-group" ;
group . classList . toggle ( "layer-manager-selection-path" , records . some ( record =>
record . id === lmeState . managerHighlightedObjectId || lmeState . managerSelectedObjectIds . has ( record . id ) ) ) ;
group . dataset . dragType = "category" ;
group . dataset . dragType = "category" ;
group . dataset . layerDepth = "0" ;
group . dataset . layerDepth = "0" ;
group . dataset . categoryKey = category . key ;
group . dataset . categoryKey = category . key ;
@@ -1576,7 +1621,8 @@ function renderLayerManagerCategory(category, records) {
&& ( ! category . roots . length || category . roots . some ( layer => lmeState . map . hasLayer ( layer ) ) ) ;
&& ( ! category . roots . length || category . roots . some ( layer => lmeState . map . hasLayer ( layer ) ) ) ;
const visible = categoryVisible ;
const visible = categoryVisible ;
const categoryLocked = records . length > 0 && records . every ( record => Boolean ( record . layer ? . feature ? . properties ? . _lmeLocked ) ) ;
const categoryLocked = records . length > 0 && records . every ( record => Boolean ( record . layer ? . feature ? . properties ? . _lmeLocked ) ) ;
group . innerHTML = ` <summary><button type="button" class="layer-drag-handle" aria-label="Trascina gruppo">☰</button><input type="checkbox" ${ visible ? "checked" : "" } aria-label="Mostra ${ escapeHtml ( categoryName ) } "><span class="layer-manager-title"> ${ escapeHtml ( categoryName ) } </span><button type="button" class="layer-inline-lock ${ categoryLocked ? "is-locked" : "" } " aria-label=" ${ categoryLocked ? "Sblocca" : "Blocca" } gruppo" title=" ${ categoryLocked ? "Sblocca" : "Blocca" } "> ${ categoryLocked ? "🔒" : "🔓" } </button> ${ rowActions } ${ layerManagerMoveButtons ( "gruppo" ) } <span class="layer-manager-count"> ${ records . length } </span></summary><div class="layer-manager-children"></div> ` ;
group . innerHTML = ` <summary> ${ layerExpandButton ( group . open , categoryName ) } <button type="button" class="layer-drag-handle" aria-label="Trascina gruppo">☰</button><input type="checkbox" ${ visible ? "checked" : "" } aria-label="Mostra ${ escapeHtml ( categoryName ) } "><span class="layer-manager-title"> ${ escapeHtml ( categoryName ) } </span><button type="button" class="layer-inline-lock ${ categoryLocked ? "is-locked" : "" } " aria-label=" ${ categoryLocked ? "Sblocca" : "Blocca" } gruppo" title=" ${ categoryLocked ? "Sblocca" : "Blocca" } "> ${ categoryLocked ? "🔒" : "🔓" } </button> ${ rowActions } ${ layerManagerMoveButtons ( "gruppo" ) } <span class="layer-manager-count"> ${ records . length } </span></summary><div class="layer-manager-children"></div> ` ;
bindLayerManagerToggleButton ( group ) ;
const checkbox = group . querySelector ( "input" ) ;
const checkbox = group . querySelector ( "input" ) ;
checkbox . indeterminate = categoryVisible && records . length > 0 && visibleCount < records . length ;
checkbox . indeterminate = categoryVisible && records . length > 0 && visibleCount < records . length ;
checkbox . addEventListener ( "click" , event => event . stopPropagation ( ) ) ;
checkbox . addEventListener ( "click" , event => event . stopPropagation ( ) ) ;
@@ -1615,6 +1661,8 @@ function renderLayerManagerCategory(category, records) {
function renderLayerManagerBlock ( category , source , records ) {
function renderLayerManagerBlock ( category , source , records ) {
const block = document . createElement ( "details" ) ;
const block = document . createElement ( "details" ) ;
block . className = "layer-manager-block" ;
block . className = "layer-manager-block" ;
block . classList . toggle ( "layer-manager-selection-path" , records . some ( record =>
record . id === lmeState . managerHighlightedObjectId || lmeState . managerSelectedObjectIds . has ( record . id ) ) ) ;
block . dataset . dragType = "block" ;
block . dataset . dragType = "block" ;
block . dataset . layerDepth = "1" ;
block . dataset . layerDepth = "1" ;
block . dataset . categoryKey = category . key ;
block . dataset . categoryKey = category . key ;
@@ -1629,7 +1677,8 @@ function renderLayerManagerBlock(category, source, records) {
` <button type="button" class="layer-remove-button" aria-label="Rimuovi gruppo dal progetto" title="Rimuovi dal progetto">Rimuovi</button> ` ) ;
` <button type="button" class="layer-remove-button" aria-label="Rimuovi gruppo dal progetto" title="Rimuovi dal progetto">Rimuovi</button> ` ) ;
block . classList . add ( "layer-manager-with-actions" ) ;
block . classList . add ( "layer-manager-with-actions" ) ;
const blockLocked = records . length > 0 && records . every ( record => Boolean ( record . layer ? . feature ? . properties ? . _lmeLocked ) ) ;
const blockLocked = records . length > 0 && records . every ( record => Boolean ( record . layer ? . feature ? . properties ? . _lmeLocked ) ) ;
block . innerHTML = ` <summary><button type="button" class="layer-drag-handle" aria-label="Trascina blocco">☰</button><input type="checkbox" ${ visible ? "checked" : "" } aria-label="Mostra ${ escapeHtml ( blockName ) } "><span class="layer-manager-title"> ${ escapeHtml ( blockName ) } </span><button type="button" class="layer-inline-lock ${ blockLocked ? "is-locked" : "" } " aria-label=" ${ blockLocked ? "Sblocca" : "Blocca" } gruppo" title=" ${ blockLocked ? "Sblocca" : "Blocca" } "> ${ blockLocked ? "🔒" : "🔓" } </button> ${ rowActions } ${ layerManagerMoveButtons ( "blocco" ) } <span class="layer-manager-count"> ${ records . length } </span></summary><div class="layer-manager-children"></div> ` ;
block . innerHTML = ` <summary> ${ layerExpandButton ( block . open , blockName ) } <button type="button" class="layer-drag-handle" aria-label="Trascina blocco">☰</button><input type="checkbox" ${ visible ? "checked" : "" } aria-label="Mostra ${ escapeHtml ( blockName ) } "><span class="layer-manager-title"> ${ escapeHtml ( blockName ) } </span><button type="button" class="layer-inline-lock ${ blockLocked ? "is-locked" : "" } " aria-label=" ${ blockLocked ? "Sblocca" : "Blocca" } gruppo" title=" ${ blockLocked ? "Sblocca" : "Blocca" } "> ${ blockLocked ? "🔒" : "🔓" } </button> ${ rowActions } ${ layerManagerMoveButtons ( "blocco" ) } <span class="layer-manager-count"> ${ records . length } </span></summary><div class="layer-manager-children"></div> ` ;
bindLayerManagerToggleButton ( block ) ;
const checkbox = block . querySelector ( "input" ) ;
const checkbox = block . querySelector ( "input" ) ;
checkbox . indeterminate = visibleCount > 0 && ! visible ;
checkbox . indeterminate = visibleCount > 0 && ! visible ;
checkbox . addEventListener ( "click" , event => event . stopPropagation ( ) ) ;
checkbox . addEventListener ( "click" , event => event . stopPropagation ( ) ) ;
@@ -1679,7 +1728,8 @@ function renderLayerManagerObject(record, index) {
object . classList . toggle ( "layer-manager-with-actions" , Boolean ( rowActions ) ) ;
object . classList . toggle ( "layer-manager-with-actions" , Boolean ( rowActions ) ) ;
const objectName = managedObjectLabel ( record , index ) ;
const objectName = managedObjectLabel ( record , index ) ;
const locked = Boolean ( record . layer ? . feature ? . properties ? . _lmeLocked ) ;
const locked = Boolean ( record . layer ? . feature ? . properties ? . _lmeLocked ) ;
object . innerHTML = ` <summary><button type="button" class="layer-drag-handle" aria-label="Trascina oggetto">☰</button><input type="checkbox" ${ visible ? "checked" : "" } aria-label="Mostra oggetto"><span class="layer-manager-title"> ${ escapeHtml ( objectName ) } </span><button type="button" class="layer-inline-lock ${ locked ? "is-locked" : "" } " aria-label=" ${ locked ? "Sblocca" : "Blocca" } oggetto" title=" ${ locked ? "Sblocca" : "Blocca" } "> ${ locked ? "🔒" : "🔓" } </button> ${ rowActions } ${ layerManagerMoveButtons ( "oggetto" ) } <span class="layer-manager-count">› </span>< /summary><div class="layer-manager-details"></div> ` ;
object . innerHTML = ` <summary> ${ layerExpandButton ( object . open , objectName ) } <button type="button" class="layer-drag-handle" aria-label="Trascina oggetto">☰</button><input type="checkbox" ${ visible ? "checked" : "" } aria-label="Mostra oggetto"><span class="layer-manager-title"> ${ escapeHtml ( objectName ) } </span><button type="button" class="layer-inline-lock ${ locked ? "is-locked" : "" } " aria-label=" ${ locked ? "Sblocca" : "Blocca" } oggetto" title=" ${ locked ? "Sblocca" : "Blocca" } "> ${ locked ? "🔒" : "🔓" } </button> ${ rowActions } ${ layerManagerMoveButtons ( "oggetto" ) } </summary><div class="layer-manager-details"></div> ` ;
bindLayerManagerToggleButton ( object ) ;
const checkbox = object . querySelector ( "input" ) ;
const checkbox = object . querySelector ( "input" ) ;
checkbox . addEventListener ( "click" , event => event . stopPropagation ( ) ) ;
checkbox . addEventListener ( "click" , event => event . stopPropagation ( ) ) ;
checkbox . addEventListener ( "change" , ( ) => setManagedObjectVisible ( record , checkbox . checked , true ) ) ;
checkbox . addEventListener ( "change" , ( ) => setManagedObjectVisible ( record , checkbox . checked , true ) ) ;
@@ -1701,7 +1751,6 @@ function renderLayerManagerObject(record, index) {
object . querySelector ( ".layer-manager-title" ) . addEventListener ( "click" , event => {
object . querySelector ( ".layer-manager-title" ) . addEventListener ( "click" , event => {
event . preventDefault ( ) ;
event . preventDefault ( ) ;
event . stopPropagation ( ) ;
event . stopPropagation ( ) ;
object . open = true ;
highlightManagedObject ( record ) ;
highlightManagedObject ( record ) ;
} ) ;
} ) ;
const details = object . querySelector ( ".layer-manager-details" ) ;
const details = object . querySelector ( ".layer-manager-details" ) ;
@@ -1769,11 +1818,17 @@ function renderManagedGroupTools(records) {
const tools = document . createElement ( "div" ) ;
const tools = document . createElement ( "div" ) ;
tools . className = "layer-group-tools" ;
tools . className = "layer-group-tools" ;
const properties = records [ 0 ] ? . layer ? . feature ? . properties || { } ;
const properties = records [ 0 ] ? . layer ? . feature ? . properties || { } ;
const sourceCategory = records [ 0 ] ? . categoryKey ;
const membershipAction = sourceCategory === "drawings"
? ` <button type="button" data-group-action="membership">Sposta in Progetti e dati importati</button> `
: sourceCategory === "imported"
? ` <button type="button" data-group-action="membership">Sposta in Disegni e POI</button> `
: "" ;
tools . innerHTML = ` <strong>Azioni gruppo</strong><span class="layer-group-totals"> ${ escapeHtml ( managedGroupTotals ( records ) ) } </span><div class="layer-style-controls">
tools . innerHTML = ` <strong>Azioni gruppo</strong><span class="layer-group-totals"> ${ escapeHtml ( managedGroupTotals ( records ) ) } </span><div class="layer-style-controls">
<label>Colore<input type="color" value=" ${ normalizedColor ( properties . _lmeColor , "#16806a" ) } "></label>
<label>Colore<input type="color" value=" ${ normalizedColor ( properties . _lmeColor , "#16806a" ) } "></label>
<label>Spessore<input type="range" min="1" max="10" step="1" value=" ${ Number ( properties . _lmeWeight ) || 3 } "></label>
<label>Spessore<input type="range" min="1" max="10" step="1" value=" ${ Number ( properties . _lmeWeight ) || 3 } "></label>
<label>Opacita<input type="range" min="0.1" max="1" step="0.1" value=" ${ Number ( properties . _lmeOpacity ) || 1 } "></label></div>
<label>Opacita<input type="range" min="0.1" max="1" step="0.1" value=" ${ Number ( properties . _lmeOpacity ) || 1 } "></label></div>
<div class="layer-compact-actions"><button type="button" data-group-action="select">Seleziona tutti</button><button type="button" data-group-action="lock">Blocca/sblocca</button><button type="button" data-group-action="labels">Etichette</button></div> ` ;
<div class="layer-compact-actions"><button type="button" data-group-action="select">Seleziona tutti</button><button type="button" data-group-action="lock">Blocca/sblocca</button><button type="button" data-group-action="labels">Etichette</button> ${ membershipAction } </div> ` ;
const inputs = tools . querySelectorAll ( "input" ) ;
const inputs = tools . querySelectorAll ( "input" ) ;
inputs . forEach ( input => input . addEventListener ( "change" , ( ) => {
inputs . forEach ( input => input . addEventListener ( "change" , ( ) => {
applyManagedStyle ( records , { color : inputs [ 0 ] . value , weight : Number ( inputs [ 1 ] . value ) , opacity : Number ( inputs [ 2 ] . value ) } ) ;
applyManagedStyle ( records , { color : inputs [ 0 ] . value , weight : Number ( inputs [ 1 ] . value ) , opacity : Number ( inputs [ 2 ] . value ) } ) ;
@@ -1790,6 +1845,8 @@ function renderManagedGroupTools(records) {
const visible = ! records . every ( record => Boolean ( record . layer ? . feature ? . properties ? . _lmeLabelVisible ) ) ;
const visible = ! records . every ( record => Boolean ( record . layer ? . feature ? . properties ? . _lmeLabelVisible ) ) ;
updateManagedProperties ( records , properties => { properties . _lmeLabelVisible = visible ; } ) ;
updateManagedProperties ( records , properties => { properties . _lmeLabelVisible = visible ; } ) ;
} ) ;
} ) ;
tools . querySelector ( "[data-group-action='membership']" ) ? . addEventListener ( "click" , ( ) =>
moveManagedRecordsToCategory ( records , sourceCategory === "drawings" ? "imported" : "drawings" ) ) ;
return tools ;
return tools ;
}
}
@@ -1812,6 +1869,11 @@ function renderManagedObjectTools(record) {
const properties = record . layer . feature ? . properties || ( record . layer . feature = { type : "Feature" , properties : { } } ) . properties ;
const properties = record . layer . feature ? . properties || ( record . layer . feature = { type : "Feature" , properties : { } } ) . properties ;
const selected = lmeState . managerSelectedObjectIds . has ( record . id ) ;
const selected = lmeState . managerSelectedObjectIds . has ( record . id ) ;
const locked = Boolean ( properties . _lmeLocked ) ;
const locked = Boolean ( properties . _lmeLocked ) ;
const membershipAction = record . categoryKey === "drawings"
? ` <button type="button" data-action="membership">Sposta in Progetti e dati importati</button> `
: record . categoryKey === "imported"
? ` <button type="button" data-action="membership">Sposta in Disegni e POI</button> `
: "" ;
const tools = document . createElement ( "section" ) ;
const tools = document . createElement ( "section" ) ;
tools . className = "layer-object-tools" ;
tools . className = "layer-object-tools" ;
tools . innerHTML = ` <label class="layer-manager-suboption"><input type="checkbox" data-field="selected" ${ selected ? "checked" : "" } ><span>Seleziona per azioni multiple</span></label>
tools . innerHTML = ` <label class="layer-manager-suboption"><input type="checkbox" data-field="selected" ${ selected ? "checked" : "" } ><span>Seleziona per azioni multiple</span></label>
@@ -1826,7 +1888,7 @@ function renderManagedObjectTools(record) {
<label>Note<textarea data-field="notes" rows="2" maxlength="1000" placeholder="Note tecniche"> ${ escapeHtml ( properties . _lmeNotes || "" ) } </textarea></label>
<label>Note<textarea data-field="notes" rows="2" maxlength="1000" placeholder="Note tecniche"> ${ escapeHtml ( properties . _lmeNotes || "" ) } </textarea></label>
<label class="layer-photo-picker">Foto<input type="file" data-field="photo" accept="image/*"></label>
<label class="layer-photo-picker">Foto<input type="file" data-field="photo" accept="image/*"></label>
${ properties . _lmePhoto ? ` <img class="layer-photo-preview" src=" ${ properties . _lmePhoto } " alt="Foto allegata"><button type="button" data-action="remove-photo">Rimuovi foto</button> ` : "" }
${ properties . _lmePhoto ? ` <img class="layer-photo-preview" src=" ${ properties . _lmePhoto } " alt="Foto allegata"><button type="button" data-action="remove-photo">Rimuovi foto</button> ` : "" }
<div class="layer-compact-actions"><button type="button" data-action="attributes">Attributi</button><button type="button" data-action="duplicate">Duplica</button><button type="button" data-action="numeric">Coordinate</button><button type="button" data-action="inspect">WGS84 / UTM</button><button type="button" data-action="move">Sposta</button><button type="button" data-action="buffer">Buffer</button></div> ` ;
<div class="layer-compact-actions"><button type="button" data-action="attributes">Attributi</button><button type="button" data-action="duplicate">Duplica</button><button type="button" data-action="numeric">Coordinate</button><button type="button" data-action="inspect">WGS84 / UTM</button><button type="button" data-action="move">Sposta geometria </button><button type="button" data-action="buffer">Buffer</button> ${ membershipAction } </div> ` ;
tools . querySelector ( "[data-field='selected']" ) . addEventListener ( "change" , event => {
tools . querySelector ( "[data-field='selected']" ) . addEventListener ( "change" , event => {
if ( event . target . checked ) lmeState . managerSelectedObjectIds . add ( record . id ) ;
if ( event . target . checked ) lmeState . managerSelectedObjectIds . add ( record . id ) ;
else lmeState . managerSelectedObjectIds . delete ( record . id ) ;
else lmeState . managerSelectedObjectIds . delete ( record . id ) ;
@@ -1856,6 +1918,8 @@ function renderManagedObjectTools(record) {
tools . querySelector ( "[data-action='inspect']" ) . addEventListener ( "click" , ( ) => showCoordinateInspectorForRecord ( record ) ) ;
tools . querySelector ( "[data-action='inspect']" ) . addEventListener ( "click" , ( ) => showCoordinateInspectorForRecord ( record ) ) ;
tools . querySelector ( "[data-action='move']" ) . addEventListener ( "click" , ( ) => moveManagedRecords ( [ record ] ) ) ;
tools . querySelector ( "[data-action='move']" ) . addEventListener ( "click" , ( ) => moveManagedRecords ( [ record ] ) ) ;
tools . querySelector ( "[data-action='buffer']" ) . addEventListener ( "click" , ( ) => bufferManagedRecords ( [ record ] ) ) ;
tools . querySelector ( "[data-action='buffer']" ) . addEventListener ( "click" , ( ) => bufferManagedRecords ( [ record ] ) ) ;
tools . querySelector ( "[data-action='membership']" ) ? . addEventListener ( "click" , ( ) =>
moveManagedRecordsToCategory ( [ record ] , record . categoryKey === "drawings" ? "imported" : "drawings" ) ) ;
return tools ;
return tools ;
}
}
@@ -1867,25 +1931,35 @@ function editManagedAttributes(record) {
if ( lmeState . readOnly ) { setInfo ( "Il progetto e' in sola lettura." ) ; return ; }
if ( lmeState . readOnly ) { setInfo ( "Il progetto e' in sola lettura." ) ; return ; }
const properties = record . layer ? . feature ? . properties || { } ;
const properties = record . layer ? . feature ? . properties || { } ;
const reserved = new Set ( [ "name" , "Name" , "label" , "LABEL" ] ) ;
const reserved = new Set ( [ "name" , "Name" , "label" , "LABEL" ] ) ;
const current = Object . entries ( properties )
const entries = Object . entries ( properties )
. filter ( ( [ key , value ] ) => ! key . startsWith ( "_lme" ) && ! reserved . has ( key ) && [ "string" , "number" , "boolean" ] . includes ( typeof value ) )
. filter ( ( [ key , value ] ) => ! key . startsWith ( "_lme" ) && ! reserved . has ( key ) && [ "string" , "number" , "boolean" ] . includes ( typeof value ) ) ;
. map ( ( [ key , value ] ) => ` ${ key } = ${ value } ` ) . join ( "\n" ) ;
const dialog = showAppDialog ( "Attributi oggetto" , ` <div class="attribute-editor"><p>Ogni attributo viene salvato nel progetto e negli export GeoJSON, KML e KMZ.</p><div class="attribute-editor-rows"></div><button type="button" data-add-attribute>+ Aggiungi attributo</button><div class="dialog-command-row"><button type="button" data-cancel>Annulla</button><button type="button" class="primary-command" data-save-attributes>Salva attributi</button></div></div> ` ) ;
const value = window . prompt ( "A ttributi, uno per riga nel formato chiave=valore\nEsempi: foglio, particella, proprietario, qualita, data_rilievo, stato" , current ) ;
const rows = dialog . querySelector ( ".a ttribute-editor-rows" ) ;
if ( value === null ) return ;
const addRow = ( key = "" , value = "" ) => {
const row = document . createElement ( "div" ) ;
row . className = "attribute-editor-row" ;
row . innerHTML = ` <label>Nome<input type="text" maxlength="60" value=" ${ escapeHtml ( key ) } " placeholder="Es. proprietario"></label><label>Valore<textarea rows="3" maxlength="4000" placeholder="Testo anche su piu righe"> ${ escapeHtml ( String ( value ) ) } </textarea></label><button type="button" title="Rimuovi attributo" aria-label="Rimuovi attributo">× </button> ` ;
row . querySelector ( "button" ) . addEventListener ( "click" , ( ) => row . remove ( ) ) ;
rows . appendChild ( row ) ;
} ;
entries . forEach ( ( [ key , value ] ) => addRow ( key , value ) ) ;
if ( ! entries . length ) addRow ( ) ;
dialog . querySelector ( "[data-add-attribute]" ) . addEventListener ( "click" , ( ) => addRow ( ) ) ;
dialog . querySelector ( "[data-cancel]" ) . addEventListener ( "click" , ( ) => dialog . remove ( ) ) ;
dialog . querySelector ( "[data-save-attributes]" ) . addEventListener ( "click" , ( ) => {
const parsed = { } ;
const parsed = { } ;
value . split ( /\r?\n/ ) . forEach ( line => {
rows . querySelectorAll ( ".attribute-editor-row" ) . forEach ( row => {
const separator = line . indexOf ( "=" ) ;
const key = row . querySelector ( "input" ) . value . trim ( ) . replace ( /[^a-zA-Z0-9_ -]/g , "_" ) . slice ( 0 , 60 ) ;
if ( separator < 1 ) return ;
const value = row . querySelector ( "textarea" ) . value . slice ( 0 , 4000 ) ;
const key = line . slice ( 0 , separator ) . trim ( ) . replace ( /[^a-zA-Z0-9_ -]/g , "_" ) . slice ( 0 , 60 ) ;
if ( key ) parsed [ key ] = value ;
const raw = line . slice ( separator + 1 ) . trim ( ) . slice ( 0 , 500 ) ;
if ( key ) parsed [ key ] = raw ;
} ) ;
} ) ;
updateManagedProperties ( [ record ] , target => {
updateManagedProperties ( [ record ] , target => {
Object . keys ( target ) . filter ( key => ! key . startsWith ( "_lme" ) && ! reserved . has ( key ) ) . forEach ( key => delete target [ key ] ) ;
Object . keys ( target ) . filter ( key => ! key . startsWith ( "_lme" ) && ! reserved . has ( key ) ) . forEach ( key => delete target [ key ] ) ;
Object . assign ( target , parsed ) ;
Object . assign ( target , parsed ) ;
} ) ;
} ) ;
finishMapChange ( "Aggiornamento attributi" ) ;
setInfo ( ` ${ Object . keys ( parsed ) . length } attributi aggiornati. ` ) ;
setInfo ( ` ${ Object . keys ( parsed ) . length } attributi aggiornati. ` ) ;
dialog . remove ( ) ;
} ) ;
}
}
function utmFromLatLng ( latitude , longitude ) {
function utmFromLatLng ( latitude , longitude ) {
@@ -1949,19 +2023,66 @@ function showAppDialog(title, bodyHtml) {
return overlay ;
return overlay ;
}
}
function showFormDialog ( title , fields , submitLabel = "Salva" ) {
return new Promise ( resolve => {
const fieldHtml = fields . map ( field => {
const common = ` name=" ${ escapeHtml ( field . name ) } " ${ field . required === false ? "" : "required" } ${ field . maxLength ? ` maxlength=" ${ field . maxLength } " ` : "" } ` ;
const control = field . type === "textarea"
? ` <textarea ${ common } rows=" ${ field . rows || 5 } "> ${ escapeHtml ( field . value ? ? "" ) } </textarea> `
: ` <input ${ common } type=" ${ field . type || "text" } " value=" ${ escapeHtml ( field . value ? ? "" ) } " ${ field . step ? ` step=" ${ field . step } " ` : "" } > ` ;
return ` <label> ${ escapeHtml ( field . label ) } ${ control } ${ field . help ? ` <small> ${ escapeHtml ( field . help ) } </small> ` : "" } </label> ` ;
} ) . join ( "" ) ;
const dialog = showAppDialog ( title , ` <form class="lme-edit-form"> ${ fieldHtml } <div class="dialog-command-row"><button type="button" data-cancel>Annulla</button><button type="submit" class="primary-command"> ${ escapeHtml ( submitLabel ) } </button></div></form> ` ) ;
const form = dialog . querySelector ( "form" ) ;
let settled = false ;
const finish = value => { if ( settled ) return ; settled = true ; resolve ( value ) ; dialog . remove ( ) ; } ;
dialog . querySelector ( "header button" ) . addEventListener ( "click" , ( ) => finish ( null ) , { capture : true } ) ;
dialog . querySelector ( "[data-cancel]" ) . addEventListener ( "click" , ( ) => finish ( null ) ) ;
dialog . addEventListener ( "click" , event => { if ( event . target === dialog ) finish ( null ) ; } , { capture : true } ) ;
form . addEventListener ( "submit" , event => {
event . preventDefault ( ) ;
const values = { } ;
fields . forEach ( field => { values [ field . name ] = form . elements [ field . name ] ? . value ? ? "" ; } ) ;
finish ( values ) ;
} ) ;
form . elements [ fields [ 0 ] ? . name ] ? . focus ( ) ;
} ) ;
}
function showObjectTable ( ) {
function showObjectTable ( ) {
const records = layerManagerCategories ( ) . flatMap ( category => managedRecordsForCategory ( category ) ) ;
const records = layerManagerCategories ( ) . flatMap ( category => managedRecordsForCategory ( category ) ) ;
lmeState . managerRecords = new Map ( records . map ( record => [ record . id , record ] ) ) ;
const body = records . map ( ( record , index ) => {
const body = records . map ( ( record , index ) => {
const properties = record . layer ? . feature ? . properties || { } ;
const properties = record . layer ? . feature ? . properties || { } ;
const details = managedObjectDetails ( record . layer ) ;
const details = managedObjectDetails ( record . layer ) ;
return ` <tr data-object-id=" ${ escapeHtml ( record . id ) } "><td><button type="button" data-locate=" ${ escapeHtml ( record . id ) } ">⌖</button></td><td> ${ escapeHtml ( managedObjectLabel ( record , index ) ) } </td><td> ${ escapeHtml ( record . categoryKey ) } </td><td> ${ escapeHtml ( properties . foglio || properties . FOGLIO || "" ) } </td><td> ${ escapeHtml ( properties . particella || properties . PARTICELLA || properties . LABEL || "" ) } </td><td> ${ escapeHtml ( details . slice ( 0 , 2 ) . join ( " | " ) ) } </td></tr> ` ;
const selected = lmeState . managerSelectedObjectIds . has ( record . id ) ;
const locked = Boolean ( properties . _lmeLocked ) ;
return ` <tr data-object-id=" ${ escapeHtml ( record . id ) } "><td><input type="checkbox" data-select=" ${ escapeHtml ( record . id ) } " ${ selected ? "checked" : "" } aria-label="Seleziona ${ escapeHtml ( managedObjectLabel ( record , index ) ) } "></td><td><input type="checkbox" data-lock=" ${ escapeHtml ( record . id ) } " ${ locked ? "checked" : "" } ${ lmeState . readOnly ? "disabled" : "" } aria-label="Blocca ${ escapeHtml ( managedObjectLabel ( record , index ) ) } "></td><td><button type="button" data-locate=" ${ escapeHtml ( record . id ) } " title="Mostra sulla mappa" aria-label="Mostra sulla mappa">⌖</button></td><td> ${ escapeHtml ( managedObjectLabel ( record , index ) ) } </td><td> ${ escapeHtml ( managedCategoryName ( layerManagerCategories ( ) . find ( item => item . key === record . categoryKey ) || { key : record . categoryKey , label : record . categoryKey } , [record]))}</td><td> ${ escapeHtml ( properties . foglio || properties . FOGLIO || "" ) } </td><td> ${ escapeHtml ( properties . particella || properties . PARTICELLA || properties . LABEL || "" ) } </td><td> ${ escapeHtml ( details . slice ( 0 , 2 ) . join ( " | " ) ) } </td></tr> ` ;
} ) . join ( "" ) ;
} ) . join ( "" ) ;
const dialog = showAppDialog ( "Elenco oggetti e particelle" , ` <label class="object-table-search">Filtra<input type="search" placeholder="Nome, foglio, particella o gruppo"></label><div class="object-table-wrap"><table><thead><tr><th> </th><th>Nome</th><th>Gruppo</th><th>Foglio</th><th>Particella</th><th>Misure</th></tr></thead><tbody> ${ body || "<tr><td colspan='6 '>Nessun oggetto</td></tr>" } </tbody></table></div> ` ) ;
const dialog = showAppDialog ( "Elenco oggetti e particelle" , ` <label class="object-table-search">Filtra<input type="search" placeholder="Nome, foglio, particella o gruppo"></label><div class="object-table-legend"><span>Seleziona più oggetti con i flag nella colonna Selez.</span><span>Il lucchetto impedisce modifiche e rimozioni.</span></div><div class="object-table-wrap"><table><thead><tr><th>Selez.</th><th>Blocca</th><th>Mappa </th><th>Nome</th><th>Gruppo</th><th>Foglio</th><th>Particella</th><th>Misure</th></tr></thead><tbody> ${ body || "<tr><td colspan='8 '>Nessun oggetto</td></tr>" } </tbody></table></div> ` ) ;
const byId = new Map ( records . map ( record => [ record . id , record ] ) ) ;
const byId = new Map ( records . map ( record => [ record . id , record ] ) ) ;
dialog . querySelector ( "input" ) . addEventListener ( "input" , event => {
dialog . querySelector ( "input[type='search'] " ) . addEventListener ( "input" , event => {
const query = event . target . value . toLocaleLowerCase ( "it" ) ;
const query = event . target . value . toLocaleLowerCase ( "it" ) ;
dialog . querySelectorAll ( "tbody tr" ) . forEach ( row => { row . hidden = query && ! row . textContent . toLocaleLowerCase ( "it" ) . includes ( query ) ; } ) ;
dialog . querySelectorAll ( "tbody tr" ) . forEach ( row => { row . hidden = query && ! row . textContent . toLocaleLowerCase ( "it" ) . includes ( query ) ; } ) ;
} ) ;
} ) ;
dialog . querySelectorAll ( "[data-select]" ) . forEach ( input => input . addEventListener ( "change" , ( ) => {
if ( input . checked ) lmeState . managerSelectedObjectIds . add ( input . dataset . select ) ;
else lmeState . managerSelectedObjectIds . delete ( input . dataset . select ) ;
const record = byId . get ( input . dataset . select ) ;
if ( record && input . checked ) {
lmeState . managerHighlightedObjectId = record . id ;
lmeState . managerHighlightedCategoryKey = record . categoryKey ;
renderManagerHighlight ( [ record ] ) ;
}
refreshLayerManager ( ) ;
setInfo ( ` ${ lmeState . managerSelectedObjectIds . size } oggetti selezionati. ` ) ;
} ) ) ;
dialog . querySelectorAll ( "[data-lock]" ) . forEach ( input => input . addEventListener ( "change" , ( ) => {
const record = byId . get ( input . dataset . lock ) ;
if ( ! record ) return ;
updateManagedProperties ( [ record ] , properties => { properties . _lmeLocked = input . checked ; } ) ;
setInfo ( input . checked ? "Oggetto bloccato." : "Oggetto sbloccato." ) ;
} ) ) ;
dialog . querySelectorAll ( "[data-locate]" ) . forEach ( button => button . addEventListener ( "click" , ( ) => {
dialog . querySelectorAll ( "[data-locate]" ) . forEach ( button => button . addEventListener ( "click" , ( ) => {
const record = byId . get ( button . dataset . locate ) ; if ( ! record ) return ;
const record = byId . get ( button . dataset . locate ) ; if ( ! record ) return ;
dialog . remove ( ) ; highlightManagedObject ( record ) ;
dialog . remove ( ) ; highlightManagedObject ( record ) ;
@@ -2088,12 +2209,17 @@ function duplicateManagedRecords(records) {
setInfo ( ` ${ records . length } ${ records . length === 1 ? "oggetto duplicato" : "oggetti duplicati" } . ` ) ;
setInfo ( ` ${ records . length } ${ records . length === 1 ? "oggetto duplicato" : "oggetti duplicati" } . ` ) ;
}
}
function moveManagedRecords ( records ) {
async function moveManagedRecords ( records ) {
if ( lmeState . readOnly ) { setInfo ( "Il progetto e' in sola lettura." ) ; return ; }
if ( lmeState . readOnly ) { setInfo ( "Il progetto e' in sola lettura." ) ; return ; }
if ( records . some ( record => record . layer ? . feature ? . properties ? . _lmeLocked ) ) { setInfo ( "Sblocca gli oggetti da spostare." ) ; return ; }
if ( records . some ( record => record . layer ? . feature ? . properties ? . _lmeLocked ) ) { setInfo ( "Sblocca gli oggetti da spostare." ) ; return ; }
const north = Number ( window . prompt ( "Spostamento verso nord in metri (negativo verso sud)" , "0" ) ) ;
const result = await showFormDialog ( "Sposta oggetti" , [
{ name : "north" , label : "Metri verso nord (negativo verso sud)" , type : "number" , value : "0" , step : "0.01" } ,
{ name : "east" , label : "Metri verso est (negativo verso ovest)" , type : "number" , value : "0" , step : "0.01" }
] , "Sposta" ) ;
if ( ! result ) return ;
const north = Number ( result . north ) ;
if ( ! Number . isFinite ( north ) ) return ;
if ( ! Number . isFinite ( north ) ) return ;
const east = Number ( window . prompt ( "Spostamento verso est in metri (negativo verso ovest)" , "0" ) ) ;
const east = Number ( result . east ) ;
if ( ! Number . isFinite ( east ) || ( ! north && ! east ) ) return ;
if ( ! Number . isFinite ( east ) || ( ! north && ! east ) ) return ;
const distance = Math . hypot ( north , east ) ;
const distance = Math . hypot ( north , east ) ;
const bearing = Math . atan2 ( east , north ) * 180 / Math . PI ;
const bearing = Math . atan2 ( east , north ) * 180 / Math . PI ;
@@ -2105,9 +2231,11 @@ function moveManagedRecords(records) {
finishManagedGeometryChange ( ` Spostati ${ records . length } oggetti. ` ) ;
finishManagedGeometryChange ( ` Spostati ${ records . length } oggetti. ` ) ;
}
}
function bufferManagedRecords ( records ) {
async function bufferManagedRecords ( records ) {
if ( lmeState . readOnly ) { setInfo ( "Il progetto e' in sola lettura." ) ; return ; }
if ( lmeState . readOnly ) { setInfo ( "Il progetto e' in sola lettura." ) ; return ; }
const meters = Number ( window . prompt ( "Distanza buffer in metri" , "5" ) ) ;
const result = await showFormDialog ( "Crea buffer" , [ { name : "meters" , label : "Distanza in metri" , type : "number" , value : "5" , step : "0.01" } ] , "Crea buffer" ) ;
if ( ! result ) return ;
const meters = Number ( result . meters ) ;
if ( ! Number . isFinite ( meters ) || meters <= 0 ) return ;
if ( ! Number . isFinite ( meters ) || meters <= 0 ) return ;
rememberMapChange ( ) ;
rememberMapChange ( ) ;
let count = 0 ;
let count = 0 ;
@@ -2158,14 +2286,17 @@ function splitManagedRecords(records) {
}
}
function selectedManagedRecords ( ) {
function selectedManagedRecords ( ) {
const current = lmeState . managerRecords || new Map ( ) ;
const current = new Map ( layerManagerCategories ( )
. flatMap ( category => managedRecordsForCategory ( category ) )
. map ( record => [ record . id , record ] ) ) ;
lmeState . managerRecords = current ;
return [ ... lmeState . managerSelectedObjectIds ] . map ( id => current . get ( id ) ) . filter ( Boolean ) ;
return [ ... lmeState . managerSelectedObjectIds ] . map ( id => current . get ( id ) ) . filter ( Boolean ) ;
}
}
function createHoleFromSelected ( ) {
function createHoleFromSelected ( ) {
if ( lmeState . readOnly ) { setInfo ( "Il progetto e' in sola lettura." ) ; return false ; }
if ( lmeState . readOnly ) { setInfo ( "Il progetto e' in sola lettura." ) ; return false ; }
const polygons = selectedManagedRecords ( ) . filter ( record => record . layer instanceof L . Polygon ) ;
const polygons = selectedManagedRecords ( ) . filter ( record => record . layer instanceof L . Polygon ) ;
if ( polygons . length !== 2 ) { setInfo ( "Seleziona esattamente due poligoni: prima l'area esterna e poi l'area da escludere." ) ; return false ; }
if ( polygons . length !== 2 ) { showGeometryResult ( "Creazione foro" , "Seleziona esattamente due poligoni: l'area esterna e l'area interna da escludere." , "warning" ) ; return false ; }
try {
try {
const ordered = [ ... polygons ] . sort ( ( left , right ) => turf . area ( right . layer . toGeoJSON ( ) ) - turf . area ( left . layer . toGeoJSON ( ) ) ) ;
const ordered = [ ... polygons ] . sort ( ( left , right ) => turf . area ( right . layer . toGeoJSON ( ) ) - turf . area ( left . layer . toGeoJSON ( ) ) ) ;
const outer = ordered [ 0 ] . layer . toGeoJSON ( ) ;
const outer = ordered [ 0 ] . layer . toGeoJSON ( ) ;
@@ -2179,14 +2310,15 @@ function createHoleFromSelected() {
ordered . forEach ( removeRecordWithoutPrompt ) ;
ordered . forEach ( removeRecordWithoutPrompt ) ;
lmeState . managerSelectedObjectIds . clear ( ) ;
lmeState . managerSelectedObjectIds . clear ( ) ;
finishManagedGeometryChange ( "Poligono con foro creato correttamente." ) ;
finishManagedGeometryChange ( "Poligono con foro creato correttamente." ) ;
showGeometryResult ( "Foro creato" , "Il nuovo poligono contiene l'area interna esclusa. I due oggetti originali sono stati sostituiti." , "success" ) ;
return true ;
return true ;
} catch ( error ) { setInfo ( ` Creazione foro non riuscita: ${ error . message } ` ) ; return false ; }
} catch ( error ) { showGeometryResult ( " Creazione foro non riuscita" , error . message , "error" ) ; return false ; }
}
}
function validateAndRepairSelected ( ) {
function validateAndRepairSelected ( ) {
if ( lmeState . readOnly ) { setInfo ( "Il progetto e' in sola lettura." ) ; return "Progetto in sola lettura" ; }
if ( lmeState . readOnly ) { setInfo ( "Il progetto e' in sola lettura." ) ; return "Progetto in sola lettura" ; }
const records = selectedManagedRecords ( ) ;
const records = selectedManagedRecords ( ) ;
if ( ! records . length ) { setInfo ( "Seleziona gli oggetti da controllare. " ) ; return "Nessuna selezione" ; }
if ( ! records . length ) { showGeometryResult ( "Controlla e ripara" , " Seleziona uno o più oggetti dalla mappa, dal pannello Layer o dalla tabella oggetti." , "warning ") ; return "Nessuna selezione" ; }
let valid = 0 ; let repaired = 0 ; let rejected = 0 ;
let valid = 0 ; let repaired = 0 ; let rejected = 0 ;
rememberMapChange ( ) ;
rememberMapChange ( ) ;
records . forEach ( ( record , index ) => {
records . forEach ( ( record , index ) => {
@@ -2209,14 +2341,16 @@ function validateAndRepairSelected() {
} catch { rejected ++ ; }
} catch { rejected ++ ; }
} ) ;
} ) ;
lmeState . managerSelectedObjectIds . clear ( ) ;
lmeState . managerSelectedObjectIds . clear ( ) ;
finishManagedGeometryChange ( ` Controllo geometrie : ${ valid } valide , ${ repaired } riparate , ${ rejected } non riparabili. ` ) ;
const message = ` Controllati ${ records . length } oggetti : ${ valid } validi , ${ repaired } riparati , ${ rejected } non riparabili. ` ;
finishManagedGeometryChange ( message ) ;
showGeometryResult ( "Controllo geometrie completato" , message , rejected ? "warning" : "success" ) ;
return ` ${ valid } valide, ${ repaired } riparate, ${ rejected } non riparabili ` ;
return ` ${ valid } valide, ${ repaired } riparate, ${ rejected } non riparabili ` ;
}
}
function compareSelectedGeometries ( ) {
function compareSelectedGeometries ( ) {
if ( lmeState . readOnly ) { setInfo ( "Il progetto e' in sola lettura." ) ; return false ; }
if ( lmeState . readOnly ) { setInfo ( "Il progetto e' in sola lettura." ) ; return false ; }
const polygons = selectedManagedRecords ( ) . filter ( record => record . layer instanceof L . Polygon ) ;
const polygons = selectedManagedRecords ( ) . filter ( record => record . layer instanceof L . Polygon ) ;
if ( polygons . length !== 2 ) { setInfo ( "Seleziona esattamente due poligoni da confrontare. " ) ; return false ; }
if ( polygons . length !== 2 ) { showGeometryResult ( "Confronta due confini" , " Seleziona esattamente due poligoni dalla mappa, dal pannello Layer o dalla tabella oggetti." , "warning ") ; return false ; }
try {
try {
const first = polygons [ 0 ] . layer . toGeoJSON ( ) ; const second = polygons [ 1 ] . layer . toGeoJSON ( ) ;
const first = polygons [ 0 ] . layer . toGeoJSON ( ) ; const second = polygons [ 1 ] . layer . toGeoJSON ( ) ;
const onlyFirst = turf . difference ( turf . featureCollection ( [ first , second ] ) ) ;
const onlyFirst = turf . difference ( turf . featureCollection ( [ first , second ] ) ) ;
@@ -2231,34 +2365,72 @@ function compareSelectedGeometries() {
addResult ( onlyFirst , ` Solo ${ managedObjectLabel ( polygons [ 0 ] , 0 ) } ` , "#ef4444" ) ;
addResult ( onlyFirst , ` Solo ${ managedObjectLabel ( polygons [ 0 ] , 0 ) } ` , "#ef4444" ) ;
addResult ( onlySecond , ` Solo ${ managedObjectLabel ( polygons [ 1 ] , 1 ) } ` , "#3b82f6" ) ;
addResult ( onlySecond , ` Solo ${ managedObjectLabel ( polygons [ 1 ] , 1 ) } ` , "#3b82f6" ) ;
addResult ( overlap , "Sovrapposizione" , "#16a34a" ) ;
addResult ( overlap , "Sovrapposizione" , "#16a34a" ) ;
const firstArea = onlyFirst ? turf . area ( onlyFirst ) : 0 ; const secondArea = onlySecond ? turf . area ( onlySecond ) : 0 ;
const firstArea = onlyFirst ? turf . area ( onlyFirst ) : 0 ; const secondArea = onlySecond ? turf . area ( onlySecond ) : 0 ; const overlapArea = overlap ? turf . area ( overlap ) : 0 ;
finishManagedGeometryChange ( ` Confronto creato: eccedenza ${ formatArea ( firstArea ) } , mancanza ${ formatArea ( secondArea ) } . ` ) ;
const message = ` Confronto creato. Solo primo confine: ${ formatArea ( firstArea ) } . Solo secondo confine: ${ formatArea ( secondArea ) } . Sovrapposizione: ${ formatArea ( overlapArea ) } . ` ;
finishManagedGeometryChange ( message ) ;
showGeometryResult ( "Confronto completato" , ` ${ message } Sulla mappa: rosso solo primo, blu solo secondo, verde sovrapposizione. ` , "success" ) ;
return true ;
return true ;
} catch ( error ) { setInfo ( ` Confronto non riuscito: ${ error . message } ` ) ; return false ; }
} catch ( error ) { showGeometryResult ( " Confronto non riuscito" , error . message , "error" ) ; return false ; }
}
}
function numericEditManagedRecord ( record ) {
function showGeometryResult ( title , message , tone = "success" ) {
setInfo ( ` ${ title } : ${ message } ` ) ;
const icons = { success : "✓" , warning : "!" , error : "× " } ;
showAppDialog ( title , ` <div class="geometry-result geometry-result- ${ tone } "><span aria-hidden="true"> ${ icons [ tone ] || "i" } </span><p> ${ escapeHtml ( message ) } </p></div> ` ) ;
}
function addGeometryToolsControl ( ) {
if ( lmeState . geometryToolsControl || ! lmeState . map ) return ;
const GeometryTools = L . Control . extend ( {
options : { position : "topright" } ,
onAdd ( ) {
const container = L . DomUtil . create ( "div" , "leaflet-bar lme-geometry-toolbar" ) ;
const actions = [
{ icon : "✓" , title : "Controlla e ripara geometrie" , run : validateAndRepairSelected } ,
{ icon : "◉" , title : "Crea foro da due poligoni" , run : createHoleFromSelected } ,
{ icon : "⇄" , title : "Confronta due confini" , run : compareSelectedGeometries }
] ;
actions . forEach ( action => {
const button = L . DomUtil . create ( "a" , "" , container ) ;
button . href = "#" ;
button . title = action . title ;
button . setAttribute ( "role" , "button" ) ;
button . setAttribute ( "aria-label" , action . title ) ;
button . textContent = action . icon ;
L . DomEvent . on ( button , "click" , event => { L . DomEvent . stop ( event ) ; action . run ( ) ; } ) ;
} ) ;
L . DomEvent . disableClickPropagation ( container ) ;
return container ;
}
} ) ;
lmeState . geometryToolsControl = new GeometryTools ( ) . addTo ( lmeState . map ) ;
}
async function numericEditManagedRecord ( record ) {
if ( lmeState . readOnly ) { setInfo ( "Il progetto e' in sola lettura." ) ; return ; }
if ( lmeState . readOnly ) { setInfo ( "Il progetto e' in sola lettura." ) ; return ; }
if ( record . layer ? . feature ? . properties ? . _lmeLocked ) { setInfo ( "Sblocca prima l'oggetto." ) ; return ; }
if ( record . layer ? . feature ? . properties ? . _lmeLocked ) { setInfo ( "Sblocca prima l'oggetto." ) ; return ; }
if ( record . layer instanceof L . Circle ) {
if ( record . layer instanceof L . Circle ) {
const center = record . layer . getLatLng ( ) ;
const center = record . layer . getLatLng ( ) ;
const value = window . prompt ( "Latitudine, longitudine, raggio in metri" , ` ${ center . lat . toFixed ( 7 ) } , ${ center . lng . toFixed ( 7 ) } , ${ record . layer . getRadius ( ) . toFixed ( 2 ) } ` ) ;
const result = await showFormDialog ( "Modifica cerchio" , [ { name : "coordinates" , label : " Latitudine, longitudine, raggio in metri", type : "textarea" , rows : 3 , value : ` ${ center . lat . toFixed ( 7 ) } , ${ center . lng . toFixed ( 7 ) } , ${ record . layer . getRadius ( ) . toFixed ( 2 ) } ` } ] , "Applica" ) ;
if ( value === null ) return ;
if ( ! result ) return ;
const value = result . coordinates ;
const parts = value . split ( /[;, ]+/ ) . map ( Number ) . filter ( Number . isFinite ) ;
const parts = value . split ( /[;, ]+/ ) . map ( Number ) . filter ( Number . isFinite ) ;
if ( parts . length < 3 ) { setInfo ( "Inserisci latitudine, longitudine e raggio validi." ) ; return ; }
if ( parts . length < 3 ) { setInfo ( "Inserisci latitudine, longitudine e raggio validi." ) ; return ; }
rememberMapChange ( ) ; record . layer . setLatLng ( [ parts [ 0 ] , parts [ 1 ] ] ) ; record . layer . setRadius ( parts [ 2 ] ) ; annotateDrawingLayer ( record . layer , "circle" ) ;
rememberMapChange ( ) ; record . layer . setLatLng ( [ parts [ 0 ] , parts [ 1 ] ] ) ; record . layer . setRadius ( parts [ 2 ] ) ; annotateDrawingLayer ( record . layer , "circle" ) ;
} else if ( typeof record . layer ? . getLatLng === "function" ) {
} else if ( typeof record . layer ? . getLatLng === "function" ) {
const point = record . layer . getLatLng ( ) ;
const point = record . layer . getLatLng ( ) ;
const value = window . prompt ( "Latitudine, longitudine" , ` ${ point . lat . toFixed ( 7 ) } , ${ point . lng . toFixed ( 7 ) } ` ) ;
const result = await showFormDialog ( "Modifica punto" , [ { name : "coordinates" , label : "Latitudine, longitudine" , type : "textarea" , rows : 3 , value : ` ${ point . lat . toFixed ( 7 ) } , ${ point . lng . toFixed ( 7 ) } ` } ] , "Applica" ) ;
if ( value === null ) return ;
if ( ! result ) return ;
const value = result . coordinates ;
const parts = value . split ( /[;, ]+/ ) . map ( Number ) . filter ( Number . isFinite ) ;
const parts = value . split ( /[;, ]+/ ) . map ( Number ) . filter ( Number . isFinite ) ;
if ( parts . length < 2 ) { setInfo ( "Inserisci coordinate valide." ) ; return ; }
if ( parts . length < 2 ) { setInfo ( "Inserisci coordinate valide." ) ; return ; }
rememberMapChange ( ) ; record . layer . setLatLng ( [ parts [ 0 ] , parts [ 1 ] ] ) ;
rememberMapChange ( ) ; record . layer . setLatLng ( [ parts [ 0 ] , parts [ 1 ] ] ) ;
} else {
} else {
const lines = lineLatLngGroups ( record . layer . getLatLngs ( ) ) ;
const lines = lineLatLngGroups ( record . layer . getLatLngs ( ) ) ;
const current = lines [ 0 ] ? . map ( point => ` ${ point . lat . toFixed ( 7 ) } , ${ point . lng . toFixed ( 7 ) } ` ) . join ( "; " ) || "" ;
const current = lines [ 0 ] ? . map ( point => ` ${ point . lat . toFixed ( 7 ) } , ${ point . lng . toFixed ( 7 ) } ` ) . join ( "; " ) || "" ;
const value = window . prompt ( "Vertici: c oppie latitudine,longitudine separate da punto e virgola" , current ) ;
const result = await showFormDialog ( "Modifica vertici" , [ { name : "coordinates" , label : "C oppie latitudine,longitudine separate da punto e virgola", type : "textarea" , rows : 8 , value : current } ] , "Applica" ) ;
if ( value === null ) return ;
if ( ! result ) return ;
const value = result . coordinates ;
const points = value . split ( ";" ) . map ( pair => pair . split ( "," ) . map ( Number ) ) . filter ( pair => pair . length === 2 && pair . every ( Number . isFinite ) ) ;
const points = value . split ( ";" ) . map ( pair => pair . split ( "," ) . map ( Number ) ) . filter ( pair => pair . length === 2 && pair . every ( Number . isFinite ) ) ;
if ( points . length < 2 ) { setInfo ( "Servono almeno due vertici validi." ) ; return ; }
if ( points . length < 2 ) { setInfo ( "Servono almeno due vertici validi." ) ; return ; }
rememberMapChange ( ) ; record . layer . setLatLngs ( points . map ( point => L . latLng ( point [ 0 ] , point [ 1 ] ) ) ) ;
rememberMapChange ( ) ; record . layer . setLatLngs ( points . map ( point => L . latLng ( point [ 0 ] , point [ 1 ] ) ) ) ;
@@ -2379,6 +2551,7 @@ function setAdvancedOptions(options) {
lmeState . snapEnabled = value . snapEnabled !== false ;
lmeState . snapEnabled = value . snapEnabled !== false ;
lmeState . snapDistancePixels = Math . max ( 4 , Math . min ( 40 , Number ( value . snapDistancePixels ) || 14 ) ) ;
lmeState . snapDistancePixels = Math . max ( 4 , Math . min ( 40 , Number ( value . snapDistancePixels ) || 14 ) ) ;
lmeState . autoSaveEnabled = value . autoSaveEnabled !== false ;
lmeState . autoSaveEnabled = value . autoSaveEnabled !== false ;
lmeState . autoFitSelectedObject = Boolean ( value . autoFitSelectedObject ) ;
lmeState . gpsProximityEnabled = value . gpsProximityEnabled !== false ;
lmeState . gpsProximityEnabled = value . gpsProximityEnabled !== false ;
lmeState . gpsBoundaryAlertEnabled = Boolean ( value . gpsBoundaryAlertEnabled ) ;
lmeState . gpsBoundaryAlertEnabled = Boolean ( value . gpsBoundaryAlertEnabled ) ;
}
}
@@ -2504,6 +2677,26 @@ function bindLayerManagerExpansion(row, key) {
} else {
} else {
lmeState . managerExpandedRows . delete ( key ) ;
lmeState . managerExpandedRows . delete ( key ) ;
}
}
const button = row . querySelector ( ":scope > summary > .layer-expand-button" ) ;
if ( button ) {
button . textContent = row . open ? "▾" : "▸" ;
button . setAttribute ( "aria-expanded" , String ( row . open ) ) ;
}
} ) ;
}
function layerExpandButton ( expanded , label ) {
return ` <button type="button" class="layer-expand-button" aria-label=" ${ expanded ? "Comprimi" : "Espandi" } ${ escapeHtml ( label ) } " aria-expanded=" ${ expanded } "> ${ expanded ? "▾" : "▸" } </button> ` ;
}
function bindLayerManagerToggleButton ( row ) {
const summary = row . querySelector ( ":scope > summary" ) ;
const button = summary ? . querySelector ( ".layer-expand-button" ) ;
summary ? . addEventListener ( "click" , event => event . preventDefault ( ) ) ;
button ? . addEventListener ( "click" , event => {
event . preventDefault ( ) ;
event . stopPropagation ( ) ;
row . open = ! row . open ;
} ) ;
} ) ;
}
}
@@ -2603,8 +2796,22 @@ function highlightManagedObject(record) {
} ) ;
} ) ;
setSelectedDrawing ( record . categoryKey === "drawings" ? record . layer : null ) ;
setSelectedDrawing ( record . categoryKey === "drawings" ? record . layer : null ) ;
renderManagerHighlight ( [ record ] ) ;
renderManagerHighlight ( [ record ] ) ;
if ( lmeState . autoFitSelectedObject ) {
fitLayer ( record . layer ) ;
fitLayer ( record . layer ) ;
}
}
revealManagedRecordInLayerManager ( record . id ) ;
}
function revealManagedRecordInLayerManager ( recordId ) {
const row = document . querySelector ( ` .layer-manager-object[data-object-id=" ${ CSS . escape ( recordId ) } "] ` ) ;
if ( ! row ) return ;
const block = row . closest ( ".layer-manager-block" ) ;
const group = row . closest ( ".layer-manager-group" ) ;
if ( group ) group . classList . add ( "layer-manager-selection-path" ) ;
if ( block ) block . classList . add ( "layer-manager-selection-path" ) ;
row . classList . add ( "layer-manager-object-selected" ) ;
row . scrollIntoView ( { block : "nearest" , behavior : "smooth" } ) ;
}
function drawingEditToolbar ( ) {
function drawingEditToolbar ( ) {
return lmeState . drawControl ? . _toolbars ? . [ L . EditToolbar . TYPE ] || null ;
return lmeState . drawControl ? . _toolbars ? . [ L . EditToolbar . TYPE ] || null ;
@@ -2668,7 +2875,6 @@ function bindManagedParentHighlight(row, key, records) {
row . querySelector ( ":scope > summary > .layer-manager-title" ) . addEventListener ( "click" , event => {
row . querySelector ( ":scope > summary > .layer-manager-title" ) . addEventListener ( "click" , event => {
event . preventDefault ( ) ;
event . preventDefault ( ) ;
event . stopPropagation ( ) ;
event . stopPropagation ( ) ;
row . open = true ;
highlightManagedParent ( row , key , records ) ;
highlightManagedParent ( row , key , records ) ;
} ) ;
} ) ;
summary . addEventListener ( "pointerenter" , ( ) => {
summary . addEventListener ( "pointerenter" , ( ) => {
@@ -3404,6 +3610,7 @@ function bindDrawingLayerSelection(layer) {
}
}
selectDrawingLayer ( layer ) ;
selectDrawingLayer ( layer ) ;
refreshLayerManager ( ) ;
refreshLayerManager ( ) ;
window . requestAnimationFrame ( ( ) => revealManagedRecordInLayerManager ( id ) ) ;
} ) ;
} ) ;
}
}
@@ -3424,6 +3631,7 @@ function bindManagedMapSelection(layer, categoryKey, parent) {
else lmeState . managerSelectedObjectIds . add ( record . id ) ;
else lmeState . managerSelectedObjectIds . add ( record . id ) ;
highlightManagedObject ( record ) ;
highlightManagedObject ( record ) ;
refreshLayerManager ( ) ;
refreshLayerManager ( ) ;
window . requestAnimationFrame ( ( ) => revealManagedRecordInLayerManager ( record . id ) ) ;
setInfo ( ` Selezionato dalla mappa: ${ managedObjectEditableName ( record ) || "oggetto" } . ` ) ;
setInfo ( ` Selezionato dalla mappa: ${ managedObjectEditableName ( record ) || "oggetto" } . ` ) ;
} ) ;
} ) ;
}
}
@@ -4123,23 +4331,6 @@ function emptyFeatureCollection() {
function managedGroupGeoJson ( group , categoryKey ) {
function managedGroupGeoJson ( group , categoryKey ) {
const collection = group ? . toGeoJSON ? . ( ) || emptyFeatureCollection ( ) ;
const collection = group ? . toGeoJSON ? . ( ) || emptyFeatureCollection ( ) ;
const features = [ ... ( collection . features || [ ] ) ] ;
const features = [ ... ( collection . features || [ ] ) ] ;
const existingIds = new Set ( features . map ( feature => feature ? . properties ? . _lmeManagerId ) . filter ( Boolean ) ) ;
lmeState . managerHiddenObjects . forEach ( record => {
if ( record . categoryKey !== categoryKey ) {
return ;
}
const hidden = record . layer ? . toGeoJSON ? . ( ) ;
const hiddenFeatures = hidden ? . type === "FeatureCollection" ? hidden . features : hidden ? [ hidden ] : [ ] ;
hiddenFeatures . forEach ( feature => {
const id = feature ? . properties ? . _lmeManagerId ;
if ( ! id || ! existingIds . has ( id ) ) {
features . push ( feature ) ;
if ( id ) {
existingIds . add ( id ) ;
}
}
} ) ;
} ) ;
const categoryName = lmeState . managerCategoryNames [ categoryKey ] ;
const categoryName = lmeState . managerCategoryNames [ categoryKey ] ;
if ( categoryName ) {
if ( categoryName ) {
features . forEach ( feature => {
features . forEach ( feature => {