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; }
Außerdem wird die Größe der Karte anhand der TabPage „tabMap“ beim Initialisieren der Klasse „frmMain“ erstmalig festgelegt.
Um ein Shapefile laden zu können, definiere ich eine Funktion, die eine Instanz der Klasse „OpenFileDialog“ entgegen nimmt.
private SharpMap.Layers.VectorLayer loadShapefile(OpenFileDialog _openFileDialog) { //--> Definieren des Layernamens und des Pfads string dataPath = String.Empty; string dataName = String.Empty; dataPath = _openFileDialog.FileName; int indexOfPoint = _openFileDialog.FileName.IndexOf('.'); dataName = _openFileDialog.FileName.Remove(indexOfPoint, 4); int indexOfShpName = dataName.LastIndexOf(@"\"); dataName = dataName.Substring(indexOfShpName); dataName = dataName.Substring(1); vLayer = new SharpMap.Layers.VectorLayer(dataName); //Mit dem File-basierten Spatial Index (Quadtree) versuchen try { vLayer.DataSource = new SharpMap.Data.Providers.ShapeFile(dataPath, true); vLayer.DataSource.Open(); } catch (Exception ex) { MessageBox.Show("Fehler beim Laden des Layers " + vLayer.LayerName + " aufgetreten." + Environment.NewLine + "Die Meldung lautet: " + ex.Message + Environment.NewLine + "Stack Trace: " + Environment.NewLine + ex.StackTrace.ToString(), "Fehler!", MessageBoxButtons.OK, MessageBoxIcon.Exclamation); } return vLayer; }
Diese wird aus dem Ergebnis der Benutzerauswahl des OpenFileDialogs einen VectorLayer erzeugen und zurückgeben. Zudem wird der Layername aus dem Shapefile gelesen und aus Performance-Gründen zunächst versucht, das Shapefile mit filebasiertem räumlichen Index zu laden:
vLayer.DataSource = new SharpMap.Data.Providers.ShapeFile(dataPath, true);
Weiterhin brauche ich diese Funktionen:
private SharpMap.Map createMap(int width, int height) { //--> Initialisieren der Karte map = new Map(new Size(width, height)); map.BackColor = Color.AliceBlue; return map; } private void addLayer(SharpMap.Layers.Layer _layer) { mapImage1.Map.Layers.Add(_layer); //Layernamen zum TreeView hinzufügen Boolean isRemoved = false; populateTreeView(_layer.LayerName, isRemoved); //Zoom to Layer _layer.Enabled = true; mapImage1.Map.ZoomToBox(_layer.Envelope.Grow(0.5)); } private void paintLayer(SharpMap.Layers.VectorLayer vLayer) { //Zufällige Farbe Random random = new Random(); int rot = random.Next(0, 255); int gruen = random.Next(0, 255); int blau = random.Next(0, 255); string geometrytype = getGeometryType(vLayer); switch (geometrytype) { case "POINT": //bm.SetPixel(2, 2, Color.FromArgb(rot, gruen, blau)); //vLayer.Style.Symbol = new Bitmap(bm); //vLayer.Style.SymbolScale = 5; break; case "LINE": vLayer.Style.Line = new Pen(Color.FromArgb(rot, gruen, blau)); break; case "LINESTRING": vLayer.Style.Line = new Pen(Color.FromArgb(rot, gruen, blau)); break; case "MULTILINESTRING": vLayer.Style.Line = new Pen(Color.FromArgb(rot, gruen, blau)); break; case "POLYGON": vLayer.Style.Fill = new SolidBrush(Color.FromArgb(rot, gruen, blau)); vLayer.Style.EnableOutline = true; vLayer.Style.Outline = new Pen(Color.Black); break; case "MULTIPOLYGON": vLayer.Style.Fill = new SolidBrush(Color.FromArgb(rot, gruen, blau)); vLayer.Style.EnableOutline = true; vLayer.Style.Outline = new Pen(Color.Black); break; } }
Dabei wird zunächst ein Map-Objekt erzeugt, das die angegebe Größe berücksichtigt. Daraufhin definiere ich eine Funktion, die für das Hinzufügen eines Layers zuständig ist und eine Funktion, die einen Layer tatsächlich auch in der Kartenansicht zeichnet. In der Funktion paintLayer
werden dem Layer Zufallsfarben zugewiesen. Je nach Geometrietyp werden dafür unterschiedliche Styles benutzt. Beispielsweise kann man eine Fläche füllen – eine Linie nicht. Dies macht die Unterscheidung nach Geometrietyp notwendig.
Die entsprechende Funktion zur Ermittlung des Geometrietyps wird folgendermaßen definiert:
private string getGeometryType(SharpMap.Layers.VectorLayer vLayer) { try { string s = vLayer.DataSource.GetFeature(0).Geometry.AsText().ToString(); int i = s.IndexOf(' '); string geometryType = s.Remove(i, s.Length - i); return geometryType; } catch { return String.Empty; } }
Hier wird also das erste Element des Layers eingelesen und in Well-Known-Text (WKT) umgewandelt. Dieser Text beinhaltet als ersten Eintrag immer den Geometrietyp, welchen die Funktion zurückgibt.
Ausgelöst werden soll der Ablauf createMap
-> loadShapefile
-> addLayer
-> paintLayer
durch einen Klick auf den Button „Laden“ aus dem ToolStrip und der Auswahl des entsprechenden Shapefiles. Also erzeugen wir mit einem Doppelklick in der Desgin-Ansicht auf den LabelButton „Laden“ einen ClickEvent mit den folgenden Inhalten:
private void toolStripbtnLoad_Click(object sender, EventArgs e) { // Lade Shapefiles DialogResult result = openFileDialog1.ShowDialog(); // Abfangen des Abbruchs der Benutzereingabe beim Lade-Dialog if (result != DialogResult.Cancel) { mapImage1.Cursor = Cursors.WaitCursor; // Erzeuge Karte if (isMapOpen == false) { map = createMap(width, height); isMapOpen = true; } try { VectorLayer vLayer = loadShapefile(openFileDialog1); // Wenn das Oeffnen der Datenquelle funktioniert hat.. if (vLayer.DataSource != null) { if (vLayer.DataSource.IsOpen) { paintLayer(vLayer); addLayer(vLayer); } } } catch (SharpMap.Layers.DuplicateLayerException ex) { MessageBox.Show("Der Layer " + ex.DuplicateLayerName + " ist bereits geladen.", "Fehler!", MessageBoxButtons.OK, MessageBoxIcon.Exclamation); } mapImage1.Cursor = Cursors.Default; } }
Wenn also alle Kontrollen durchlaufen sind, werden die Funktionen paintLayer
und addLayer
aufgerufen. Außerdem fange ich mit einem try/catch-Block eine Ausnahme ab, die hervorgerufen wird, sollte ein Layer doppelt geladen werden. Ein Layerbezeichner muss immer eindeutig innerhalb der LayerCollection des SharpMap.Map-Objekts sein.
Ein Letztes: Wir müssen den Layer natürlich auch der Layerübersicht hinzufügen. In der Funktion addLayer
wird die Funktion populateTreeView
aufgerufen. Die Definition lautet:
private void populateTreeView(string layername, Boolean _isRemoved) { rootNode = treeView1.Nodes[0]; int insertIdx = 0; if (_isRemoved) { rootNode.Nodes.RemoveByKey(layername); treeView1.ExpandAll(); } else { // "Insert" statt "Add" nutzen, weil ein neuer Layer immer als erstes // Element nach dem Root-Knoten eingefügt werden soll rootNode.Nodes.Insert(insertIdx, layername, layername); treeView1.ExpandAll(); } }
Diese Funktion hat zwei Aufgaben: entweder entfernt sie den Layer, der übergeben wurde aus dem TreeView, oder sie fügt den Layer dort hinzu.
Mit diesen Code-Schnipseln kann man im G#.Viewer nun schon Shapefiles laden und mit zufälliger Farbe einfärben lassen.