-----------------------------------

Acquista i software ArcGIS tramite Studio A&T srl, rivenditore autorizzato dei prodotti Esri.

I migliori software GIS, il miglior supporto tecnico!

I migliori software GIS, il miglior supporto tecnico!
Azienda operante nel settore GIS dal 2001, specializzata nell’utilizzo della tecnologia ArcGIS e aderente ai programmi Esri Italia Business Network ed Esri Partner Network

-----------------------------------



giovedì 18 agosto 2011

Archiving: questo sconosciuto ...

Quante volte ti hanno chiesto di tenere traccia di tutte le modifiche apportate nel tempo al geodatabase? Spesso la gestione della history dei dati è un’operazione che non viene effettuata. Ma, così facendo, perdiamo moltissime informazioni. Ad esempio, a domande di questo tipo non potremmo rispondere:
-  che valore aveva uno specifico attributo in un determinato periodo di tempo?
-  come si è modificata nel tempo una specifica feature o row?
-  come è cambiata nel tempo la geometria di un poligono? 
Quante operazioni di analisi spaziale eseguiamo senza considerare le differenze temporali relative ai dati: capite che l’elaborazione potrebbe non dare risultati corretti.  Il caso più evidente, per fare un esempio pratico, sono le particelle catastali che hanno una dinamicità nel tempo abbastanza elevata (frazionamenti, fusioni, cambi di proprietà ecc.) ma se ci pensate bene anche dati come i comuni o le province cambiano nel tempo (esempio: formazione della provincia di Monza e Brianza o - voci di questi giorni - l’eliminazione di provincie con meno di 300000 abitanti).
Archiving in ArcGIS, introdotto dalla versione 9.2, fornisce la funzionalità di registrare ed accedere ai cambiamenti apportati a tutti i dati o ad un sottoinsieme di essi di un geodatase versionato. Archiving è il meccanismo per catturare, gestire ed analizzare i dati che vengono modificati nel tempo.
Il geodatabase archiving mantiene i cambi dal momento in cui viene abilitato a farlo fino a quando l’archiving è disabilitato.
Precedentemente all’introduzione dell’archiving, una delle soluzioni alternative per catturare le modifiche nel tempo era quella di creare delle versioni, che fungevano da snapshots, per l’intero geodatabase. Comunque, una soluzione di questo tipo non era completamente soddisfacente perché, una volta che i dati venivano deversionati o la versione veniva eliminata, si perdeva la rappresentazione storica dei dati. L’archiving avviene parallelamente alle modiche dei dati non appesantendo il sistema e richiede meno consumo di risorse quando si utilizza rispetto all’equivalente classe versionata.
Il Geodatabase archiving introduce la versione historical in aggiunta a quella già esistente ovvero la versione transactional.
L’utente può connettersi o ad una versione transactional o ad una versione historical.
Una versione transactional consente ad un utente di modificare i dati. Una versione historical rappresenta i dati ad uno specifico momento nel tempo e fornisce una rappresentazione in sola lettura del geodatabase.
Un utente può connettersi ad una versione historical utilizzando un riferimento storico (historical marker) o specificando un momento ben preciso. In sostanza con il riferimento storico nominiamo un tempo (esempio: “incendio boschivo zona Spinelli” è il nostro riferimento storico per fare riferimento alla data 14:00 PM Giugno 2003).
Il modello archiving supporta completamente il modello dati del geodatabase: pertanto stand-alone feature classes, feature datasets, tables, relationship classes, networks, topologies, e terrains possono partecipare all’archiving.
L’archiving richiede che i dati siano registrati come versionati e con l’opzione “Register the selected objects with the option to move edits to base.” non selezionata.
Una volta che l’archiving è abilitato, tutti i cambi salvati o postati alla versione di DEFAULT sono registrati nella corrispondente classe archive. La classe archive è una completa copia della classe abilitata più tutte le modifiche che sono stata salvate o postate alla versione di DEFAULT.
Una cosa importate da capire è però come ArcGIS rappresenti il tempo quando i cambi vengono effettuati. La history può essere registrata o con un valido momento temporale o con un riferimento temporale transaction. La differenza tra i due è che il primo rappresenta il momento temporale in cui i cambi sono avvenuti nel mondo reale e normalmente vengono registrati dall’utente che applica le modifiche mentre il secondo è il momento in cui il dato è stato modificato nel database. I riferimenti temporali transaction sono generati automaticamente dal sistema e sono quelli utilizzati da ArcGIS. Pertanto occorre prestare molta attenzione per non avere ad esempio modifiche nel geodatabase nell’ordine non corretto rispetto al mondo reale.
Illustriamo ora come abilitare e gestire l’archiving. In questo caso utilizzo la licenza ArcEditor con installato un personal SDE geodatabase memorizzato in SQL Server Express 2008 R2 e copiamo la feature class Trees che trovate nel file geodatabase GDBTrees.gdb  della cartella C:\Programmi\ArcGIS\ DeveloperKit10.0\Samples\data\Trees.

 

 
Da ArcCatalog seleziono la feature class che desidero abilitare all’archiving.
Come detto precedentemente, per poter abilitare la feature class all’archiving, devo in via preliminare versionarla con l’opzione “Register the selected objects with the option to move edits to base.” non abilitata.

 

 

 
Un dataset abilitato all’archiving abiliterà tutte le feature class contenute in esso.
Lato programmazione: se la proprietà moveEditsToBase è abilitata sulla nostra object class, occorre chiamare dall’interfaccia IVersionedObject3 il metodo UnRegisterAsVersioned3(false) per deregistrare la classe come versionata disabilitando il ‘moving of edits to the base table’ e poi chiamare sempre dalla IVersionedObject3  il metodo RegisterAsVersioned3(false) per registrare l’object come versionato con l’opzione moveEditsToBase a false.
A questo punto possiamo abilitare l’archiving:

 
 
Nel momento in cui si abilita l’archiving nel geodatabase viene creata una classe archiving che tiene traccia di tutte le modiche effettuare sull’ object class (in questo esempio la feature class Trees). Nel geodatabase la classe archiving è una tabella con il nome della feature class con l’aggiunta del suffisso ‘_H’
 

Oltre ai campi dell’object class (nel nostro esempio: ObjectID, Year_Planted e Shape) all’archiving class vengono aggiunti anche i campi: GDB_FROM_DATE, GDB_TO_DATE e GDB_ARCHIVE_OID.

 
L’GDB_ARCHIVE_OID funge da OBJECTID nella classe archiving (Trees_H).

 

 
E’ possibile modificare il nome della classe archiving ed i nomi dei campi aggiunti per l’archiving utilizzando la classe ArchiveRegistrationInfo.

 

 

 
namespace Studioat.Samples.Archiving
{
    using System;
    using System.Collections.Generic;
    using ESRI.ArcGIS.esriSystem;
    using ESRI.ArcGIS.Geodatabase;
 
    internal class Program
    {
        private static LicenseInitializer licenseInitializer = new LicenseInitializer();
    
        [STAThread]
        internal static void Main(string[] args)
        {
            ////ESRI License Initializer generated code.
            licenseInitializer.InitializeApplication(new esriLicenseProductCode[] { esriLicenseProductCode.esriLicenseProductCodeArcEditor }, new esriLicenseExtensionCode[] { });
 
            ////connessione ad una versione transactional
            IWorkspace workpace = Archive.GetPersonalOrWorkgroupArcSdeWorkspace("studioat40_sqlexpress""sde:sqlserver:studioat40\\sqlexpress""OSA""History"Archive.TypeVersion.VERSION, "dbo.DEFAULT");
 
            IFeatureWorkspace fw = workpace as IFeatureWorkspace;
            IObjectClass oc = fw.OpenFeatureClass("Trees"as IObjectClass;
 
            ////facoltativo il passaggio di un set di ArchiveRegistrationInfo per indicare dei nomi diversi
            ////rispetto a quelli di default (impostati ad esempio in dbtune)
            ISet setArchiveInfo = new SetClass();
            setArchiveInfo.Add(new ArchiveRegistrationInfoClass() { ArchiveTableName = "TreesHistory", FromFieldName = "Da", ToFieldName = "A", OIDFieldName = "OIDHistory", DatasetName = "Trees" });
 
            ////imposto ad esempio un secondo ArchiveRegistrationInfo se è una classe relazionata a Trees (se utilizzo il 3° parametro true nell'abilitazione all'archiving)
            ////setArchiveInfo.Add(new ArchiveRegistrationInfoClass() { ArchiveTableName = "TreesMaintenanceHistory", FromFieldName = "Da", ToFieldName = "A", OIDFieldName = "OIDHistory", DatasetName = "TreesMaintenance" });
            
            ////ISet setArchiveInfo = null;
            
            oc.EnableArchiving(setArchiveInfo);
            
            ////determino il numero di differenze (inserimenti) tra l'historical name DEFAULT e l'historical name di nome 'Test'
            ////IFIDSet fids = FindHistoricalDifferences(workpace, "Test", "Trees", esriDifferenceType.esriDifferenceTypeInsert);
            ////Console.WriteLine("Insert:" + fids.Count().ToString());
 
            ////ESRI License Initializer generated code.
            ////Do not make any call to ArcObjects after ShutDownApplication()
            licenseInitializer.ShutdownApplication();
        } 
    }
 
    public static class Archive
    {
        public enum TypeVersion
        { 
             /// <summary>
             /// Version Transactional
             /// </summary>
             VERSION,
             /// <summary>
             /// Version Historical Name
             /// </summary>
             HISTORICAL_NAME,
             /// <summary>
             /// Version Historical Timestamp
             /// </summary>
             HISTORICAL_TIMESTAMP
        }
 
        public static IWorkspace GetPersonalOrWorkgroupArcSdeWorkspace(string server, string instance, string authenticationMode, string database, TypeVersion typeVersion, string version)
        {
            IPropertySet propertySet = new PropertySetClass();
            propertySet.SetProperty("SERVER", server);
            propertySet.SetProperty("INSTANCE", instance);
            propertySet.SetProperty("DATABASE", database);
            propertySet.SetProperty("AUTHENTICATION_MODE", authenticationMode);
            object o = version;
            if (typeVersion == TypeVersion.HISTORICAL_TIMESTAMP)
            {
                o = Convert.ToDateTime(version);
            }
            propertySet.SetProperty(Enum.GetName(typeof(TypeVersion), typeVersion), o);
 
            Type factoryType = Type.GetTypeFromProgID("esriDataSourcesGDB.SdeWorkspaceFactory");
            IWorkspaceFactory workspaceFactory = (IWorkspaceFactory)Activator.CreateInstance(factoryType);
            return workspaceFactory.Open(propertySet, 0);
        }
 
        public static ITable GetArchiveTable(this IObjectClass objectClass)
        {
            IArchivableClass archivableClass = objectClass as IArchivableClass;
            return archivableClass.Archive;
        }
 
        public static void EnableArchiving(this IObjectClass objectClass, ISet setArchiveInfo)
        {
            IArchivableObject archivableObjecting = objectClass as IArchivableObject;
            if (!archivableObjecting.IsArchiving)
            {
                ////time stamp inizializzato dal sistema (imposto null)
                ////se l'object class ha relationship è possibile impostare l'ultimo parametro a true per automaticamente
                ////abilitare l'archiving sulle object class relazionate
                archivableObjecting.EnableArchiving(setArchiveInfo, nullfalse);
            }
        }
 
        public static IWorkspace ConnectoToVersion(string server, string instance, string user, string password, string database, TypeVersion typeVersion, string version)
        {
            IPropertySet propertySet = new PropertySetClass();
            propertySet.SetProperty("SERVER", server);
            propertySet.SetProperty("INSTANCE", instance);
            propertySet.SetProperty("DATABASE", database);
            propertySet.SetProperty("USER", user);
            propertySet.SetProperty("PASSWORD", password);
            object o = version;
            if (typeVersion == TypeVersion.HISTORICAL_TIMESTAMP)
            {
                o = Convert.ToDateTime(version);
            }
            propertySet.SetProperty(Enum.GetName(typeof(TypeVersion), typeVersion), o);
 
            Type factoryType = Type.GetTypeFromProgID("esriDataSourcesGDB.SdeWorkspaceFactory");
            IWorkspaceFactory workspaceFactory = (IWorkspaceFactory)Activator.CreateInstance(factoryType);
            return workspaceFactory.Open(propertySet, 0);
        }
 
        public static IFIDSet FindHistoricalDifferences(IWorkspace workspace, string historicalMarkerName, string tableName, esriDifferenceType differenceType)
        {
            IHistoricalWorkspace historicalWorkspace = (IHistoricalWorkspace)workspace;
            IHistoricalVersion defaultVersion = historicalWorkspace.FindHistoricalVersionByName(historicalWorkspace.DefaultMarkerName);
            IHistoricalVersion historicalVersion = historicalWorkspace.FindHistoricalVersionByName(historicalMarkerName);
 
            IFeatureWorkspace defaultFWS = (IFeatureWorkspace)defaultVersion;
            IFeatureWorkspace historicalFWS = (IFeatureWorkspace)historicalVersion;
            ITable defaultTable = defaultFWS.OpenTable(tableName);
            ITable historicalTable = historicalFWS.OpenTable(tableName);
 
            IVersionedTable versionedTable = (IVersionedTable)defaultTable;
            IDifferenceCursor differenceCursor = versionedTable.Differences(historicalTable, differenceType, null);
 
            IFIDSet fidSet = new FIDSetClass();
            IRow differenceRow = null;
            int objectID = -1;
 
            differenceCursor.Next(out objectID, out differenceRow);
            while (objectID != -1)
            {
                fidSet.Add(objectID);
                differenceCursor.Next(out objectID, out differenceRow);
            }
 
            fidSet.Reset();
            return fidSet;
        }
 
        public static IList<IPair<stringobject>> GetHistoricalMarkers(IWorkspace workspace)
        {
            IList<IPair<stringobject>> list = new List<IPair<stringobject>>();
            IHistoricalWorkspace historicalWorkspace = (IHistoricalWorkspace)workspace;
            IEnumHistoricalMarker enumHistoricalMarker = historicalWorkspace.HistoricalMarkers;
            enumHistoricalMarker.Reset();
 
            IHistoricalMarker historicalMarker = null;
            while ((historicalMarker = enumHistoricalMarker.Next()) != null)
            {
                list.Add(new Pair<stringobject>() { First = historicalMarker.Name, Second = historicalMarker.TimeStamp }); 
            }
 
            return list;
        }
    }
 
    /// <summary>
    /// classe generica per gestire coppie di oggetti
    /// </summary>
    /// <typeparam name="T">tipo del primo oggetto</typeparam>
    /// <typeparam name="U">tipo del secondo oggetto</typeparam>
    public struct Pair<T, U> : IPair<T, U>
    {
        private T first;
        private U second;
 
        public T First
        {
            get
            {
                return this.first;
            }
 
            set
            {
                this.first = value;
            }
        }
 
        public U Second
        {
            get
            {
                return this.second;
            }
 
            set
            {
                this.second = value;
            }
        }
    }
 
    /// <summary>
    /// interfaccia per coppie di oggetti generici
    /// </summary>
    /// <typeparam name="T">tipo del primo oggetto</typeparam>
    /// <typeparam name="U">tipo del secondo oggetto</typeparam>
    public interface IPair<T, U>
    {
        T First
        {
            get;
            set;
        }
 
        U Second
        {
            get;
            set;
        }
    }
}

 

Una volta che si abilita l’archiving, la classe archiving si presenta con tutti i record dell’object class presenti nella versione di DEFAULT. Come si può notare, la data GDB_FROM_DATE è stata inizializzata nel momento in cui è stato abilitato l’archiving (13/7/2011 alle 20.59.19), mentre il GDB_FROM_TO è lasciato aperto (data impostata ad infinito) .
 
 
Facendo una connessione alla versione historical con una data precedente al 13/7/2011 alle 20.59.19 non vediamo nessuna feature perché prima di questa data non era ancora abilitato l’archiving:


Ma se effettuiamo una connessione indicando una data posteriore al 13/7/2011 alle 20.59.19 vedremo tutte le feature della feature class (nel nostro caso 5 trees).


Da notare che viene creato anche in automatico il riferimento temporale di abilitazione dell’archiving denominato DEFAULT.
Per ogni operazione di archive, il riferimento temporale DEFAULT è aggiornato con il valore dell’operazione di archive. Ciò consente che, quando si sceglie il riferimento temporale DEFAULT e si lavora con una versione historical,  la corrente rappresentazione della classe archive è equivalente alla rappresentazione della classe versionata nella versione DEFAULT di tipo transactional.

Il riferimento temporale di abilitazione dell’archiving può anche essere visto nelle proprietà della feature class:


Ma ora vediamo cosa succede all’archiving class quando si aggiunge, modifica o cancella una feature nella feature class.

Lato programmazione: l’evento che cattura il momento dell’operazione di archive è OnArchiveUpdated dell’interfaccia IVersionEvents2.

Sull’aggiunta di una feature in ArcMap verrà compilato in automatico con la data di sistema il campo GDB_FROM_DATE mentre il campo GDB_TO_DATE verrà lasciato aperto (data impostata ad infinito). I valori nei campi che rappresentano la feature class (OBJECTID, YEAR_PLATED e SHAPE) sono uguali ai corrispondenti della feature class.


Ora modifichiamo la posizione spaziale da ArcMap della feature con OBJECID uguale ad 1.



Come possiamo notare la feature con OBJECTID uguale ad 1 e GDB_ARCHIVE_OID uguale ad 1 viene chiusa con la data della modifica della feature class (13/08/2011 alle 20:41.28) col campo GDB_TO_DATE e viene creato un nuovo record con i dati della feature modificati (in questo caso solo il campo shape mentre OBJECTID e YEAR_PLANTED rimangono invariati) e con il valore della data di modifica nel campo GDB_FROM_DATE mentre GDB_TO_DATE rimane aperto (data impostata ad infinito).

Infine vediamo la cancellazione di una feature con OBJECTID uguale ad 3. In questo caso viene solo compilato il campo GDB_TO_DATE con la data di cancellazione della feature.


Per la gestione della history in ArcMap abbiamo a disposizione la toolbar “Geodatabase History”


Il primo pulsante apre il “Geodatabase History Viewer” che permette di visualizzare i dati selezionando un riferimento temporale precedentemente creato (Historical marker) od uno specifico momento temporale senza modificare la connessione. Comodo è l’Auto Apply che, se abilitato, permette sulla modifica dei riferimenti temporali di visualizzare automaticamente i cambiamenti in mappa senza dover cliccare ulteriori pulsanti di conferma.


Il secondo pulsante consente di caricare in mappa la classe di archiving della feature class selezionata in TOC.



In questo modo l’utente potrà effettuare le proprie interrogazioni, ad esempio utilizzando il Select By Attributes.

Il terzo pulsante consente di creare i riferimenti temporali (Historical Marker). Una volta selezionata la connessione sulla TOC è possibile creare i riferimenti temporali per poi poterli successivamente utilizzare nella visualizzazione temporale dei dati.


E’ possibile disabilitare l’archiving di un object class. In questo caso tutte le modifiche salvate o postate alla versione di DEFAULT non verranno più registrate nella relativa classe di archivio.
Quando si disabilita l’archiving, viene presentato un messaggio che chiede se si vuole mantenere o cancellare la classe archive. Se si sceglie di non eliminare la classe archive associata possiamo preservarla come tabella temporale nel geodatabase.
Consigli sulla disabilitazione dell’archiving
  • L’archiving deve essere disabilitato su una classe prima che possa essere deversionata.
  • Eliminando un feature dataset od una classe con archiving abilitato preserviamo le classi archive.
Dalla versione 10 nel geodatabase replication è possibile utilizzare la classe archive per tenere traccia delle modifiche invece di utilizzare le tabelle delta delle versioni. Questa possibilità è disponibile solo per le repliche one-way. In tal modo i riconcilia, post e compress non sono influenzati rendendo la gestione delle versioni e delle repliche indipendenti. Ciò consente anche che la schedulazione della sincronizzazione sia più flessibile.
Infine illustriamo uno scenario in cui si potrebbe creare un salto temporale nella classe archive:
Un utente effettua modifiche direttamente nella versione di DEFAULT ed elimina una feature in una sessione di editing. Infine, l’utente salva le modifiche.  L’attributo GDB_TO_DATE nella classe archive viene aggiornato con la data di sistema della cancellazione dell’oggetto.
Se lo stesso oggetto è aggiornato da una versione figlia e riconciliata con la versione di DEFAULT, scatta il conflitto.
Se durante il processo di risoluzione del conflitto l’utente sceglie di sostituire il conflitto con la rappresentazione modificata nella versione figlia, la riga verrà sostituita nella versione di DEFAULT quando la versione stessa verrà postata. L’operazione di archive inserisce una nuova riga nella classe archive e imposta l’attributo GDB_FROM_DATE alla data di sistema e l’attributo GDB_TO_DATE ad infinito (12/31/9999).
Pertanto, quando l’utente osserva il lineage dell’oggetto temporalmente, le date conterranno un salto tra il GDB_TO_DATE e il GDB_FROM_DATE per il tempo in cui l’oggetto non esiste nella versione di DEFAULT.




4 commenti:

ernesto sferlazza ha detto...

Che bello...
Finalmente scopro, con ritardo, che già esisteva, per la gestione della dimensione "tempo", una soluzione tecnologicamente più avanzata rispetto a quella, molto più artigianale e di difficile "attecchimento" nella pratica, se non a patto di ricorrere a tools personalizzati, che nel 2004 avevo ipotizzato nell'articolo presentato alla conferenza ASITA [ 1].
Non vedo l'ora di sperimentare gli stessi concetti utilizzando il geodatabase "versioned". Mi rimane, tuttavia, un dubbio: nella pratica gli aggiornamenti vengono effettuati da chi opera sul database in una determinata data, mentre le features che vengono inserite si riferiscono ad entità del mondo reale il cui "ciclo di vita" ha inizio in una data diversa. Risulta possibile inserire o modificare manualmente (e con relativa semplicità) la data che compare nella tabella della versione storica?.
Mi scuso in anticipo per eventuali inesattezze terminologiche o banalità nelle affermazioni, ma mi sto approcciando solo da poco - e non senza difficoltà di apprendimento - al geodatabase Enterprise gestito attraverso ArcSDE (che nella versione che ho installato è abbinato al DBMS PostgreSQL).
Cordiali saluti
Ernesto Sferlazza
[1] http://www.provincia.agrigento.it/flex/cm/pages/ServeAttachment.php/L/IT/D/D.1ca591e5be2fe39a9071/P/BLOB%3AID%3D26

Ing. Domenico Ciavarella ha detto...

Dall'help di ESRI:

"...It is important to understand how ArcGIS represents time when change is recorded. History can be recorded as either valid time or transaction time. Valid time is the actual moment for which a change occurred in the real world and is typically recorded by the user who is applying the change. Transaction time is the time an event was recorded in the database. Transaction times are generated automatically by the system.

ArcGIS uses transaction time, which is based on the current server time, to record change to the data when changes are saved or posted to the DEFAULT version. Transaction time and the time that the event occurred in the real world are rarely the same time. Time will lapse between an event happening in the real world and when the event is recorded in the database. For example, a parcel is sold on May 14, 2006; however, the change is not recorded to the data until June 5, 2006. The transaction time of June 5, 2006, is recorded in the archive class for this change.

When the edit occurs, ArcGIS will archive the transaction to the archive class. The difference between the time of the real-world event and the transaction time may seem insignificant, but it becomes more apparent when queries are performed against the archived information. Backlogs in editing and updating data are not uncommon in production systems, which results in the time difference and lag between valid and transaction time.

The difference between valid and transaction time is also an issue in situations where history is recorded in a multiuser environment with many different users or departments editing the database. The sequence in which changes are performed and logged in the database may not be the same order in which those changes occurred in the real world..."

ernesto sferlazza ha detto...

"Well, I had already read the help, where the issue regarding difference between historical and transactional time is highlighted, but I haven't found in the manual a solution" (scrivo anch'io in inglese, così se dico sciocchezze se ne accorge meno gente). Quindi, scherzi a parte, se ho inteso bene, al momento ci si deve accontentare delle versioni tipo "transactional", senza possibilità di modificare le date?

Ing. Domenico Ciavarella ha detto...

sì soprattutto per il fatto che può essere utilizzato nella gestione di sincronizzazione one- way svincolandosi dal workflow versioning.