Archiv für ‘C#’

Oktober 24th, 2012

Solving the Transportation Problem (3) – MSF API in .NET

Solver Foundation in C#. Net

In order to solve the above described instance of a Transporation problem we only need a C# Console Application with a few lines of code. So start your IDE (SharpDevelop or Visual Studio [Express Edition will work either]) and create a new console application project. Then add a reference to the Microsoft.Solver.Foundation.dll under „References“. This DLL should be found after the successful installation of MSF in „C:\Program Files\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0\“. As we are using Data (DataTables, DataSets) you also need to reference „System.Data“.

The agenda for the current article is the following:

  • load the OML model into Solver Foundation Context
  • retrieve data from our sample Access Database
  • bind data to the model
  • solve the problem
  • print out minimized total result and the amount of flow of each arc

So here we go with the first section of our class.

using System;
using System.IO;
using System.Data;
using System.Data.OleDb;
using Microsoft.SolverFoundation.Services;

namespace GSharpBlog.Optimization.TP_Example 
{
	class Program
	{
        /// Gets results of a SQL query through a OLE-DB connection.
        private static DataSet getDataFromDB(string connectionStr, string query)
        {
            DataSet ds = new DataSet();
            OleDbDataAdapter adapter = new OleDbDataAdapter();
            OleDbConnection conn = new OleDbConnection(connectionStr);
            try {
                conn.Open();
                adapter.SelectCommand = new OleDbCommand(query, conn);
                adapter.Fill(ds);
                return ds;
            }
            catch (Exception ex){ throw ex; }
            finally{ conn.Close(); }
        }

First we state a helper method for retrieving data from our Access Database, that simply takes the Connection String and the SQL query and returns a DataSet. After that we can go on with the automatically generated Main() method in which all code is written for the sake of simplicity.
In the following lines of code we define the above stated OML model as string and read it into the Solver Foundation Context. Then the structure of the supply, demand and arcs table (or OD-Matrix) are defined as DataTables.

            // Access (2007) DB ConnectionString
            string connectionStr = @"Provider=Microsoft.ACE.OLEDB.12.0;Data Source=D:\tp.accdb";

			// Transportation Problem as OML model
			string strModel = @"Model[
				Parameters[Sets,Source,Sink],
				Parameters[Reals,Supply[Source],Demand[Sink],Cost[Source,Sink]],
				Decisions[Reals[0,Infinity],flow[Source,Sink],TotalCost],
				Constraints[
					TotalCost == Sum[{i,Source},{j,Sink},Cost[i,j]*flow[i,j]],
                    Foreach[{i,Source}, Sum[{j,Sink},flow[i,j]]<=Supply[i]],
					Foreach[{i,Source}, Sum[{j,Sink},flow[i,j]]>=Demand[j]]],
				Goals[Minimize[TotalCost]] ]";

			// Load OML-Model
			SolverContext context = SolverContext.GetContext();
			context.LoadModel(FileFormat.OML, new StringReader(strModel));
			context.CurrentModel.Name = "Transportation Problem";

			// Create Tables
			// Supply table
			DataTable pSupply = new DataTable();
			pSupply.Columns.Add("SupplyNode",Type.GetType("System.String"));
			pSupply.Columns.Add("Supply",Type.GetType("System.Int32"));

			// Demand table
			DataTable pDemand = new DataTable();
			pDemand.Columns.Add("DemandNode",Type.GetType("System.String"));
			pDemand.Columns.Add("Demand",Type.GetType("System.Int32"));

			// OD-Matrix
			DataTable pCost = new DataTable();
			pCost.Columns.Add("SupplyNode",Type.GetType("System.String"));
			pCost.Columns.Add("DemandNode",Type.GetType("System.String"));
			pCost.Columns.Add("Cost",Type.GetType("System.Double"));

Then data is pulled out of our Access Database and loaded into the DataTables. Now we can use the helper method getDataFromDB().

            //// Fill tables
            // 1. Fill Supply
            string query = String.Empty;
            DataSet accessDS = new DataSet();
            query = "SELECT SupplyNode, Supply FROM Supply ORDER BY SupplyNode";
            accessDS = getDataFromDB(connectionStr, query);

            foreach (DataRow row in accessDS.Tables[0].Rows) {
                pSupply.Rows.Add(row[0].ToString(), row[1]);
            }

            // Clear
            query = String.Empty;
            accessDS.Clear();

            // 2.Fill Demand
            query = "SELECT DemandNode, Demand FROM Demand ORDER BY DemandNode";
            accessDS = getDataFromDB(connectionStr, query);

            foreach (DataRow row in accessDS.Tables[0].Rows) {
                pDemand.Rows.Add(row[0].ToString(), row[1]);
            }

            // Clear
            query = String.Empty;
            accessDS.Clear();

            // 3. Fill Arcs (or OD-Matrix)
            query = "SELECT SupplyNode, DemandNode, Cost FROM Arcs ORDER BY SupplyNode";
            accessDS = getDataFromDB(connectionStr, query);

            foreach (DataRow row in accessDS.Tables[0].Rows) {
                pCost.Rows.Add(row[0].ToString(), row[1].ToString(), row[2]);
            }

Now we bind the data to the OML model that we have defined earlier. Microsoft Solver Foundation has an advanced binding mechanism that is pretty straight forward. With the following lines, we bind the data from our tables to the OML model: loop through the parameters in the model and simply call the method SetBinding([DataTable].AsEnumerable(), [ValueColumnName], [IDColumnName]) for each parameter.

			// Bind values from tables to parameter of the OML model
			foreach (Parameter p in context.CurrentModel.Parameters)
			{
				switch (p.Name)
					{
						case "Supply":
							p.SetBinding(pSupply.AsEnumerable(), "Supply", "SupplyNode");
							break;
						case "Demand":
							p.SetBinding(pDemand.AsEnumerable(), "Demand", "DemandNode");
							break;
						case "Cost":
                            p.SetBinding(pCost.AsEnumerable(), "Cost", "SupplyNode", "DemandNode");
							break;
					}
			}

Then we just have to call the Solve() method and fetch the result. Now we loop through the decisions of the model, look for the decision „TotalCost“ and fetch the value of the optimized result.

			// Call solver
			solution = context.Solve();

			// Fetch results: minimized total costs
			Report report = solution.GetReport();	
			double cost = 0;
			foreach (Decision desc in solution.Decisions) {
				if (desc.Name == "TotalCost")
				{
						foreach (object[] value in desc.GetValues()) {
							cost = Convert.ToDouble(value[0]);
						}
				}
			}

Because we want to know not only the optimized total costs but also the amount of flow between each supply node and demand node we have to query the solution more detailed. We get information about the source/sink-pairs about the amount of flow from MSF – but we lack the distance (or cost) between each pair. So we need to lookup the distances in the original arcs table.

// Print out optimized results
string result = String.Empty;
double totalFlow = 0.0;
foreach (Decision desc in solution.Decisions) {
	// flow as variable
	if (desc.Name == "flow")
	{
		foreach (object[] value in desc.GetValues()) {
			string source = value[1].ToString();
			string sink =  value[2].ToString();
			double flow = Convert.ToDouble(value[0]);

			// lookup km from arcs table
			DataRow[] rows = new DataRow[1];
			rows = pCost.Select("SupplyNode ='"+source+"' AND DemandNode ='"+sink+"'");
			double km = Convert.ToDouble(rows[0]["Cost"]);
			string sourceSink = String.Format("{0}_{1}", source, sink);
			if(flow != 0)
			{
				totalFlow += flow;
				result = result + "\n" + String.Format("\"{0}\";\"{1}\";\"{2}\";{3};{4}",
													   sourceSink, source, sink, flow, km);
			}
		}
		Console.WriteLine(result);
	}
}

The optimized result is 46. Our solver application prints out the following results.

Total result: 46
**********************

„S1_D1″;“S1″;“D1“;5;3
„S2_D1″;“S2″;“D1“;2;4
„S2_D2″;“S2″;“D2“;3;2
„S2_D3″;“S2″;“D3“;2;4
„S3_D3″;“S3″;“D3“;3;3

Download as IDE-Project (SharpDevelop/Visual Studio 2010)

4 Leute mögen diesen Artikel.

Mai 30th, 2011

ArcSDE lesen mit OpenSource GIS in .NET

In diesem Artikel möchte ich zeigen, wie man Geometrieobjekte aus einer ESRI ArcSDE-Datenbank mit einem OpenSource GIS Client visualisieren lassen kann. Dieser Ansatz ist nicht komplett quelloffen, da sich die verwendete Feature Data Object (FDO) API auch der proprietären ArcSDE API bedienen. Als GIS-Viewer wird die Bibliothek DotSpatial eingesetzt. In einer vor kurzem veröffentlichten Artikelserie habe ich beschrieben, wie man die FDO API mit DotSpatial in C# als Datenprovider verwenden kann. Als beispielhafte Datenquelle diente ein ESRI Shapefile.

Bei einer ArcSDE als Datenquelle wirds nur leicht anders. Das ist das Schöne an der FDO API: wenn man das Konzept und die Vorgehensweise einmal begriffen hat, ist die Verwendung der FDO API unabhängig vom Datenprovider. Nur einige wenige Dinge ändern sich.

Voraussetzungen
Die FDO API ist bezüglich des Datenproviders für eine ArcSDE abhängig von der proprietären ESRI ArcSDE API. Die entsprechenden DLLs von ESRI müssen deshalb im Systempfad zu finden sein. Eine detailliertere Beschreibung findet sich auf den Seiten der FDO API. Ein kurzer Auszug der Informationen, die sich zwar auf ArcGIS 9.1 beziehen, jedoch im wesentlichen auch für 9.3.1 gelten.

The operation of FDO Provider for ArcSDE is dependent on the presence of ArcSDE 9 and a supported data source, such as Oracle 9i, in the network environment. The host machine running FDO Provider for ArcSDE must also have the required DLLs present, which are available by installing either an ArcGIS 9.1 Desktop application or the ArcSDE SDK. For example, the required DLLs are present if either ArcView®, ArcEditor®, or ArcInfo® are installed. For more information about ArcGIS 9.1 Desktop applications and the ArcSDE SDK, refer to the ESRI documentation.

Specifically, in order for FDO Provider for ArcSDE to run, three dynamically linked libraries, sde91.dll, sg91.dll, and pe91.dll, are required and you must ensure that the PATH environment variable references the local folder containing these DLLs. For example, in Microsoft Windows, if ArcGIS 9.1 Desktop is installed to C:\Program Files\ArcGIS, then the required ArcSDE binaries are located at C:\Program Files\ArcGIS\ArcSDE\bin. Similarly, if the ArcSDE SDK is installed to the default location, then the required ArcSDE binaries are located at C:\ArcGis\ArcSDE\bin. The absence of this configuration may cause the following exception message „The ArcSDE runtime was not found.“.

Einzige Veränderung gegenüber der Version von 9.1 ist mittlerweile, dass die benötigten DLLs wohl nun im Verzeichnis „C:\Program Files\ArcGIS\Bin“ liegen. Das Verzeichnis ArcSDE gibt es auf der Clientseite (ArcGIS Desktop-Installation) nicht mehr.

Datenprovider-String

Der Datenprovider für die ArcSDE muss wie bei jedem anderen FDO-Provider auch dem ConnectionManager als Argument mitgeteilt werden.

FDORegistry = FeatureAccessManager.GetProviderRegistry();
FDOManager = FeatureAccessManager.GetConnectionManager();
FDOConnection = FeatureAccessManager.GetConnectionManager().CreateConnection("OSGeo.ArcSDE.3.6");

Verbindungsparameter

Die Verbindungsparameter sind im Gegensatz zum ESRI Shapefile etwas umfangreicher. Es müssen die im Datenbankbereich üblichen Verbindungseigenschaften folgendermaßen gesetzt werden:

IConnectionPropertyDictionary connectionPropertyDictionary;
connectionPropertyDictionary = FDOConnection.ConnectionInfo.ConnectionProperties;
connectionPropertyDictionary.SetProperty("Server","localhost");
connectionPropertyDictionary.SetProperty("Instance","5151");
connectionPropertyDictionary.SetProperty("Username","gisuser");
connectionPropertyDictionary.SetProperty("Password","password");
connectionPropertyDictionary.SetProperty("Datastore","Default Datastore");

Wobei die sog. Datastore zunächst einmal auf „Default Datastore“ gesetzt werden kann. Auf der Seite der FDO API findet man folgende Info dazu:

An ArcSDE data source may contain more than one data store. For the first call to Open(), a data store name is optional. If successful, the first call to Open() results in the data store parameter becoming a required parameter and a list of the names of the data stores in the data source becoming available. You must choose a data store and call Open() again.

Das Auswählen der Datastore funktioniert in C#-Code übersetzt folgendermaßen:

string value;
bool isRequired;
bool isEnumerable;
string[] strArry = null;
IConnectionPropertyDictionary dict = FDOConnection.ConnectionInfo.ConnectionProperties;
foreach (string st in dict.PropertyNames) {
	value = dict.GetProperty(st);
	isRequired = dict.IsPropertyRequired(st);
	isEnumerable = dict.IsPropertyEnumerable(st);

	if (isEnumerable && st == "Datastore")
	{
		strArry = new string[dict.EnumeratePropertyValues(st).Length];
		dict.EnumeratePropertyValues(st).CopyTo(strArry,0);
	}
}

Damit haben wir schon alles, was wir für den ArcSDE-Zugriff mit der FDO API in .NET brauchen. Das Verfahren, die Attributdaten und die Geometriedaten per FeatureReader zu lesen und in ein DotSpatial FeatureSet zu übersetzen ist identisch mit dem Verfahren beim ESRI Shapefile in den Artikeln 1,2 und 3.

Eine Besonderheit noch gegenüber dem Auslesen von Shapefiles ist natürlich, das eine ArcSDE mehrere Schemata besitzen kann, in denen widerum mehrere Feature Classes vorhanden sind. Mit der FDO API geht man jedoch nicht den hierarchischen Weg von Datenbank -> Schema -> Feature Class.

Vielmehr ist es hier so, dass die Feature Classes einen voll qualifizierenden Namen bekommen. Das heisst, die Feature Classes werden mit der FDO API nach folgendem Muster zugänglich gemacht, wenn man sich erfolgreich zur Datenbank verbunden hat:

<SCHEMA>:<FEATURE CLASS>

Ein Beispiel:

GISDYN:WEGE“ steht für die Feature Class „WEGE“, die im Schema „GISDYN“ angesiedelt ist. Beim setzen des Feature Class Namens sollte dies also berücksichtigt werden.

Mit dieser Info versteht man auch den unten beigefügten Code der Methode ListTablesOfDB() besser.

Versionierten SDE-Zugriff habe ich jedoch nicht getestet. Der standardmäßige Zugriff erfolgt wohl über die Version SDE.DEFAULT.

Als Anhang noch hier kompletten Code zum Zugriff auf eine ArcSDE über die FDO API:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using OSGeo.FDO;
using OSGeo.FDO.Connections;
using OSGeo.FDO.ClientServices;
using OSGeo.FDO.Geometry;
using OSGeo.FDO.Commands;
using OSGeo.FDO.Commands.Feature;
using OSGeo.FDO.Expression;
using OSGeo.FDO.Commands.DataStore;
using OSGeo.FDO.Commands.Schema;
using OSGeo.FDO.Filter;
using OSGeo.FDO.Schema;
using DotSpatial.Data;

namespace GSharpDotSpatial
{

	public class ArcSDEHelper
    {
		private string hostname;
		private int port;
		private string user;
		private string password;
		private string datastore;
		private string connStr;
		private string pkeyColumn;
		private DataTable dataTable;

		private IConnection FDOConnection;
        private IProviderRegistry FDORegistry;
        private IConnectionManager FDOManager;
        private OSGeo.FDO.Connections.ConnectionState ConState;

        public ArcSDEHelper(string _hostname, int _port, string _user, string _password, string _datastore)
        {
            this.hostname = _hostname;
			this.port = _port;
			this.user = _user;
			this.password = _password;
			this.datastore = _datastore;

			connStr = "Server=" + hostname + ";" + "Instance=" + port + ";" + "User=" + user + ";" + "Password=" + password + ";" + "Datastore=" + datastore + ";";
        }

        public IConnection connect()
		{
        	try
        	{
			    FDORegistry = FeatureAccessManager.GetProviderRegistry();
				FDOManager = FeatureAccessManager.GetConnectionManager();
				FDOConnection = FeatureAccessManager.GetConnectionManager().CreateConnection("OSGeo.ArcSDE.3.6"); // Replace the version of DLL your using…

				IConnectionPropertyDictionary connectionPropertyDictionary;
				connectionPropertyDictionary = FDOConnection.ConnectionInfo.ConnectionProperties;
				connectionPropertyDictionary.SetProperty("Server",hostname);
				connectionPropertyDictionary.SetProperty("Instance",port.ToString());
				connectionPropertyDictionary.SetProperty("Username",user);
				connectionPropertyDictionary.SetProperty("Password",password);
				connectionPropertyDictionary.SetProperty("Datastore",datastore);

//				FDO Gets the Datastore options out of a pending connection (2-step connection)
//				FDOConnection.Open();
//				string s = "";
//				string[] strArry = null;
//				IConnectionPropertyDictionary dict = FDOConnection.ConnectionInfo.ConnectionProperties;
//				foreach (string st in dict.PropertyNames) {
//
//					string val = dict.GetProperty(st);
//					string defVal = dict.GetPropertyDefault(st);
//					string localname = dict.GetLocalizedName(st);
//					bool isRequired = dict.IsPropertyRequired(st);
//					bool isEnumerable = dict.IsPropertyEnumerable(st);
//
//					if (isEnumerable)
//					{
//						strArry = new string[dict.EnumeratePropertyValues(st).Length];
//						dict.EnumeratePropertyValues(st).CopyTo(strArry,0);
//					}
//				}

				return FDOConnection;
        	}
        	catch (OSGeo.FDO.Common.Exception ex)
        	{
        		Console.WriteLine(ex.Message);
                Console.ReadLine();
                return null;
        	}
		}        

        public string ConnStr
        { get { return connStr; } }

        public DataTable ListSchema()
        {
            OSGeo.FDO.Connections.IConnection conn = connect();
            ConState = conn.Open();
            try
            {
            	if (null != conn && ConState == OSGeo.FDO.Connections.ConnectionState.ConnectionState_Open)
        		{
	            	// Get Schema Names:
					OSGeo.FDO.Commands.Schema.IGetSchemaNames pGSN =(OSGeo.FDO.Commands.Schema.IGetSchemaNames)conn.CreateCommand
						(OSGeo.FDO.Commands.CommandType.CommandType_GetSchemaNames);

					OSGeo.FDO.Common.StringCollection stCol = pGSN.Execute();

	            	dataTable = new DataTable();
	            	dataTable.Columns.Add("FeatureSchemaName", typeof(string));

					foreach ( OSGeo.FDO.Common.StringElement s in stCol)
					{
						dataTable.Rows.Add(s.String);
					}
					// sort dataTable
					DataView v = dataTable.DefaultView;
					v.Sort = "FeatureSchemaName ASC";
					dataTable = v.ToTable();

	                conn.Close();
	                return dataTable;
            	}
            	else {return null;}
            }
            catch (OSGeo.FDO.Common.Exception ex)
            {
                Console.WriteLine(ex.Message);
                Console.ReadLine();
                return null;
            }
        }
        public DataTable ListTablesOfDB(string _schemaName)
        {
            OSGeo.FDO.Connections.IConnection conn = connect();
            ConState = conn.Open();
            try
            {
            	if (null != conn && ConState == OSGeo.FDO.Connections.ConnectionState.ConnectionState_Open)
        		{
	                //Get a list of tables in the DB
					OSGeo.FDO.Commands.Schema.IGetClassNames pGSN =(OSGeo.FDO.Commands.Schema.IGetClassNames)conn.CreateCommand
						(OSGeo.FDO.Commands.CommandType.CommandType_GetClassNames);

					OSGeo.FDO.Common.StringCollection stCol = pGSN.Execute();

	            	dataTable = new DataTable();
	            	dataTable.Columns.Add("FeatureClasses", typeof(string));

					foreach ( OSGeo.FDO.Common.StringElement s in stCol)
					{
						if (s.String.StartsWith(_schemaName))
						{
							string newStr = s.String.Replace(_schemaName + ':', string.Empty);
						}
					}

					// sort dataTable
					DataView v = dataTable.DefaultView;
					v.Sort = "FeatureClasses ASC";
					dataTable = v.ToTable();

	                conn.Close();
	                return dataTable;
            	}
            	else {return null;}
            }
            catch (OSGeo.FDO.Common.Exception ex)
            {
                Console.WriteLine(ex.Message);
                Console.ReadLine();
                return null;
            }
            finally { conn.Close(); }
            }

        public string GetGeomColumn(string _tableName, string _schemaName)
        {
            string geomCol = "";
            OSGeo.FDO.Connections.IConnection conn = connect();
            ConState = conn.Open();
            try
            {
            	if (null != conn && ConState == OSGeo.FDO.Connections.ConnectionState.ConnectionState_Open)
            	{
					ISelect sel = (ISelect)conn.CreateCommand(OSGeo.FDO.Commands.CommandType.CommandType_Select);
					sel.SetFeatureClassName(_tableName);

					IFeatureReader FDOReader = sel.Execute();
					OSGeo.FDO.Schema.ClassDefinition cDef = FDOReader.GetClassDefinition();

					foreach (OSGeo.FDO.Schema.PropertyDefinition def in cDef.Properties)
					{
						if (def.PropertyType == OSGeo.FDO.Schema.PropertyType.PropertyType_GeometricProperty)
						{
							geomCol = def.Name;
						}
					}
	                conn.Close();
	                return geomCol;
            	}
            	else {return null;}
            }
            catch (OSGeo.FDO.Common.Exception ex)
            {
                Console.WriteLine(ex.Message);
                Console.ReadLine();
                return null;
            }
            finally { conn.Close(); }

        }
		public FeatureSet GetAllFeatures(string _featureClassName, string _geomColumn)
        {
            OSGeo.FDO.Connections.IConnection conn = connect();
            ConState = conn.Open();

            if (ConState == OSGeo.FDO.Connections.ConnectionState.ConnectionState_Open
                && _featureClassName != null && (_geomColumn != null || _geomColumn != ""))
            {
                ISelect sel = (ISelect)conn.CreateCommand(OSGeo.FDO.Commands.CommandType.CommandType_Select);
                sel.SetFeatureClassName(_featureClassName);

                IFeatureReader FDOReader = sel.Execute();

                GeometryCollection Geo_Collection = new GeometryCollection();
                FgfGeometryFactory fdoGeoFac = new FgfGeometryFactory();

                OSGeo.FDO.Schema.ClassDefinition cDef = FDOReader.GetClassDefinition();

                DotSpatial.Data.FeatureSet fs = new FeatureSet();

                //Populate schema of DataTable
                foreach (OSGeo.FDO.Schema.PropertyDefinition pDef in cDef.Properties)
                {
                    // Only Data - no geometry!
                    if (OSGeo.FDO.Schema.PropertyType.PropertyType_DataProperty == pDef.PropertyType)
                    {
                        //Get Datatype
                        var dProDef = pDef as OSGeo.FDO.Schema.DataPropertyDefinition;
                        OSGeo.FDO.Schema.DataType typ = dProDef.DataType;
                        Type t = ChangeType(typ);
                        //Get Property name
                        string propertyName = pDef.Name;
                        fs.DataTable.Columns.Add(pDef.Name, t);
                    }
                }

                while (FDOReader.ReadNext())
                {
                    Feature feat = new Feature();
                    DataRow dr = fs.DataTable.NewRow();
                    foreach (PropertyDefinition pDef in cDef.Properties)
                    {
                        if (pDef.PropertyType == PropertyType.PropertyType_DataProperty)
                        {
                            DataPropertyDefinition dDef = pDef as DataPropertyDefinition;
                            switch (dDef.DataType)
                            {
                                case OSGeo.FDO.Schema.DataType.DataType_String:
                                    if (!FDOReader.IsNull(dDef.Name))
                                        dr[dDef.Name] = FDOReader.GetString(dDef.Name);
                                    break;
                                case OSGeo.FDO.Schema.DataType.DataType_Int16:
                                    if (!FDOReader.IsNull(dDef.Name))
                                        dr[dDef.Name] = FDOReader.GetInt16(dDef.Name);
                                    break;
                                case OSGeo.FDO.Schema.DataType.DataType_Int32:
                                    if (!FDOReader.IsNull(dDef.Name))
                                        dr[dDef.Name] = FDOReader.GetInt32(dDef.Name);
                                    break;
                                case OSGeo.FDO.Schema.DataType.DataType_Int64:
                                    if (!FDOReader.IsNull(dDef.Name))
                                        dr[dDef.Name] = FDOReader.GetInt64(dDef.Name);
                                    break;
                                case OSGeo.FDO.Schema.DataType.DataType_DateTime:
                                    if (!FDOReader.IsNull(dDef.Name))
                                        dr[dDef.Name] = FDOReader.GetDateTime(dDef.Name);
                                    break;
                                case OSGeo.FDO.Schema.DataType.DataType_Single:
                                    if (!FDOReader.IsNull(dDef.Name))
                                        dr[dDef.Name] = FDOReader.GetSingle(dDef.Name);
                                    break;
                                case OSGeo.FDO.Schema.DataType.DataType_Double:
                                    if (!FDOReader.IsNull(dDef.Name))
                                        dr[dDef.Name] = FDOReader.GetDouble(dDef.Name);
                                    break;
                                case OSGeo.FDO.Schema.DataType.DataType_Decimal:
                                    if (!FDOReader.IsNull(dDef.Name))
                                        dr[dDef.Name] = FDOReader.GetDouble(dDef.Name);
                                    break;
                                case OSGeo.FDO.Schema.DataType.DataType_Boolean:
                                    if (!FDOReader.IsNull(dDef.Name))
                                        dr[dDef.Name] = FDOReader.GetBoolean(dDef.Name);
                                    break;
                                default:
                                    if (!FDOReader.IsNull(dDef.Name))
                                        dr[dDef.Name] = FDOReader.GetByte(dDef.Name);
                                    break;
                            }
                            feat.DataRow = dr;
                        }

                        if (pDef.PropertyType == PropertyType.PropertyType_GeometricProperty)
                        {
                            //Get Geometry with FDO Reader
                            Byte[] Tmppts = FDOReader.GetGeometry(_geomColumn);
                            IGeometry fdoGeo = fdoGeoFac.CreateGeometryFromFgf(Tmppts);
                            // Convert to WKB
                            Byte[] wkbFDO = fdoGeoFac.GetWkb(fdoGeo);

                            // Read WKB from FDO and convert to DotSpatial Geometry
                            DotSpatial.Topology.GeometryFactory geoFac = new DotSpatial.Topology.GeometryFactory();
                            DotSpatial.Topology.Utilities.WkbReader wkbReader = new DotSpatial.Topology.Utilities.WkbReader();
                            DotSpatial.Topology.IGeometry geom = wkbReader.Read(wkbFDO);

                            //Add DotSpatial Geometry
                            feat.BasicGeometry = geom;
                        }
                    }
                    fs.Features.Add(feat);
                }
                return fs;
            }
            else {return null;}
        }

        public static Type ChangeType(OSGeo.FDO.Schema.DataType dt)
		{

			switch (dt)
			{
				case OSGeo.FDO.Schema.DataType.DataType_BLOB:
				{
					return Type.GetType("Sytem.Object");
				}
				case OSGeo.FDO.Schema.DataType.DataType_Boolean:
				{
					return Type.GetType("System.Boolean");
				}
				case OSGeo.FDO.Schema.DataType.DataType_Byte:
				{
					return Type.GetType("System.Byte");
				}
				case OSGeo.FDO.Schema.DataType.DataType_Int16:
				{
					return Type.GetType("System.Int16");
				}
				case OSGeo.FDO.Schema.DataType.DataType_Int32:
				{
					return Type.GetType("System.Int32");
				}
				case OSGeo.FDO.Schema.DataType.DataType_Int64:
				{
					return Type.GetType("System.Int64");
				}
				case OSGeo.FDO.Schema.DataType.DataType_Single:
				{
					return Type.GetType("System.Single");
				}
				case OSGeo.FDO.Schema.DataType.DataType_Double:
				{
					return Type.GetType("System.Double");
				}
				case OSGeo.FDO.Schema.DataType.DataType_Decimal:
				{
					return Type.GetType("System.Decimal");
				}
				case OSGeo.FDO.Schema.DataType.DataType_DateTime:
				{
					return Type.GetType("System.DateTime");
				}
				case OSGeo.FDO.Schema.DataType.DataType_String:
				{
					return Type.GetType("System.String");
				}
			}
		throw new ArgumentException("Unknown DataType");
		}
	}
}

5 Leute mögen diesen Artikel.

Mai 29th, 2011

Feature Data Object API und DotSpatial in .NET (3)

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..

2 Leute mögen diesen Artikel.

Mai 29th, 2011

Feature Data Object API und DotSpatial in .NET (2)

Ich möchte zunächst einmal eine Verbindung zu einem ESRI Shapefile mit der FDO API erstellen. Die Verbindung zu einer ESRI ArcSDE mit der FDO werde ich später aufzeigen.
Die Dokumentation von FDO gibt unter der Rubrik „Getting Started“ folgende Hinweise:

Write the Code to Connect to a Provider

Do the following:

  1. Get the list of installed providers
  2. Create a connection manager
  3. Create a connection
  4. Get the connection state
  5. Get the connection properties
  6. Get values for the connection properties
  7. Set the connection properties
  8. Open a connection
  9. Open a pending connection

Diese Auflistung werde ich etwas verkürzen. Die Stufen 5-6 kann man nämlich auch mit Hilfe der Dokumentation lösen. Interessant ist jedoch, dass man auch per Code die jeweiligen Verbindungseinstellungen und Möglichkeiten abrufen könnte.

Meine Agenda sieht folgendes 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

Und das wollen wir nun mit ein paar Code-Schnippsel füllen.

1. Get the list of installed providers

Mit den folgenden Zeilen kann man sich alle registrierten Provider anzeigen lassen.

//FDO Provider Registry
IProviderRegistry FDORegistry = (IProviderRegistry)FeatureAccessManager.GetProviderRegistry();
ProviderCollection providers = FDORegistry.GetProviders();
string s = "Registered FDO Providers: n";
foreach (Provider p in providers)
{
	s += "# " + p.DisplayName + "n";
}
MessageBox.Show(s, "Registered FDO providers", MessageBoxButtons.OK, MessageBoxIcon.Information);

Hat alles geklappt, sollte man folgende MessageBox sehen:

Achtung: Dabei werden die Provider ausgegeben, wie sie in der Datei providers.xml aufgelistet sind. Das bedeutet jedoch nicht, dass alle Provider und die Assemblies dafür auch wirklich vorhanden sind. Die Provider-Registry liest also nur das XML aus, ohne zu prüfen, ob die referenzierten DLLs auch existieren.

 

2. Create a connection manager / 3. Create a connection / 4. Set the connection properties / 5. Open a connection

Wir brauchen also erst einmal einen ConnectionManager. Mit diesem erzeugen wir eine Verbindung mit der Angabe des entsprechenden Datenproviders als string, z.B. "OSGeo.SHP.3.6".

//FDO Connection - ShapeFile example
IConnectionManager connManager = FeatureAccessManager.GetConnectionManager();
IConnection conn = connManager.CreateConnection("OSGeo.SHP.3.6");
string shapeFile = @"D:\GISDATA\world_adm0.shp";
conn.ConnectionInfo.ConnectionProperties.SetProperty("DefaultFileLocation", shapeFile);
OSGeo.FDO.Connections.ConnectionState connState = conn.Open();

Mit der Wahl des Datenproviders ändern sich die Möglichkeiten, die man bei den Verbindungseigenschaften (ConnectionProperties) setzen kann. Eine Liste der Eigenschaften, die je nach Provider gesetzt werden müssen bzw. können findet man in der FDO Doku – The Open Source Providers ->Connection API. Bei ESRI Shapefile muss man mindestens "DefaultFileLocation" oder "TemporaryFileLocation" und den Pfad zum Shapefile angeben (s.o.).

In der letzten Zeile des obigen Code-Blocks merkt man spätestens beim Aufruf der Methode Open(), ob die benötigten Datenprovider auch wirklich an der Stelle sind, wie sie in der Datei providers.xml beschrieben wurden. Erst dann werden die DLLs samt Abhängigkeiten nämlich zur Laufzeit geladen.

6. Get the connection state

Den Status der Verbindung sollte man abrufen, um eine Fehlerquelle auszuschließen. Das geht auch recht einfach. Anschließend werden wir Daten aus der Datenquelle holen.

//FDO Check ConnectionState
if (connState == OSGeo.FDO.Connections.ConnectionState.ConnectionState_Open)
{
	//Fetch Data
}

Wie man nun aus der FDO-Verbindung Daten herausbekommt (Punkt 7. Fetch Data) bespreche ich im dritten Teil

2 Leute mögen diesen Artikel.

Mai 29th, 2011

Feature Data Object API und DotSpatial in .NET (1)

DotSpatial

DotSpatial ist eine sehr gut dokumentierte, quelloffene GIS – Bibliothek für .NET 4. In diesem Projekt sind einige Open Source basierte GIS-Bibliotheken zusammen geflossen – unter anderem MapWindow6, ProjNET oder GPS.NET3. Auch Entwickler von SharpMap sind bei dem groß angelegten Projekt dabei. Gute Tutorials zu DotSpatial für den Einstieg findet man hier. SharpMap ist zwar ganz toll – es existiert ja auch schon länger für .NET. Aber DotSpatial ist wirklich der Hammer!

Project Vision: DotSpatial aims to provide a free, open source, consistent and dependable set of libraries for the .NET, Silverlight and Mono platforms, enabling developers to easily incorporate spatial data, analysis, and mapping into their applications thereby unleashing the massive potential of GIS in solutions for organizations and communities of all types in a nonrestrictive way.

DotSpatial Architektur

Mit DotSpatial kann man – ohne viel Programmierkenntnisse zu besitzen – ein kleines GIS zusammenbasteln. Mit der entsprechenden Entwicklungsumgebung (MS Visual Studio Express oder SharpDevelop) kommt man schon mit Drag&Drop so weit, dass man die meisten Funktionen von DotSpatial nutzen kann. Bei DotSpatial werden die entsprechenden GIS-Controls mitgeliefert – so wie man es vielleicht von anderen kommerziellen GIS-Bibliotheken kennt. Man merkt schon: Ich bin begeistert..

Im aktuellen Release von DotSpatial kann die Bibliothek mehrere Geodatenformate (sowohl Vektor- als auch Rasterformate) lesen. Schreiben kann es auf jeden Fall ESRI Shapefiles. Seit kurzem gibt es auch einen experimentellen lesenden Datenprovider für PostGIS und SpatiaLite. Schöne Sache – aber es gibt ja noch mehr Geodatenbanken bzw. Middleware auf diesen.  Ich wollte also lesenden Zugriff auf die ESRI ArcSDE in einem eigenen GIS-Viewer realisieren.

Nach etwas Recherche kam ich auf die Feature Data Object API.

Feature Data Object API

Die Feature Data Object API ist ein OSGeo-Projekt, das ursprünglich von Autodesk entwickelt wurde. Es stellt ein abstraktes Objektmodell für Geodaten dar, das viele Geodatenformate lesen und schreiben kann.

FDO Data Access Technology is an API for manipulating, defining and analyzing geospatial information regardless of where it is stored. FDO uses a provider-based model for supporting a variety of geospatial data sources, where each provider typically supports a particular data format or data store. FDO (“Feature Data Object”) is free, open source software licensed under the LGPL.

Feature Data Objects ArchitekturUnter anderem liest diese Bibliothek ESRI ArcSDE, OGR/GDAL, OGC Web Services, MySQL und mit einem proprietären Provider auch Oracle Spatial. Eine vollständige Liste und die Möglichkeiten der Provider findet man hier.

Interessanterweise setzt auch safe die FDO API in ihrer FME-Software ein. Auf jeden Fall ist die Bibliothek Open Source und sowohl in C++ als auch in .NET einsetzbar. Eine GUI für die API ist die FDO Toolbox.

So wunderbar die Dokumentation von DotSpatial für den Anfang auch ist – bei FDO fuer .NET muss man schon länger suchen und einiges ausprobieren. Zwar gibt es einiges an Doku auf der Seite der FDO. Allerdings fehlten mir praktische Beispiele für den schnellen Einstieg. Hilfreich war aber dieses Tutorial:

„Taking Geospatial Data Access to the next level with the FDO API“

Voraussetzungen für die Verwendung der FDO API in .NET

Im Ausgabeverzeichnis (bin\Debug oder bin\Release) des Visual Studio-Projekts müssen folgende Assemblies von FDO liegen:

  • Fdo.dll
  • FDOCommon.dll
  • FDOGeometry.dll
  • FDOMessages.dll
  • FDOSpatial.dll
  • OSGeo.FDO.dll
  • OSGeo.FDO.Common.dll
  • OSGeo.FDO.Geometry.dll
  • Xalan-C_1_11.dll
  • XalanMessages_1_11.dll
  • xerces-c_3_1.dll

Direkt arbeitet man in .NET nur mit den Assemblies im Namespace OSGeo, die dem entsprechend auch im Projekt referenziert werden müssen. Die anderen oben aufgeführten DLLs werden von diesen Assemblies jedoch auch im selben Verzeichnis benötigt. Darüber hinaus existieren noch mehr Assemblies der Provider für die FDO, die jedoch nicht im Ausgabeverzeichnis sein müssen. In der Datei providers.xml werden die Datenprovider und der Pfad zu diesen DLLs und den weiteren abhängigen DLLs angegeben. Dieses XML sollte den Bedürfnissen (v.a. beim Tag LibraryPath) entsprechend angepasst werden und auch im Ausgabepfad der Anwendung gespeichert sein.

Eine mögliche Konfiguration ist dabei, dass die Core-Assemblies (s.o.) und die Datei providers.xml im Ausgabeverzeichnis sind und die eigentlichen Datenprovider mit deren Abhängigkeiten in einem Unterverzeichnis des Ausgabepfads. Im Beispiel oben habe ich einen Unterordner FDO_3.6.0 angelegt.

Außerdem sollte man gleich alle Namespaces von FDO per using-Direktive ins eigene Projekt einbinden:

//FDO
using OSGeo.FDO;
using OSGeo.FDO.Connections;
using OSGeo.FDO.Common;
using OSGeo.FDO.Schema;
using OSGeo.FDO.Commands;
using OSGeo.FDO.Geometry;
using OSGeo.FDO.ClientServices;
using OSGeo.FDO.Commands.DataStore;
using OSGeo.FDO.Commands.Schema;
using OSGeo.FDO.Expression;
using OSGeo.FDO.Commands.Feature;
using OSGeo.FDO.Filter;

Weiter gehts im nächsten Teil..

6 Leute mögen diesen Artikel.

März 13th, 2011

G#.Viewer – Kartenfunktionen (4)

Im letzten Teil der Serie beschäftigten wir uns mit dem Laden und Anzeigen von ESRI Shapefiles im G#.Viewer. Nun werden wir die einfach zugänglichen Funktionen zum Bedienen der Kartenansicht in unseren GIS-Viewer einbauen.

Im Namespace SharpMap.Forms.MapImage befindet sich ein Enumerator, der die verfügbaren Kartenfunktionalitäten auflistet.

Die wichtigsten sind:

  • Pan (Verschieben)
  • ZoomIn
  • ZoomOut
  • Query

Da diese Tools ja bereits „out-of-the-box“ mitgeliefert werden, brauchen wir nichts anderes tun, als an jedem LabelButton in unserem ToolStrip ein Click-Event zu erzeugen und den Wechsel des aktuellen Werkzeugs zu vollziehen. Was sich etwas umständlich anhört ist mit C# und SharpDevelop ganz simpel umzusetzen:

weiterlesen »

1 andere Person mag diesen Artikel.

Februar 16th, 2011

G#.Viewer – Laden und Anzeigen eines Shapefiles (3)

Im Dritten Teil dieses Artikel über den G#.Viewer möchte ich auf das Laden und Anzeigen eines Shapefiles mit Sharpmap in C# eingehen. Nun werden wir das erste Mal C# Code nicht nur über den Designer der IDE erstellen, sondern auch selbst Hand anlegen.

Zunächst einmal definiere ich einige Felder am Anfang der Klasse, die im Folgenden verwendet werden:

public partial class frmMain : Form
{
	// Map
	private SharpMap.Map map;
	private bool isMapOpen = false;
	private SharpMap.Layers.VectorLayer vLayer;
	public int width;
	public int height;

	//TreeView
	private TreeNode rootNode;

	public frmMain()
	{
		InitializeComponent();

		//Größe der Karte = Größe des TabPages tabMap
		width = tabMap.Width;
		height = tabMap.Height;
	}

weiterlesen »

Be the first to like.

Februar 16th, 2011

G#.Viewer – GUI – Layerübersicht und OpenFileDialog (2)

Erweiterung der GUI

Im letzten Artikel habe ich einen ersten Entwurf der GUI für den G#.Viewer mit der IDE SharpDevelop angefertigt. Diesen Entwurf möchte ich nun etwas erweitern. Was noch gefehlt hat war eine Übersicht der Layer, die in den Viewer geladen werden. In dieser Übersicht sollen die Layer verschiebbar sein – also die Reihenfolge geändert werden können, in der sie im Kartenbild überlagert werden und die Sichtbarkeit verändert werden können. Außerdem braucht man die eine Layerübersicht wenn man Funktionen auf einzelne Layer beziehen möchte – beispielsweise das Zoomen auf eine bestimmte Ebene oder etwa auch das Entfernen einer Ebene aus dem Viewer.

Deshalb holen wir uns jetzt noch ein TreeView-Control hinzu. Mit Hilfe eines zusätzlichen SplitContainers bauen wir den TreeView in unseren GIS-Viewer ein. Anschließend müssen die Eigenschaften bearbeitet werden, so dass aus dem TreeView auch eine Layerübersicht werden kann. Die weniger interessanten Eigenschaften des TreeViews habe ich ausgeblendet. Ansonsten sollten die fett markierten Eigenschaften wie folgt aussehen:

weiterlesen »

Be the first to like.

Dezember 29th, 2010

G#.Viewer – GUI (1)

G#.Viewer – ein eigener GIS-Datenviewer in C#

Mit der Open Source .NET-Bibliothek SharpMap kann man eigene einfache GIS-Anwendungen mit C# relativ schnell entwickeln. In den nun folgenden Artikeln werde ich die Entwicklung eines einfachen GIS-Viewers beschreiben. Das Projekt heisst „G#.Viewer“ und soll das können: Geodaten darstellen und abfragen.

Als Entwicklungsumgebung (IDE) empfehle ich das Werkzeug SharpDevelop. Auch dies ist Open Source und ist eine profunde Alternative zu Microsoft Visual Studio C# Express Edition oder zur Vollversion Microsoft Visual Studio. In diesen Artikeln werde ich die portable Version 3.2 von SharpDevelop nutzen.

Los geht’s

Zunächst sollten wir uns mit der aktuellen Version der SharpMap-Bibliothek eindecken. Unter „Downloads“ auf der Seite von SharpMap wird man schnell fündig. Am besten läd man sich den aktuellen Quellcode (ich benutze im folgenden das ChangeSet 81947). Es werden zwar auch bereits kompilierte DLLs zum Download angeboten – dabei fehlen jedoch einige nette Sachen wie der Namespace SharpMap.UI, in dem alle nützlichen GUI-Kartenwerkzeuge für Windows Forms zu finden sind (nämlich das MapImage-Control).

Deshalb ist das Motto: selber kompilieren! Ist ja auch mit SharpDevelop ohne weiteres möglich..

weiterlesen »

1 andere Person mag diesen Artikel.

Dezember 11th, 2010

SharpMap – Open Source GIS mit C#

Ich wollte schon immer eigene Applikationen schreiben. Dabei habe ich mir an verschiedenen Programmiersprachen die Zähne ausgebissen und habe es immer wieder verworfen. Einen guten Einstieg in die Programmierung fand ich dann durch Python: es war leicht zu erlernen und man konnte schnell etwas interessantes entwickeln.

Nachdem ich mit Python und Geoprocessing etwas Erfahrung gesammelt hatte und ein Praktikum bei ESRI Deutschland absolvierte, bei dem ich unter anderem ein bisschen in die Sprache C# schnuppern durfte, wollte ich C# weiter vertiefen.  Vor allem aber wollte ich eigentlich C# mit GIS verbinden. Python spielt sich ja meist auf der Konsole ab. Was ich wollte, war eine Benutzeroberfläche! Einen eigenen GIS-Datenviewer in C# – das war mein Ziel. Nur gab es da ein Problem: .NET-Bibliotheken oder Engines für GIS-Applikationen waren nur kommerziell verfügbar. Ich wollte aber doch nur spielen und lernen – also was tun?

Nach einer längeren Recherche fand ich dann SharpMap. SharpMap ist eine Mapping-Bibilothek für C#. Sie steht unter der LGPL-Lizenz und ist damit für kommerzielle, closed-source Produkte ohne Schwierigkeiten verwendbar – was manch einer als einen Nachteil sehen möchte. (Meine Meinung dazu ist eher pragmatisch: mit der LGPL kann man sowohl Open Source bleiben, als auch Closed Source. Das ist eine Möglichkeit mehr.)

Mit SharpMap ist es relativ leicht, eigene GIS-Applikationen (oder zumindest GIS-Datenviewer) in C# zu erstellen.  Und genau das werde ich in den nächsten Beiträgen etwas vorstellen.

2 Leute mögen diesen Artikel.

Tags: , , , ,