PostGIS | elencare la lunghezza totale di determinati elementi lineari (es: strade)

Creazione di una query che sommi la lunghezza dei tratti di strada (geometria LINESTRING) con lo stesso nome (in questo esempio solamente di alcuni nomi):

=# SELECT DISTINCT nome, SUM(ST_length(geom)) AS lunghezza FROM tabella_strade WHERE nome IN (‘VIA SERENISSIMA’, ‘VIA TAGLIAMENTO’, ‘VIA TICINO’, ‘VIA TEVERE’, ‘VIA GIUSEPPE VE
RDI’, ‘VIA GIOVANNI VERGA’, ‘VIA ANTONIO VIVALDI’, ‘VIA GIACOMO ZANELLA’, ‘VICOLO VOLTURNO’) GROUP BY nome ORDER BY nome DESC;

nome | lunghezza
———————+——————
VICOLO VOLTURNO | 124.614359258113
VIA TICINO | 143.308534004537
VIA TEVERE | 145.891075563617
VIA TAGLIAMENTO | 556.614244025521
VIA SERENISSIMA | 245.633379632897
VIA GIUSEPPE VERDI | 226.941950908202
VIA GIOVANNI VERGA | 357.540027469002
VIA GIACOMO ZANELLA | 334.530884672328
VIA ANTONIO VIVALDI | 100.229389901525
(9 righe)

“nome” è il campo contenente il nome della strada

PostgreSQL-PostGIS | Database History – Cronologia delle modifiche utilizzando i trigger

Tratto, tradotto e leggermente modificato da: https://postgis.net/workshops/postgis-intro/history_tracking.html


Questo articolo non è assolutamente “farina del mio sacco”; ho soltanto tradotto e leggermente modificato quanto riportato in una pagina fatta molto bene (ringrazio sentitamente l’autore o gli autori) che si trova qui: https://postgis.net/workshops/postgis-intro/history_tracking.html adattando l’esempio ai dati che ho utilizzato per fare un test. Funziona davvero bene!


Un requisito comune per i database è la capacità di tenere traccia della cronologia: come sono cambiati i dati tra due date, chi ha apportato le modifiche e dove si sono verificate?

Utilizzando il database e il sistema di trigger, è possibile aggiungere il monitoraggio della cronologia a qualsiasi tabella, mantenendo un semplice accesso di “modifica diretta” alla tabella principale.

Il monitoraggio della cronologia funziona mantenendo una tabella della cronologia che registra, per ogni modifica:

  • Se un record è stato creato, quando è stato aggiunto e da chi.
  • Se un record è stato eliminato, quando è stato eliminato e da chi.
  • Se un record è stato aggiornato, aggiungendo un record di cancellazione (per il vecchio stato) e un record di creazione (per il nuovo stato).

Utilizzo di TSTZRANGE

La tabella della cronologia utilizza una funzionalità specifica di PostgreSQL, il tipo “intervallo di timestamp” (tstzrange), per memorizzare l’intervallo di tempo in cui un record della cronologia era il record “live”. Tutti gli intervalli di timestamp nella tabella della cronologia per una particolare caratteristica non si sovrappongono ma restano adiacenti.

L’intervallo per un nuovo record inizierà a now() e avrà un punto finale aperto, in modo che l’intervallo copra tutto il tempo dall’ora corrente al futuro.

Impartendo il comando seguente:

=# SELECT tstzrange(current_timestamp, NULL);

Otterremo per esempio:

=#

tstzrange
————————————
[“2023-08-01 14:49:40.910074-07”,)

Allo stesso modo, l’intervallo di tempo per un record eliminato verrà aggiornato per includere l’ora corrente come punto finale dell’intervallo di tempo.

La ricerca di intervalli di tempo è molto più semplice della ricerca di una coppia di timestamp: un intervallo di tempo aperto comprende tutto il tempo dal punto iniziale all’infinito. Per gli intervalli useremo l’operatore “contiene” @>. Per esempio:

=# SELECT tstzrange(current_timestamp – ’10m’::interval, NULL) @> current_timestamp;

Gli intervalli possono essere indicizzati in modo molto efficiente utilizzando un indice GIST, proprio come i dati spaziali, come mostreremo di seguito. Ciò rende le query di cronologia molto efficienti.

Costruire la tabella della storia (history)

Utilizzando queste informazioni è possibile ricostruire lo stato della tabella di modifica in qualsiasi momento. Partiamo per esempio da una tabella che contiene lo stradario (strade); aggiungeremo quindi il monitoraggio della cronologia alla nostra tabella “strade”.

Innanzitutto, aggiungiamo una nuova tabella “strade_history”. Questa è la tabella che useremo per memorizzare tutte le informazioni di modifica storiche. Oltre a tutti i campi di “strade”, aggiungiamo altri quattro campi.

  • hid: la chiave primaria per la tabella della cronologia
  • created_by: l’utente del database che ha creato il record
  • deleted_by: l’utente del database che ha causato la marcatura del record come cancellato
  • valid_range: l’intervallo di tempo entro il quale il record era “live”

Si noti che in realtà non eliminiamo alcun record nella tabella della cronologia, contrassegniamo solo l’ora in cui hanno cessato di far parte dello stato corrente della tabella di modifica.

=# CREATE TABLE strade_history (hid SERIAL PRIMARY KEY, gid INTEGER, nome VARCHAR(50), geom GEOMETRY (MULTILINESTRING, 3003), valid_range TSTZRANGE, created_by VARCHAR(32), deleted_by VARCHAR(32));

=# CREATE INDEX strade_history_geom_x ON strade_history USING GIST (geom);

=# CREATE INDEX strade_history_tstz_x ON strade_history USING GIST (valid_range);

Successivamente, importiamo lo stato corrente della tabella attiva, “strade” nella tabella della cronologia, in modo da avere un punto di partenza da cui tracciare la cronologia. Si noti che inseriamo l’ora di creazione e l’utente di creazione, ma lasciamo NULL la fine dell’intervallo di tempo e l’informazione “deleted_by”.

=# INSERT INTO strade_history (gid, nome, geom, valid_range, created_by) SELECT gid, nome, geom, tstzrange(now(), NULL), current_user FROM strade;

Ora abbiamo bisogno di tre trigger sulla tabella attiva, per le azioni INSERT, DELETE e UPDATE. Per prima cosa creiamo le funzioni trigger, quindi le colleghiamo alla tabella come trigger.

Per un nuovo inserimento, aggiungiamo semplicemente un nuovo record nella tabella della cronologia con l’ora di creazione/utente.

=# CREATE OR REPLACE FUNCTION strade_insert() RETURNS trigger AS
$$
BEGIN
INSERT INTO strade_history
(gid, nome, geom, valid_range, created_by)
VALUES
(NEW.gid, NEW.nome, NEW.geom,
tstzrange(current_timestamp, NULL), current_user);
RETURN NEW;
END;
$$
LANGUAGE plpgsql;

Quindi creiamo il primo trigger per l’inserimento

=# CREATE TRIGGER strade_insert_trigger AFTER INSERT ON strade FOR EACH ROW EXECUTE PROCEDURE strade_insert();

Per la cancellazione, contrassegniamo semplicemente il record della cronologia attualmente attivo (quello con un tempo di cancellazione NULL) come cancellato.

=# CREATE OR REPLACE FUNCTION strade_delete() RETURNS trigger AS
$$
BEGIN
UPDATE strade_history
SET valid_range = tstzrange(lower(valid_range), current_timestamp),
deleted_by = current_user
WHERE valid_range @> current_timestamp AND gid = OLD.gid;
RETURN NULL;
END;
$$
LANGUAGE plpgsql;

E relativo trigger per l’eliminazione:

=# CREATE TRIGGER strade_delete_trigger AFTER DELETE ON strade FOR EACH ROW EXECUTE PROCEDURE strade_delete();

Per un aggiornamento, prima contrassegniamo il record della cronologia attivo come eliminato, quindi inseriamo un nuovo record per lo stato aggiornato.

=# CREATE OR REPLACE FUNCTION strade_update() RETURNS trigger AS
$$
BEGIN
UPDATE strade_history
SET valid_range = tstzrange(lower(valid_range), current_timestamp),
deleted_by = current_user
WHERE valid_range @> current_timestamp AND gid = OLD.gid;
INSERT INTO strade_history
(gid, nome, geom, valid_range, created_by)
VALUES
(NEW.gid, NEW.nome, NEW.geom,
tstzrange(current_timestamp, NULL), current_user);
RETURN NEW;
END;
$$
LANGUAGE plpgsql;

E relativo trigger per l’aggiornamento:

=# CREATE TRIGGER strade_update_trigger AFTER UPDATE ON strade FOR EACH ROW EXECUTE PROCEDURE strade_update();

Modificare la tabella

Ora che la tabella della cronologia è abilitata, possiamo apportare modifiche alla tabella principale (“strade”) e guardare le voci del registro apparire nella tabella della cronologia (“history_strade”).

Da notare la potenza di questo approccio alla cronologia basato su database: indipendentemente dallo strumento utilizzato per apportare le modifiche, che si tratti della riga di comando SQL, di uno strumento JDBC basato sul Web o di uno strumento desktop come QGIS, la cronologia viene costantemente monitorata.

Modifiche SQL

Trasformiamo la due strada denominata “VIA ROMA” in “VIA ROMA IMPERIALE” (per esempio):

L’aggiornamento della strada farà sì che la strada originale venga contrassegnata come cancellata nella tabella della cronologia, con un’ora di eliminazione di adesso, e una nuova strada con l’aggiunta del nuovo nome, con un’ora di aggiunta di adesso. Possiamo consultare i dati storici.

Interrogazione della tabella cronologica

Ora che abbiamo una tabella cronologica, a cosa serve? È utile per viaggiare nel tempo! Per viaggiare in un particolare tempo T, è necessario costruire una query che includa:

  • Tutti i record creati prima di T e non ancora cancellati; e anche
  • Tutti i record creati prima di T, ma eliminati dopo T.

Possiamo usare questa logica per creare una query, o una vista, dello stato dei dati nel passato. Poiché presumibilmente tutte le tue modifiche di prova sono avvenute negli ultimi due minuti, creiamo una vista della tabella della cronologia che mostri lo stato della tabella 10 minuti fa, prima che si iniziasse a modificare (quindi, i dati originali).

=# CREATE OR REPLACE VIEW strade_dieci_min_ago AS SELECT * FROM strade_history WHERE valid_range @> (now() – ’10min’::interval);

Caricando questa vista su QGIS possiamo vedere i dati come erano 10 minuti fa

Se invece volessimo recuperare la versione della tabella ad una certa data (per esempio alla data del 01 agosto 2023):

=# CREATE OR REPLACE VIEW strade_al_20230801 AS SELECT strade_history.hid, strade_history.gid, strade_history.nome, strade_history.geom, strade_history.valid_range, strade_history.created_by, strade_history.deleted_by FROM strade_history WHERE lower(strade_history.valid_range) < ‘2023-08-02 13:10:38’;

In questo modo avremo tutti gli oggetti presenti nel database prima del 2 agosto 2023 alle ore 13:10:38″. Verranno pertanto esclusi tutti gli oggetti creati, modificati o eliminati dopo quel momento.

Libreoffice Calc | Funzione LEFT(), RIGHT(), MID()

In Libreoffice Calc si possono usare alcune funzioni per gestire testi (Stringhe).

Supponiamo di avere un campo in cui la data è riportata nella forma GGMMAAAA all’interno di una cella in formato Testo.

Possiamo dividere (splittare) il valore del giorno GG, mese MM e anno AAAA in tre colonne distinte utilizzando le funzioni LEFT(), RIGHT(), e (MID). Le equivalenti versioni in italiano si traducono in SINISTRA(), DESTRA() e STRINGA.ESTRAI(). Vediamo un esempio.

Sia data una cella (supponiamo la cella C7) contente il valore 27012022.

Popoliamo una nuova colonna per il giorno con la funzione:

=SINISTRA(C7;2)

che fornirà come risultato “27”

Popoliamo una nuova colonna per il mese con la funzione:

=STRINGA.ESTRAI(C7;3;2)

che fornirà come risultato “01”

Popoliamo una nuova colonna per il mese con la funzione:

=DESTRA(C7;4)

che fornirà come risultato “2022”

PostrgreSQL, PostGIS | disabilitare/abilitare Trigger

Se ad una tabella sono collegati dei trigger è possibile disabilitarli temporaneamente e riabilitarli in un secondo momento. Prima di tutto è necessario conoscere quali siano i trigger della tabella in oggetto con il comando:

=# \dS nome_tabella

che restituisce la lista dei trigger attivi.

Per disabilitarne uno:

=# ALTER TABLE nome_tabella DISABLE TRIGGER nome_trigger;

rilanciando il comando

=# \dS nome_tabella

vedremo che il trigger disabilitato verrà marcato con non attivo.

Per riabilitarlo:

=# ALTER TABLE nome_tabella ENABLE TRIGGER nome_trigger;

PostGIS | conversione da MULTISURFACE a POLYGON

Per convertire una tabella geometrica da MULTISURFACE a POLYGON è necessario prima creare una nuova tabella selezionando tutti i campi della tabella originaria con il comando:

=# CREATE TABLE nome_tabella_new AS SELECT id, nome_campo1, come_campo2, ….ST_CurveToLine(the_geom) AS the geom FROM nome_tabella_originaria;

nel nostro caso abbiamo ottenuto una tabella di tipo GEOMETRY, per cambiare in POLYGON (oppure MULTIPOLYGON):

=# ALTER TABLE nome_tabella_new ALTER COLUMN the_geom type geometry(MultiPolygon, 3003) using ST_Multi(the_geom);

GeoMOOSE | ricerca esatta

In GeoMOOSE la ricerca degli oggetti viene configurata agendo sui file “app.js” e “mapbook.xml”. Per default l’operatore che effettua la ricerca è “ILIKE” che offre in risposta tutte le ricorrenze che contengono anche solo una parte della stringa o del numero cercato.

Se volessimo effettuare una ricerca esatta è necessario modificare il file app.js. Si riporta un esempio di codice di default e di quello modificato:

default:
———————————————————————-
app.registerService(‘search-particella’, SearchService, {
searchLayers: [‘particella-wfs/particella_patrimonio’],
fields: [
 {type: ‘text’, label: ‘Foglio’, name: ‘foglio_id’},
 {type: ‘text’, label: ‘Mappale’, name: ‘mappale’}
 ]
 });
———————————————————————-

modificato per ricerca esatta:
———————————————————————-
app.registerService(‘search-particella’, SearchService, {
fields: [
{type: ‘text’, label: ‘Foglio’, name: ‘foglio_id’},
{type: ‘text’, label: ‘Mappale’, name: ‘mappale’}
],
prepareFields: function (fields) {
// reformat the fields for the query engine,
// “*stuff*” will do a case-ignored “contains” query.
var query = [‘and’];
for(var i = 0, ii = fields.length; i < ii; i++) {
if(fields[i].value !== ” && fields[i].value !== undefined) {
query.push({
comparitor: ‘eq’,
name: fields[i].name,
value:fields[i].value
});
}
}
return [query];
},
searchLayers: [‘particella-wfs/particella_patrimonio’]
});
———————————————————————-

da notare l’inserimento del valore “comparitor: ‘eq'” che forza la corrispondenza esatta dei termini cercati. Questo operatore funziona con numeri e con stringhe. Tuttavia può succedere che la ricerca non funzioni se i campi sono di tipo “varchar” ma i termini di ricerca inseriti sono numeri, ottenendo l’errore in GeoMOOSE:

———————————————————————-

Error
msWFSGetFeature(): WFS server error. FLTApplyFilterToLayer() failed
msPostGISLayerWhichShapes(): Query error. Error executing query. Check
server logs

———————————————————————-

e controllando il log di PostgrSQL si ottiene:

———————————————————————-

ERROR: operator does not exists: character varying = integer at character 572
HINT: No operators found with given name and argument types. It may
be necessary to convert the indicated types.
INSTRUCTION: select
“gid”::text,”foglio_id”::text,”mappale”::text,”tipobene”::text,”numero_repertorio”::text,”vincolo”::text,”ex_num_rep”::text,”Note”::text,”tooltip”::text,”Controllo”::text,”AnnoRep”::text,ST_AsBinary(ST_Force2D(“the_geom”),’NDR’)
as geom,”gid”::text from particella_patrimonio where “the_geom” &&
ST_GeomFromText(‘POLYGON((1684664.20503714
5037777.87184059,1684664.20503714 5049622.92254882,1692749.83495607
5049622.92254882,1692749.83495607 5037777.87184059,1684664.20503714
5037777.87184059))’,find_srid(”,’particella_patrimonio’,’the_geom’))
and (((“foglio_id” = 2) and (“mappale” = 23)))

———————————————————————-

In questo caso (grazie infinite a Dan Little Duck) è necessario modificare il MAPFILE dichiarando il tipo di dato esplicitamente (nella sezione METADATA del LAYER) altrimenti Mapserver, ottenendo dei numeri come termini della ricerca, considera i valori come “int” anche se il campo che li contiene è di tipo “varchar”; il MAPFILE va quindi così modificato:

———————————————————————-

MAP
   LAYER
   …………..
   …….
      METADATA
      …….
         “gml_foglio_id_type” “Character”
         “gml_mappale_type” “Character”
      ……
      END
      …….
   END
   ……
END

———————————————————————-

Documentazione di riferimento: https://mapserver.org/ogc/wfs_server.html

GeoMOOSE | Nozioni generali

GeoMOOSE (https://geomoose.org) è un webgis (framework) per Mapserver che utilizza javascript.

La configurazione principale avviene modificando i file:

– app.js

– mapbook.xml

e sempre facendo riferimento al MAPFILE necessario a Mapserver per restituire i dati cartografici ad ogni richiesta cgi-bin.

Per default tutti i dati cartografici sono gestiti da GeoMOOSE in coordinate proiettate EPSG:3857 (Pseudo Mercator)

La definizione della vista iniziale della mappa si imposta nel file app.js nel blocco:

app.loadMapbook({url: ‘mapbook.xml’}).then(function() {

// set the default view.

app.setView({

// center: [ 1688250, 5042650 ],

center: [1274028, 5703750 ],

zoom: 13

});

nell’esempio riportato le coordinate sono il centro di Montecchio Maggiore espresse in EPSG:3857

Postgresql | aggiornare un campo con il solo valore ‘year’ di un altro campo di tipo DATE

Supponiamo di avere una tabella con un campo “data” di tipo DATE (nella forma “aaaa-mm-gg” – esempio: 2018-01-03) e vogliamo popolare un altro campo (per esempio “anno”) di tipo integer con il solo valore dell’anno prendendolo dal campo “data”. Ecco la sintassi SQL:

=# UPDATE nome_tabella SET anno=date_part(‘year’, data);