Die Agenda beim Thema FDO in .NET sah folgende Punkte vor:
- Get the list of installed providers
- Create a connection manager
- Create a connection
- Set the connection properties
- Open a connection
- Get the connection state
- Fetch Data
Die Punkte 1-6 wurden bereits im letzten Artikel abgehandelt. Wir werden uns nun dem 7. Punkt widmen. Und der hat es in sich. Denn wenn man die FDO API zum Laden von Daten verwendet, bekommt man FDO Objekte. Ziel soll es aber sein, die FDO Objekte in einem DotSpatial Viewer darzustellen.
Dazu muss man das Ergebnis der FDO API zu einem DotSpatial FeatureSet
zusammenbauen.
7. Fetch Data
Zunächst müssen wir ein Command
erzeugen, das wir später dann ausführen werden. (Eine Liste der verfügbaren Commands
ist im Namespace OSGeo.FDO.Commands.CommandType
zu finden. Außerdem sind die Commands
auch immer abhängig vom Datenprovider. Die Dokumentation der FDO hilft hier weiter.)
if (connState == OSGeo.FDO.Connections.ConnectionState.ConnectionState_Open) { ISelect sel = (ISelect)conn.CreateCommand(OSGeo.FDO.Commands.CommandType.CommandType_Select); sel.SetFeatureClassName("world_0"); IFeatureReader reader = sel.Execute(); FgfGeometryFactory geoFac = new FgfGeometryFactory();
Wir erzeugen also ein Select-Command
, mit dem man die Daten einer Datenquelle abfragen kann. Dann weisen wir noch dem Select-Command
den Namen der FeatureClass
zu, die wir abfragen wollen. Im Falle des Shapefiles ist es der blanke Dateiname ohne die Endung „.shp“. Da wir keinen Filter eingebaut haben, werden im nächsten Befehl alle Daten per FeatureReader
zurückgegeben.
Außerdem instanziieren wir eine GeometryFactory
, die wir später noch benötigen werden.
//FDO Get Feature Class Defintion (Attribut schema) DataTable dt = new DataTable(); ClassDefinition cDef = reader.GetClassDefinition(); foreach (PropertyDefinition pDef in cDef.Properties) { if (pDef.PropertyType == PropertyType.PropertyType_DataProperty) { DataPropertyDefinition dDef = pDef as DataPropertyDefinition; // Get .NET Datatype because of FDO's Enum of datatypes Type datatype = GetNETDataType(dDef); if (!dt.Columns.Contains(dDef.Name)) dt.Columns.Add(dDef.Name, datatype); } }
Im oben aufgezeigten Code-Block wird für das Attributschema der zu erzeugenden FeatureClass
zuerst eine DataTable
erzeugt und mit den entsprechenden Columns
und den zugehörigen Datentypen versehen. Da die FDO API eigene Enumerations
für die Datentypen einer Class
(FeatureClass
oder Tabelle) zurückgibt, für die Definition eines Attributschemas der DataTable
jedoch .NET Datentypen gebraucht werden, habe ich eine Hilfsfunktion (GetNETDataType
) geschrieben, die eben für ein Element aus dem Enum
von FDO einen .NET Datentyp zurückgibt. Den Code dieser Funktion findet man ganz unten auf dieser Seite.
Weiter gehts mit der Instanziierung eines FeatureSets
. Ein FeatureSet
entspricht in der Terminologie von DotSpatial einer Sammlung von Features
. Das Äquivalent der FDO API oder auch von ESRI wäre hier eine FeatureClass
. Da wir bereits eine DataTable
mit dem passenden Attributschema des Shapefiles aus der FDO API haben, können wir die Struktur der DataTable
mit einer Methode dem FeatureSet
übergeben.
DotSpatial.Data.FeatureSet fs = new DotSpatial.Data.FeatureSet(); fs.CopyTableSchema(dt);
Jetzt holen wir uns die Attributdaten und die Geometry
per FDO-FeatureReader aus dem Datensatz. Für jede Datenzeile erzeugen wir ein neues DotSpatial-Feature
, das später dann ins FeatureSet
wandert. Anschließend gehe ich in einer foreach
-Schleife die FDO PropertyDefinition
durch, was im Endeffekt einem Attributschema entspricht. Dabei kann man mit der FDO API unterscheiden, ob es eine DataProperty
oder eine GeometryProperty
ist.
Dann kommt eine etwas unschöne switch-case
-Anweisung auf die ich nicht Stolz bin, aber es geht meines Wissens mit der FDO API nicht anders. Man muss beim FeatureReader
der FDO wissen, welcher Datentyp dem Attribut zu Grunde liegt, um es mit der entsprechenden Methode (z.B.: reader.GetString()
) korrekt auslesen zu können. Am Besten versteht man das im Code. Sind alle Attribute durchlaufen und die entsprechenden Attributwerte ausgelesen, wird die DataRow
an die DataRow
des DotSpatial-Features
gehängt.
//FDO Get Data & Geometry while (reader.ReadNext()) { DataRow dr = dt.NewRow(); DotSpatial.Data.Feature feature = new DotSpatial.Data.Feature(); foreach (PropertyDefinition pDef in cDef.Properties) { if (pDef.PropertyType == PropertyType.PropertyType_DataProperty) { DataPropertyDefinition dDef = pDef as DataPropertyDefinition; switch (dDef.DataType.ToString()) { case "DataType_String": if (!reader.IsNull(dDef.Name)) dr[dDef.Name] = reader.GetString(dDef.Name); break; case "DataType_Int16": if (!reader.IsNull(dDef.Name)) dr[dDef.Name] = reader.GetInt16(dDef.Name); break; case "DataType_Int32": if (!reader.IsNull(dDef.Name)) dr[dDef.Name] = reader.GetInt32(dDef.Name); break; case "DataType_Int64": if (!reader.IsNull(dDef.Name)) dr[dDef.Name] = reader.GetInt64(dDef.Name); break; case "DataType_DateTime": if (!reader.IsNull(dDef.Name)) dr[dDef.Name] = reader.GetDateTime(dDef.Name); break; case "DataType_Single": if (!reader.IsNull(dDef.Name)) dr[dDef.Name] = reader.GetSingle(dDef.Name); break; case "DataType_Double": if (!reader.IsNull(dDef.Name)) dr[dDef.Name] = reader.GetDouble(dDef.Name); break; case "DataType_Decimal": if (!reader.IsNull(dDef.Name)) dr[dDef.Name] = reader.GetDouble(dDef.Name); break; case "DataType_Boolean": if (!reader.IsNull(dDef.Name)) dr[dDef.Name] = reader.GetBoolean(dDef.Name); break; default: if (!reader.IsNull(dDef.Name)) dr[dDef.Name] = reader.GetByte(dDef.Name); break; } feature.DataRow = dr; }
Beim Auslesen der Geometrie wirds auch nochmal spannend. Hier wird per FDO API die Geometrie binär ausgelesen und in eine FDO-Geometrie mit Hilfe der anfangs erzeugten GeometryFactory
verwandelt. Diese Bytes sind aber eben nicht standardmäßig vom Typ Well-Known-Binary (WKB). Das muss man erst noch einmal aus einer FDO-Geometrie erzeugen. WKB ist deswegen so wichtig, weil es der kleinste gemeinsame Nenner beim Austausch der Geometrie zwischen der FDO API und DotSpatial ist. Hab ich also eine WKB-Geometrie kann ich sie mit dem enstprechenden WKBReader
von DotSpatial in eine DotSpatial-Geometrie verwandeln und dem Feature
zuweisen. Dann bin ich mit den Attributdaten und der Geometrie durch und muss das Feature dem FeatureSet hinzufügen.
Achtung: Wichtig ist hier noch, dass man nicht die Methode fs.AddFeature()
verwendet. Diese akzeptiert auch Features
– unterschlägt jedoch die Attributdaten des übergebenen Features
.
if (pDef.PropertyType == PropertyType.PropertyType_GeometricProperty) { //Get Raw Binary Data and convert it to WKB-DotSpatial Byte[] bytes = reader.GetGeometry("Geometry"); IGeometry geom = geoFac.CreateGeometryFromFgf(bytes); Byte[] wkb = geoFac.GetWkb(geom); DotSpatial.Data.WKBReader wkbReader = new DotSpatial.Data.WKBReader(); DotSpatial.Topology.IGeometry dSgeom = wkbReader.Read(wkb); feature.BasicGeometry = dSgeom; } } fs.Features.Add(feature); } //TODO: Add DotSpatial FeatureSet to map //for example: //fs.Name = "test"; //this.map1.Layers.Add(fs); //this.map1.ZoomToExtents();
Hier noch die Übersetzermethode für die FDO-Datentypen in .NET-Datentypen.
/// <summary> /// Method for getting the .NET DataType from FDO DataPropertyDefintion /// </summary> /// <param name="dDef">FDO DataPropertyDefinition</param> /// <returns>.NET DataType</returns> public Type GetNETDataType(OSGeo.FDO.Schema.DataPropertyDefinition dDef) { switch (dDef.DataType.ToString()) { case "DataType_String": return System.Type.GetType("System.String"); case "DataType_Int16": return System.Type.GetType("System.Int16"); case "DataType_Int32": return System.Type.GetType("System.Int32"); case "DataType_Int64": return System.Type.GetType("System.Int64"); case "DataType_DateTime": return System.Type.GetType("System.DateTime"); case "DataType_Single": return System.Type.GetType("System.Single"); case "DataType_Double": return System.Type.GetType("System.Double"); case "DataType_Decimal": return System.Type.GetType("System.Decimal"); case "DataType_Boolean": return System.Type.GetType("System.Boolean"); default: return System.Type.GetType("System.Byte"); } }
Soweit die Verwendung der FDO API in .NET für DotSpatial. Ich habe das Ganze anhand eines Shapefiles durchgespielt, was natürlich nicht notwendig wäre, weil DotSpatial ESRI Shapefiles bereits liest. Interessant wird die FDO API aber dann bei OGC Webservices, GDAL/OGR und vor allem bei Oracle Spatial und der ArcSDE. Und um die Verwendung der FDO API bei der ArcSDE soll es im nachfolgenden Artikel gehen..