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

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

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



Visualizzazione post con etichetta ArcObjects. Mostra tutti i post
Visualizzazione post con etichetta ArcObjects. Mostra tutti i post

domenica 29 aprile 2012

Json & ArcObjects

Con ArcGIS 10 sono state introdotte nuove classi che permettono di serializzare e deserializzare in JSON.
Stiamo parlando delle classi JSONSerializerGdb e JSONDeserializerGdb  che permettono di serializzare e deserializzare oggetti di alto livello in classi JSON e da JSON.

Ad esempio, se dovessimo deserializzare una geometria JSON ESRI (vedi GeoService REST Specification ) in ArcObjects, possiamo scrivere:


string jsonGeometryPoint = "{\"x\" : -118.15, \"y\" : 33.80, \"spatialReference\" : {\"wkid\" : 4326}}";
            IJSONReader jsonReader = new JSONReaderClass();
            jsonReader.ReadFromString(jsonGeometryPoint);
            IJSONDeserializer jsonDeserializer = new JSONDeserializerGdbClass();
            jsonDeserializer.InitDeserializer(jsonReader, null);
            IGeometry geometry = ((IExternalDeserializerGdb)jsonDeserializer).ReadGeometry(esriGeometryType.esriGeometryPoint);
            IPoint point = (IPoint)geometry;
            Console.Write("X:{0} - Y:{1} - Factory Code: {2}", point.X, point.Y, point.SpatialReference.FactoryCode);
            Console.Read();


Se dovessimo deserializzare uno spatial Reference JSON ESRI in ArcObjects, possiamo operare in modo analogo:


string jsonSpatialReference = "{\"wkid\" : 4326}";
            IJSONReader jsonReader = new JSONReaderClass();
            jsonReader.ReadFromString(jsonSpatialReference);
            IJSONDeserializer jsonDeserializer = new JSONDeserializerGdbClass();
            jsonDeserializer.InitDeserializer(jsonReader, null);
            ISpatialReference spatialReference = ((IExternalDeserializerGdb)jsonDeserializer).ReadSpatialReference();
            Console.Write("Name:{0} - FactoryCode:{1}", spatialReference.Name, spatialReference.FactoryCode);
            Console.Read();


Ora vediamo i casi inversi, ovverosia serializzare in classi JSON geometrie e spatialReference ArcObjects:


            ////ArcObjects Geometry to json geometry ESRI
            IPoint point = new PointClass();
            point.PutCoords(-118.15, 33.80);
            IJSONWriter jsonWriter = new JSONWriterClass();
            jsonWriter.WriteToString();
            IJSONSerializer jsonSerializer = new JSONSerializerGdbClass();
            jsonSerializer.InitSerializer(jsonWriter, null);
            ((IExternalSerializerGdb)jsonSerializer).WriteGeometry(null, point);
            Console.Write(Encoding.UTF8.GetString(jsonWriter.GetStringBuffer()));
            Console.Read();
 
 
            ////ArcObjects spatialReference to json spatialReference ESRI
            Type factoryType = Type.GetTypeFromProgID("esriGeometry.SpatialReferenceEnvironment");
            object obj = Activator.CreateInstance(factoryType);
            ISpatialReferenceFactory spatialReferenceFactory = obj as ISpatialReferenceFactory;
            IProjectedCoordinateSystem spatialReference = spatialReferenceFactory.CreateProjectedCoordinateSystem((int)esriSRProjCSType.esriSRProjCS_World_Mercator);
            IJSONWriter jsonWriter = new JSONWriterClass();
            jsonWriter.WriteToString();
            IJSONSerializer jsonSerializer = new JSONSerializerGdbClass();
            jsonSerializer.InitSerializer(jsonWriter, null);
            ((IExternalSerializerGdb)jsonSerializer).WriteSpatialReference(null, spatialReference);
            Console.Write(Encoding.UTF8.GetString(jsonWriter.GetStringBuffer()));
            Console.Read();

oppure
IJSONConverterGeometry jsonConvert = new JSONConverterGeometryClass();
            IJSONObject spatRefObj = new JSONObjectClass();
            jsonConvert.QueryJSONSpatialReference(spatialReference, spatRefObj);
            JsonObject spatRefJson = new JsonObject(spatRefObj.ToJSONString(null));

Per convertire una feature class possiamo impostare un Recordset e, nel caso, filtrarlo sia come campi che come record tramite un QueryFilter (2° argomento del metodo SetSourceTable di RecordSet)
            //convert feature class in json esri
            Type factoryType = Type.GetTypeFromProgID("esriDataSourcesGDB.FileGDBWorkspaceFactory");
            IWorkspaceFactory workspaceFactory = (IWorkspaceFactory)Activator.CreateInstance(factoryType);
            IWorkspace workspace = workspaceFactory.OpenFromFile(@"E:\Temp\Test2.gdb", 0);
            IFeatureWorkspace featureWorkspace = (IFeatureWorkspace)workspace;
            IFeatureClass featureClass = featureWorkspace.OpenFeatureClass("streets");
 
            IRecordSet recordset = new RecordSet();
            IRecordSetInit recordSetInit = recordset as IRecordSetInit;
            recordSetInit.SetSourceTable(featureClass as ITable, null);
 
            IGeometryResultOptions geometryResultOptions = new GeometryResultOptionsClass();
            geometryResultOptions.GeneralizeGeometries = true;
            geometryResultOptions.DensifyGeometries = true;
            IPropertySet propertySet = new PropertySetClass();
            propertySet.SetProperty("GeometryResultOptions", geometryResultOptions);
            IJSONWriter jsonWriter = new JSONWriterClass();
            jsonWriter.WriteToString();
            IJSONSerializer jsonSerializer = new JSONSerializerGdbClass();
            jsonSerializer.InitSerializer(jsonWriter, null);
            jsonSerializer.WriteObject(recordset, propertySet);
            Console.Write(Encoding.UTF8.GetString(jsonWriter.GetStringBuffer()));
            Console.Read();


Da json features a feature class:

            string jsonFeatures = "{\"displayFieldName\":\"\",\"fieldAliases\":{\"OBJECTID\":\"OBJECTID\",\"Indirizzo\":\"Indirizzo\"},\"geometryType\":\"esriGeometryPoint\",\"spatialReference\":{\"wkid\":102100},\"fields\":[{\"name\":\"OBJECTID\",\"type\":\"esriFieldTypeOID\",\"alias\":\"OBJECTID\"},{\"name\":\"Indirizzo\",\"type\":\"esriFieldTypeString\",\"alias\":\"Indirizzo\",\"length\":50}],\"features\":[" +
            "{" +
             "\"geometry\": {" +
                "\"x\": 940411.3699657875," +
                "\"y\": 5643498.120243863," +
                "\"spatialReference\": {\"wkid\": 102100}" +
              "}," +
              "\"attributes\": {" +
                "\"OBJECTID\": 1," +
                "\"Indirizzo\": \"VIA SORDI, 1\"" +
              "}" +
            "}]}";
 
            IJSONReader jsonReader = new JSONReaderClass();
            jsonReader.ReadFromString(jsonFeatures);
 
            IJSONConverterGdb JSONConverterGdb = new JSONConverterGdbClass();
            IPropertySet originalToNewFieldMap;
            IRecordSet recorset;
            JSONConverterGdb.ReadRecordSet(jsonReader, null, null, out recorset, out originalToNewFieldMap);
 
            Type factoryType = Type.GetTypeFromProgID("esriDataSourcesGDB.FileGDBWorkspaceFactory");
            IWorkspaceFactory workspaceFactory = (IWorkspaceFactory)Activator.CreateInstance(factoryType);
            IWorkspace workspace = workspaceFactory.OpenFromFile(@"C:\Temp\testsd\sd.gdb", 0);
            IRecordSet2 recordSet2 = recorset as IRecordSet2;
            recordSet2.SaveAsTable(workspace, "Test");
 
Ricordarsi di utilizzare l'Activator per oggetti singleton (SpatialReferenceEnvironmental e FileGDBWorkspaceFactory).

Gli shortcut a questi metodi li potete trovare anche nell'assembly ESRI.ArcGIS.SOESupport nella classe Conversion con i metodi ToGeometry, ToJson e ToSpatialReference. Sempre in questo assembly potete utilizzare i metodi della classe JsonObject per serializzare e deserializzare:

Esempio di deserializzazione
            JsonObject jsonPoint;
            if (!operationInput.TryGetJsonObject("location", out jsonPoint))
                throw new ArgumentNullException("location");
            IPoint location = Conversion.ToGeometry(jsonPoint,
                                                    esriGeometryType.esriGeometryPoint) as
                                                    IPoint;

Esempio di serializzazione
List<JsonObject> jsonGeometries = new List<JsonObject>();
            JsonObject jsonResultsGeometry = Conversion.ToJsonObject(resultsGeometry);
            jsonGeometries.Add(jsonResultsGeometry);
            JsonObject resultJsonObject = new JsonObject();
            resultJsonObject.AddArray("geometries", jsonGeometries.ToArray());
            byte[] result = Encoding.UTF8.GetBytes(resultJsonObject.ToJson());
            return result;


Con il MapServer possiamo utilizzare i metodi dell'interfaccia IMapServer3 che ci consentono agevolmente di avere dei risultati in JSON (mediante l'interfaccia IQueryResult) o Recordset da convertire in JSON da query che filtrano e riproiettano anche con datum differente (vedi IQueryResultsOptions)
IQueryResultOptions queryResultOptions = new QueryResultOptions();
            queryResultOptions.Format = esriQueryResultFormat.esriQueryResultRecordSetAsObject;
            IMapServer3 mapServer = serverObjectHelper.ServerObject as IMapServer3;
            IRecordSet rs = MapServer.QueryFeatureData2(mapServer.DefaultMapName, aLayerDescription, aspatialFilterOut, queryResultOptions).Object as IRecordSet;
            return Conversion.ToJson(rs);


IQueryResultOptions resultOptions = new QueryResultOptionsClass();
            resultOptions.Format = esriQueryResultFormat.esriQueryResultJsonAsMime;
            IMapTableDescription tableDesc = GetTableDesc(mapServer, layerID);
            IQueryResult result = mapServer.QueryData(mapServer.DefaultMapName, tableDesc, filter, resultOptions);
            return result.MimeData;

domenica 4 luglio 2010

Rilascio delle risorse in AO

Una delle maggiori difficoltà utilizzando gli ArcObjects in ambiente .NET è capire come gestire il ciclo di vita di un oggetto.
Come ben sappiamo, il framework .NET fornisce un ambiente dove gli oggetti vengono rilasciati quando non sono più utilizzati. Questa gestione automatica della memoria risolve i due errori più comuni delle applicazioni, ossia le perdite di memoria e i riferimenti a memoria non validi.
Questo comunque non ci esime dal prestare particolare attenzione quando abbiamo a che fare con risorse.
Gli ArcObjects sono oggetti COM (codice non gestito) che vengono 'consumati' in ambiente .NET (codice gestito). In sintesi il runtime .NET fornisce delle classi wrapper che fanno da 'ponte' tra il codice gestito (.NET) ed il codice non gestito (COM). Quando si fa riferimento a un oggetto COM, il runtime crea un Runtime Callable Wrapper (RCW) che gestisce il marshalling tra i due ambienti. In modo simile il runtime .NET crea un COM-callable wrappers (CCWs) per la comunicazione da COM a .NET.

Per generare l'RCW viene utilizzato tlbimp.exe (Type Library Importer utility) contenuto nell'SDK di .NET.
Quando facciamo riferimento ad un componente COM in Visual Studio il tlbimp.exe viene eseguito in background. Per identificare un RCW possiamo vedere in debug gli oggetti visualizzati come System.__ComObject.
Con la Type Library Importer utility le COM coClass sono convertite in classi gestite. Il nome delle classi gestite è uguale a quello di quelle non gestite con il suffisso Class. Per esempio l'RCW della classe Envelope è EnvelopeClass.

Nell'ambiente COM, la gestione della durata avviene tramite il conteggio dei riferimenti. Ogni oggetto COM dispone di un conteggio interno dei riferimenti client che lo indicano. Questi conteggi sono aumentati e diminuiti dallo sviluppatore client richiamando IUnknown::AddRef e IUnknown::Release. AddRef incrementa il conteggio e deve essere richiamato quando viene impostato un riferimento al componente, mentre Release diminuisce il conteggio e deve essere richiamato quando il riferimento viene eliminato. Se il conteggio scende a 0, l'oggetto può essere eliminato. (vedi Reference counting )
In ambiente .NET il livello utilizzato è quello di classe e l'impatto sulle prestazioni sarebbe troppo grande se il conteggio dei riferimenti fosse stato aggiunto al CLR. (vedi Garbage Collection )
In ambiente .NET l'oggetto dura finché il Garbage Collector vede che non è più raggiungibile da un riferimento principale forte tramite un percorso di riferimenti forti.

Ritorniamo alla nostra class RCW: AddRef viene chiamato dopo la creazione del componente, mentre Release viene richiamato quando l'RCW è finalizzato.

Il problema è che non sappiamo quando la finalizzazione avviene in ambiente .NET.
Il problema principale è che, se il nostro oggetto trattiene risorse, potrebbe bloccarle e quindi non renderle disponibili.

Gli ambienti .NET ed ESRI mettono a disposizione classi e metodi per una finalizzazione deterministica.
Occorre però prestare molta attenzione a quando utilizzare o meno queste classi/metodi.
Innanzitutto in ambiente .NET abbiamo a disposizione l'interfaccia IDisposable che ci permette, implementandola nella nostra classe, di rilasciare risorse gestite e non. Occorrerà seguire un determinato pattern di sviluppo poichè, se non si dovesse chiamare il metodo Dispose esplicitamente, il Garbage Collector da solo finalizzerà l'oggetto non conoscendo l'interfaccia IDisposable.

Implementando l'interfaccia IDisposable, sarà possibile utilizzare lo using che ne garantirà un utilizzo corretto.

ESRI mette a disposizione la classe ComReleaser che implementa IDisposable

Come visto nel post sul ComReleaser possiamo utilizzare questa classe per finalizzare le risorse in un determinato blocco. In sostanza ESRI ha creato un helper nel quale indichiamo quali sono gli oggetti che desideriamo rilasciare in modo deterministico.

In .NET abbiamo a disposizione il ReleaseComObject e il FinalReleaseComObject.
Il ReleaseComObject decrementa il conteggio dell' RCW associato all'oggetto COM e restituisce il valore del conteggio mentre il FinalReleaseComObject rilascia tutti i riferimenti portando il conteggio a 0, se il rilascio ha avuto successo. Occorre tener presente che l'RCW potrebbe ancora esistere perchè attende il garbage collector.

Occorre prestare attenzione a non utilizzare un COM dopo che è stato separato dal suo RCW, altrimenti si incorre in un eccezione. In questo esempio rilasciamo la FeatureClass table1 e poi successivamente la richiamiamo.

m_AOLicenseInitializer.InitializeApplication(new esriLicenseProductCode[] { esriLicenseProductCode.esriLicenseProductCodeEngine },new esriLicenseExtensionCode[] { });

            try
            {

                IPropertySet propertySet = new PropertySetClass();
                propertySet.SetProperty("DATABASE", @"D:\Temp\test.mdb");
                Type factoryType = Type.GetTypeFromProgID("esriDataSourcesGDB.AccessWorkspaceFactory");
                IWorkspaceFactory workspaceFactory = (IWorkspaceFactory) Activator.CreateInstance(factoryType);
                IWorkspace workspace = workspaceFactory.Open(propertySet, 0);
                IFeatureWorkspace featureWorkspace = workspace as IFeatureWorkspace;
                IFeatureClass table1 = featureWorkspace.OpenFeatureClass("streets");

                int k = Marshal.ReleaseComObject(table1);

                Console.Write(table1.HasOID);


                Console.Read();

            }
            catch (Exception ex)
            {
                Console.Write(ex.Message);
                Console.Read();
            }


            m_AOLicenseInitializer.ShutdownApplication();




In .NET è presente il metodo Collect che forza il garbage collector. Questo metodo è da utilizzare esclusivamente solo quando necessario (sono veramente pochi i casi). Nel caso foste interessati, vi consiglio di dare un occhio a questo post per verificare quando utilizzarlo. Nella stessa classe (GC) possiamo notare anche i metodi SuppressFinalize (da utilizzare quando ad esempio implementiamo l'IDisposable e, poichè gestiamo le risorse, desideramo evitare che il garbage collector richiami la finalizzazione sull'oggetto). WaitForPendingFinalizers è un altro metodo che permette di rimanere in attesa sul thread sul quale lo si chiama fino a quando il thread che esegue la finalizzazione degli oggetti disponibili alla finalizzazione ha terminato.


In questo esempio vediamo come i datasets sono pooled nel loro workspace

IPropertySet propertySet = new PropertySetClass();
                propertySet.SetProperty("DATABASE", @"D:\Temp\test.mdb");
                Type factoryType = Type.GetTypeFromProgID("esriDataSourcesGDB.AccessWorkspaceFactory");
                IWorkspaceFactory workspaceFactory = (IWorkspaceFactory) Activator.CreateInstance(factoryType);
                IWorkspace workspace = workspaceFactory.Open(propertySet, 0);
                IFeatureWorkspace featureWorkspace = workspace as IFeatureWorkspace;

                using (ComReleaser comReleaser = new ComReleaser())
                {
                    ITable table1 = featureWorkspace.OpenTable("zTest");
                    ITable table2 = featureWorkspace.OpenTable("zTest");


                    if (object.ReferenceEquals(table1, table2))
                       Console.WriteLine("Stesso oggetto");

                    Console.WriteLine(Marshal.FinalReleaseComObject(table1));

                    Console.WriteLine(Marshal.ReleaseComObject(table2));
                }

table1 e table2 puntano al riferimento della stessa istanza e, se eseguiamo un FinalReleaseComObject, verranno azzerati tutti riferimenti (vedi il ReleaseComObject successivo che restituisce -1) pertanto il table2 non è utilizzabile
altrimenti si incorre nell'eccezione vista precedentemente.

Come abbiamo detto nel post sul ComReleaser è importante rilasciare i cursori per evitare di lasciare in blocco la tabella alla quale il cursore fa riferimento.

                using (ComReleaser comReleaser = new ComReleaser())
                {
                    ICursor cursor = table1.Search(null, true);
                    comReleaser.ManageLifetime(cursor);
                    IRow row = null;
                    bool isManaged = false;
                    while ((row = cursor.NextRow()) != null)
                    {
                        if (!isManaged)
                        {
                            comReleaser.ManageLifetime(row);
                            isManaged = true;
                        }

                        // utilizza la row...
                    }
                }

In questo esempio abbiamo un cursore che viene riciclato (parametro true nel Search) e quindi non viene creata una nuova istanza di row ad ogni riga successiva. Pertanto occorre aggiungere la row una sola volta.
Diversamente, se dovessimo utilizzare un cursore non riciclato (parametro false nel Search) occorrerà ad ogni riga successiva gestire il rilascio poichè la row è una nuova istanza.

                using (ComReleaser comReleaser = new ComReleaser())
                {
                    ICursor cursor = table1.Search(null, false);
                    comReleaser.ManageLifetime(cursor);
                    IRow row = null;
                    while ((row = cursor.NextRow()) != null)
                    {
                        try
                        {
                            // utilizza la row...
                        }
                        catch (Exception ex)
                        {
                            // gestisci l'eccezione...
                        }
                        finally
                        {
                            Marshal.ReleaseComObject(row);
                        }
                    }
                }

Pertanto occorre sempre capire quando siamo in presenza di una stessa istanza.
Ad esempio, se ci dovessimo connettere ad ArcSDE in due modi diversi: prima con la connection properties e poi con la connection file con stessa user/password e versione, avremo un singolo COM che sarà condiviso in un singolo RCW.  Nel dubbio verificate con un object.ReferenceEquals(obj1,obj2).

domenica 13 giugno 2010

Singletons e System.__COMObject

Quando un ArcObject (basato su COM) in ambiente .NET viene istanziato via interop con la parola riservata 'new', viene preso un riferimento all'oggetto stesso tramite un wrapper .NET Framework fortemente tipizzato (runtime callable wrapper (RCW)). Un RCW è un oggetto gestito che può trattenere un oggetto COM in una applicazione .NET.

Vediamo un esempio:

IEnvelope env = new EnvelopeClass();


In questo caso, in fase di compilazione, il compilatore conosce il tipo esatto della variabile tramite Reflection e metadati ed a runtime utilizza questa informazione per sapere il tipo esatto della variabile.

Vediamo un altro esempio:

            IEnvelope a = axMapControl1.Extent;

            try
            {
                EnvelopeClass b = (EnvelopeClass)a;
            }
            catch (Exception ex)
            {
                System.Windows.Forms.MessageBox.Show(ex.Message);
            }


In questo caso, quando il codice è compilato, il compilatore sa solo che la proprietà Extent restituisce un oggetto che implementa l'interfaccia IEnvelope ma non può sapere il tipo della classe dell'oggetto. Sebbene possiamo sempre 'castare' ad interfacce che implementano Envelope, il runtime .NET 'wrappa' il riferimento all'oggetto COM tramite un RCW generico chiamato System.__ComObject.
Riprendendo l'esempio, se proviamo a 'castare' la variabile a in EnvelopeClass otterremmo un errore perchè non possiamo 'castare' da un System.__ComObject ad una classe.



Possiamo, come abbiato detto, castare mediante le interfacce che sono implementate dall'oggetto ma, come potete vedere, l'oggetto è un System.__ComObject.

Invece nel primo esempio che abbiamo visto l'rcw è fortemente tipizzato:




Vediamo ora cosa succede con gli oggetti Singleton.
Gli oggetti singleton supportano una sola istanza dell'oggetto (vedi post).
Dalla ArcGIS 9.x, gli oggetti singleton sono singletons per thread e non per processo (vedi post).

Anche qui vale quello che abbiamo detto precedentemente solo che, se dovessimo istanziare un nuovo oggetto, prenderemmo come riferimento l'istanza corrente.
Per esempio:
IWorkspaceFactory workspaceFactory = new AccessWorkspaceFactoryClass();

            IWorkspaceFactory workspaceFactory1 = new AccessWorkspaceFactoryClass();

            if (object.ReferenceEquals(workspaceFactory, workspaceFactory1))
                System.Windows.Forms.MessageBox.Show("workspaceFactory and workspaceFactory1: same reference");


            SpatialReferenceEnvironment spatialReferenceEnvironment = new SpatialReferenceEnvironmentClass();
            SpatialReferenceEnvironment spatialReferenceEnvironment1 = new SpatialReferenceEnvironmentClass();

            if (object.ReferenceEquals(spatialReferenceEnvironment, spatialReferenceEnvironment1))
                System.Windows.Forms.MessageBox.Show("spatialReferenceEnvironment and spatialReferenceEnvironment1: same reference");

AccessWorkspaceFactoryClass e SpatialReferenceEnvironment sono singleton (vedi qui l'elenco degli oggetti singleton negli arcobjects).  In questo caso, dopo aver istanziato la classe, se la dovessimo reinstanziare, otterremmo lo stesso riferimento (verifica con il metodo ReferenceEquals). Non si può dire lo stesso di una classe non singleton. Env e env1 non rappresentano la stessa istanza:

IEnvelope env = new EnvelopeClass();
            IEnvelope env1 = new EnvelopeClass();
            if (object.ReferenceEquals(env, env1))
                System.Windows.Forms.MessageBox.Show("env and env1: same reference");
            else
                System.Windows.Forms.MessageBox.Show("env and env1: Not the same reference");

Con gli oggetti singleton, a differenza di quelli non singleton, potremmo incorrere nell'errore precentemente visto ("Impossibile eseguire il cast...") anche con un semplice new

IWorkspaceFactory workspaceFactory = new AccessWorkspaceFactoryClass();


Questo perchè è possibile che sia presente già la singola istanza come RCW non fortemente tipizzato e quindi il cast fallisce anche se si fa riferimento con un'interfaccia perchè il new tenta di 'castare' da RCW non tipizzato a RCW tipizzato e l'operazione fallisce. Ad esempio tools in ArcObjects scritti .NET da terze parti potrebbero 'wrappare' un oggetto in un generico wrapper causando l'errore.

In questo esempio non fallisce perchè supponiamo di trovarci nel caso in cui non siamo in presenza di una istanza già presente (AccessWorkspaceFactoryClass) e non tipizzata ( System.__ComObject ) e quindi il cast viene eseguito:

IWorkspaceFactory workspaceFactory = new AccessWorkspaceFactoryClass();
IWorkspaceFactory workspaceFactory1 = new AccessWorkspaceFactoryClass();


Per evitare comunque il possibile errore precedentemente descritto è buona regola utilizzare la classe Activator per assicurare che l'oggetto singleton sia wrappato con un rcw fortemente tipizzato quando fai riferimento ad esso per la prima volta. Usando il metodo CreateInstance della class Activator invece di usare il new, l'Activator è abilitato a prendere le informazioni (metadato) per eseguire il cast.

Type factoryType = Type.GetTypeFromProgID(
                  "esriDataSourcesGDB.AccessWorkspaceFactory");
            IWorkspaceFactory workspaceFactory = (IWorkspaceFactory)
              Activator.CreateInstance(factoryType);

mercoledì 21 aprile 2010

Lambda con Standard Query Operators, Extension method e Type Inference in Lambdas

Una lambda expression è una funzione anonima che può contenere espressioni e blocchi di codice, e può essere usata per creare delegati o tipi di espressione ad albero. 

Le Lambda sono utilizzate nelle LINQ queries method-based come argomenti per standand query in metodi come Where


Quando usi una sintassi method-based per chiamare il metodo Where in una classe Enumerable il parametro è un delegato di tipo Func(T, TResult)
Una lambda expression è il modo più adatto per creare quel delegato.

La forma di una lambda expression è:

(input parameters) => expression

e restituisce il risultato dell'espressione.
Le parentesi sono sempre obbigatorie a meno di avere un solo parametro:

Esempio con due parametri con dichiarati i tipi:
(int x, string s) => s.Length > x
Esempio con due parametri:
(x, y) => x == y


Esempio senza parametri: () => SomeMethod()


Lambda con blocco di codice
(input parameters) => {statement;}


Esempio Lambda con blocco di codice
n => { string s = n + " " + "World"; Console.WriteLine(s); };



Lambda con Standard Query Operators

Molte Standard query operators hanno come parametro di input un delegato generico della famiglia Func(T, TResult). Questi delegati usano i tipi dei parametri per definire il numero ed il tipo di parametri, e restituire il tipo di delegato.



Il delegato può essere utilizzato come Func<int,bool> myFunc dove int è un parametro di input e bool è il valore restituito. Il valore restituito è sempre specificato nel tipo dell'ultimo parametro.
Func<int,string,bool> definisce un delegato con 2 parametri di input, int e string e restituisce un tipo bool.
Il seguente delegato Func, quando è invocato, restituirà true o false per indicare se il parametro di input è 5:

Func myFunc = x => x == 5;

bool result = myFunc(4); // qui chiaramente restituisce false
 
Type Inference in Lambda


Quando scrivi lambda, spesso non devi specificare il tipo per i parametri di input perchè il compilatore può capire il tipo in base al corpo della lambda, al sottostante tipo di delegato e ad altri fattori come descritto nelle specifiche del linguaggio C# 3.0.

Per la maggior parte delle standard query operators, il primo input è il tipo degli elementi nella sequenza della sorgente. Così se stai interrogando un IEnumerable<IFeature>, successivamente la variable di input sarà vista come un oggetto IFeature che significa che hai accesso ai suoi metodi e proprietà:
In questo esempio k è un tipo IEnumerable<IFeature>

decimal t = k.Where(i => (i.get_Value(idxName).ToString() == "Amsterdam")).Sum(i => Convert.ToDecimal(i.get_Value(idxShapeLength)));


Mettiamo insieme tutte le nozioni ed applichiamole ad un esempio utilizzando anche gli arcobjects:


public static class ExtensionsMethod
    {
        public static IEnumerable<T> AsEnumerable<U,T>(this U cursor, Func<T> next) where T:class
        {
            T current = next();
            while (current != default(T))
            {
                yield return current;
                current = next();
            }
        }



        public static IField get_Field(this IFields fields, string FieldName)
        {

            int i = fields.FindField(FieldName);

            return (i == -1) ? null : fields.get_Field(i);

        }

}


IPropertySet propertySet = new PropertySetClass();
            propertySet.SetProperty("DATABASE", @"C:\Program Files\ArcGIS\DeveloperKit\SamplesNET\data\Atlanta.gdb");
            Type factoryType = Type.GetTypeFromProgID(
              "esriDataSourcesGDB.FileGDBWorkspaceFactory");
            IWorkspaceFactory2 workspaceFactory = (IWorkspaceFactory2)Activator.CreateInstance(factoryType);

                    IFeatureWorkspace featureWorkspace = (IFeatureWorkspace)workspaceFactory.Open(propertySet, 0);

                    IFeatureClass featureClass = featureWorkspace.OpenFeatureClass("streets");



                    using (ComReleaser comReleaser = new ComReleaser())
                    {


                        IFeatureCursor featureCursor = featureClass.Search(null,true);
                        comReleaser.ManageLifetime(featureCursor);
                        IFields fields = featureClass.Fields;
                        int idxName = fields.FindField("NAME");
                        int idxType = fields.FindField("TYPE");
                        int idxShapeLength = fields.FindField(featureClass.LengthField.Name);

                        IEnumerable<IFeature> k = featureCursor.AsEnumerable(() => featureCursor.NextFeature());

                        decimal t = k.Where(i => (i.get_Value(idxName).ToString() == "Amsterdam")).Sum(i => Convert.ToDecimal(i.get_Value(idxShapeLength)));

                        Console.Write("La lunghezza della via {0} è {1}", "Amsterdam", t);

                    }

sabato 6 marzo 2010

Sample SOE to REST

In questo video vengono illustrate alcune funzionalità sviluppate lato desktop ed esposte anche lato server senza dover modificare il codice. In sintesi sono state create delle dll condivise (parte core: funzionalità generazione scheda cameretta, profili, goccia), dll UI lato desktop e dll SOE lato server esposte via REST per facilitare l'utilizzo con la maggior parte dei client.

lunedì 23 novembre 2009

Geoprocessing message & log4net in arcgis desktop

Quando eseguiamo un processo di Geoprocessing attraverso gli ArcObjects, possiamo a runtime ricevere i messaggi che il processo stesso genera. Per poter 'catturare' i messaggi possiamo creare una classe che implementa l'interfaccia IGeoProcessorEvents.

public class GPMessageEventHandler : IGeoProcessorEvents
{

    private MessageDetails messageDetails = null;


    public GPMessageEventHandler(MessageDetails messageDetails)
    {
        this.messageDetails = messageDetails;
    }


    public void OnMessageAdded(IGPMessage message)
    {
        this.messageDetails(message);
    }


    public void PostToolExecute(IGPTool tool, IArray values, int result, IGPMessages messages)
    {
    }


    public void PreToolExecute(IGPTool tool, IArray values, int processID)
    {
    }


    public void ToolboxChange()
    {
    }
}

Prima di eseguire il geoprocesso registriamo l'istanza della classe che riceve gli eventi mediante RegisterGeoProcessorEvents.


private static readonly log4net.ILog log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
    private static readonly bool isDebugEnabled = log.IsDebugEnabled;

    public static void RunTool(Geoprocessor geoprocessor, IGPProcess process, ESRI.ArcGIS.esriSystem.ITrackCancel trackCancel, MessageDetails messageDetails)
    {
        try
        {
            // Set the overwrite output option to true
            geoprocessor.OverwriteOutput = true;

            IGeoProcessorEvents geoProcessorEvents = new GPMessageEventHandler(messageDetails);
            if (isDebugEnabled)
            {
                log.Debug("RegisterGeoProcessorEvents");
            }
            geoprocessor.RegisterGeoProcessorEvents(geoProcessorEvents);
            if (isDebugEnabled)
            {
                log.Debug("Execute the tool");
            }
            geoprocessor.Execute(process, trackCancel);

            if (isDebugEnabled)
            {
                log.Debug("UnRegisterGeoProcessorEvents");
            }
            geoprocessor.UnRegisterGeoProcessorEvents(geoProcessorEvents);
        }
        catch(Exception ex)
        {
            ExceptionHandler.ThrowException(ex, typeof(Helper));
        }
    }


Nel costruttore della classe, che riceve gli eventi, passiamo il delegato che si occupa di visualizzare i messaggi ( messageDetails ).

Nel nostro caso il delegato è dichiarato nel seguente modo:

public delegate void MessageDetails(IGPMessage data);

e nella windows form, il metodo di tipo MessageDetails, che visualizza i messaggi è:

public void WriteDetails(IGPMessage message)
    {
        // Font font = new Font("Tahoma", 8, FontStyle.Regular);
        // richTextBox1.SelectionFont = font;
        Color color = Color.Black;
        if (message.IsError())
        {
            color = Color.Red;
        }
        else if (message.IsWarning())
        {
            color = Color.Green;
        }
        else if (message.IsAbort())
        {
            color = Color.DarkMagenta;
        }

        rtbDetails.SelectionColor = color;
        rtbDetails.SelectedText = message.Description;
        rtbDetails.SelectionStart = rtbDetails.Text.Length;
        rtbDetails.ScrollToCaret();
        rtbDetails.AppendText(System.Environment.NewLine);
    }

Poichè il controllo di output è di tipo RichTextBox, possiamo formattarlo in funzione del tipo di messaggio (come fa il GeoProcessing degli ArcToolBox).

Volevo aprire una parentesi riguardo alla possibilità di utilizzare log4net come logger per la dll.

Impostiamo il file di appender a runtime:



XmlConfigurator.Configure(new System.IO.FileInfo(string.Format("{0}\\logging.config", codeBase)));
        IAppender appender = log4net.LogManager.GetRepository().GetAppenders()[0];

        FileAppender fileAppender = appender as FileAppender;
        fileAppender.File = Path.Combine(codeBase, "GPExtensionLog.txt");
        fileAppender.ActivateOptions();


e  utilizziamo una classe di handler per le eccezioni:

public static class ExceptionHandler
{
    private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

    public static void ThrowException(Exception e, Type source, string message)
    {
        log.Error("Exception: " + message);
        log.Error("{" + source + "} " + e.Message + ", StackTrace: " + e.StackTrace);
        throw e;
    }

    public static void ThrowException(Exception e, Type source)
    {
        log.Error("{" + source + "} " + e.Message + ", StackTrace: " + e.StackTrace);
        throw e;
    }
}


per poi utilizzarla:

   geoprocessor.UnRegisterGeoProcessorEvents(geoProcessorEvents);
        }
        catch(Exception ex)
        {
            ExceptionHandler.ThrowException(ex, typeof(Helper));
        }



Scarica qui la soluzione

sabato 3 ottobre 2009

Store IGeometry in SQL server 2008

Problema: supponiamo di avere un web service che crea IGeometry utilizzando ArcGIS Server e di voler esporre il dato geometrico per SQL Server 2008 (Express).


Soluzione: per ottimizzare il passaggio dei dati dal web service al client conviene passarli in wkb (well-known binary). Per questa operazione ci viene incontro un metodo degli ArcObjects: CreateWkbVariantFromGeometry.


Con il seguente codice convertiamo una IGeometry nella rappresentazione wkb.


IPolygon polygon = GetIGeometry();


IGeometryFactory3 geometryFactory = (IGeometryFactory3)serverContext.CreateObject("esriGeometry.GeometryEnvironment");


byte[] polygonWKB = geometryFactory.CreateWkbVariantFromGeometry(polygon) as byte[];



Lato client: creiamo un SqlGeometry con il metodo SQL server 2008 STGeomFromWKB e prima di memorizzare la geometria la validiamo con il metodo MakeValidGeographyFromGeometry della libreria SQL Server Spatial Tools


System.Data.SqlTypes.SqlBytes polygonWKB = new System.Data.SqlTypes.SqlBytes(PolygonWKB);


SqlGeometry sqlGeometry = SqlGeometry.STGeomFromWKB(polygonWKB, 4326);



SqlGeography sqlGeography = Functions.MakeValidGeographyFromGeometry(sqlGeometry);



using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["SQLServerConnectionString"].ConnectionString))


{


conn.Open();


string sqlCommandText = "insert into [YourTable]([Punto],[Poligono]) Values(@pPunto,@pPoligono)";



SqlGeography punto = SqlGeography.Point(lat, lon, 4326);


using (SqlCommand sqlCommand = new SqlCommand(sqlCommandText, conn))


{


sqlCommand.CommandType = CommandType.Text;


sqlCommand.Parameters.Add(new SqlParameter("@pPunto", punto) { UdtTypeName = "Geography" });


sqlCommand.Parameters.Add(new SqlParameter("@pPoligono", sqlGeometry) { UdtTypeName = "Geography" });


sqlCommand.ExecuteNonQuery();



}



}