Die Agenda beim Thema FDO in .NET sah folgende Punkte vor:

  1. Get the list of installed providers
  2. Create a connection manager
  3. Create a connection
  4. Set the connection properties
  5. Open a connection
  6. Get the connection state
  7. 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..