COMMON TASKS FOR SHAPEFILES AND OTHER VECTOR FORMATS.
Most of the recipes listed here can also be used for vector formats other than ESRI shapefile, including spatial databases. These formats can be opened with OgrLayer class while their data is stored in an instance of Shapefile class, which can be accessed using OgrLayer.GetBuffer property. See more information on opening different formats here.The detailed API reference on classes, properties, methods used in the samples can be found here.
All the sample code is written in C#. Most of the samples were not executed / tested, so there may be some small slips which hopefully can be dealt with easy enough using IntelliSense and API reference.
1. Opening of shapefiles and other vector formats.
For ESRI shapefiles:var sf = new Shapefile(); if (!sf.Open(@"d:\my_data.shp")) { Debug.Print("Failed to open: " + sf.get_ErrorMsg(sf.LastErrorCode)); return; }
For other vector formats:
var ogrLayer = new OgrLayer(); if (!ogrLayer .Open(@"d:\my_data.kml")) { Debug.Print("Failed to open: " + ogrLayer.get_ErrorMsg(ogrLayer.LastErrorCode)); return; } var sf = ogrLayer.GetBuffer(); // data is stored in instance an of Shapefile class
2. Adding layers to the map and retrieving them back:
For ESRI shapefiles:// -1 will be returned on failureint layerHandle = axMap1.AddLayer(sf, true); // somewhere else in code to get this layervar sf = axMap1.get_Shapefile(layerHandle);
For other vector formats:
// -1 will be returned on failureint layerHandle = axMap1.AddLayer(ogrLayer, true); // somewhere else in code to get this layervar ogrLayer2 = axMap1.get_OgrLayer(layerHandle); // or if you only need an underlying buffer with datavar sf2 = axMap1.get_Shapefile(layerHandle); // calls ogrLayer.GetBuffer under the hood
3. Changing visualization: colors, width of outline, etc.
Shapefile visualization options are stored in the instance of ShapeDrawingOptions class. It can be accessed via Shapefile.DefaultDrawingOptions property.var utils = new Utils(); sf.DefaultDrawingOptions.FillColor = utils.ColorByName(tkMapColor.Blue); sf.DefaultDrawingOptions.LineColor = utils.ColorByName(tkMapColor.Gray); sf.DefaultDrawingOptions.LineWidth = 2.0f;
In .NET it's not possible to assign System.Drawing.Color for these properties so we are using Utils class, other languages may set the colors directly.
3. Icons / markers for point layers.
Make sure to change the value of ShapeDrawingOptions.PointType property. A point can be represented by vector marker (ptSymbolStandard), bitmap (ptSymbolPicture) or font character (ptSymbolFontCharacter) but only one of those at a time.Built-in vector markers:
// a triangle; in fact first 2 values are default, so they can be skippedvar options = sf.DefaultDrawingOptions; options.PointType = tkPointSymbolType.ptSymbolStandard; options.PointShape = tkPointShapeType.ptShapeRegular; options.PointSidesCount = 3; // there is also shortcut to set vector markers which will set several properties at once// for example the previous 3 lines can be substituted with options.SetDefaultPointSymbol(tkDefaultPointSymbol.dpsTriangleUp);
Custom bitmaps as markers:
var icon = new Image(); if (!icon.Open(@"d:\icon.png")) { MessageBox.Show("Failed to open icon: " + icon.get_ErrorMsg(icon.LastErrorCode)); return; } options.PointType = tkPointSymbolType.ptSymbolPicture; options.Picture = icon;
By default MapWinGIS prevents icons / markers to overlap one another. This behavior is controlled by Shapefile.CollisionMode.
See example on how to assign different icons for points here. Shapefile categories which are needed to do it are discussed later in this document.
4. Generation of labels.
Typically labels are generated based on value from attribute table. This can be done either in fully automatic manner using expressions or in a more manual way which can give more flexibility.// labels are taken from type [Type] field; field names must be in square brackets// lpCentroid positioning method can be used for polygons and points// (in fact point layers ignore this argument so any value can be passed) sf.Labels.Generate("[Type]", tkLabelPositioning.lpCentroid, true); // the same method but with more complex expression; // pay attention that string constants must be quoted and a plus operator is used for concatenation sf.Labels.FloatNumberFormat = "%.3f"; // three decimal places sf.Labels.Generate("[Area]/10000 + \" ha\"", tkLabelPositioning.lpCentroid, true);
And here is the first example but done manually:
sf.Labels.Clear(); int fieldIndex = sf.FieldIndexByName["Type"]; if (fieldIndex != -1) { for (int i = 0; i < sf.NumShapes; i++) { var text = sf.CellValue[fieldIndex, i].ToString(); var pnt = sf.Shape[i].Centroid; sf.Labels.AddLabel(text, pnt.x, pnt.y); } }
Finally let's set some visualization options.
var utils = new Utils(); sf.Labels.FrameBackColor = utils.ColorByName(tkMapColor.Orange); sf.Labels.FontSize = 12; sf.Labels.OffsetX = 30; // perhaps we want them to be shifted to the right a bit
By default MapWinGIS prevents labels from overlapping each other. This behavior is controlled by Labels.AvoidCollisions property.
5. Adding a visualization category.
To assign different colors to certain shapes within a shapefile you should use categories. By category we mean "visualization category", i.e. certain set of visualization options that are defined independent of any shapes. Categories can be managed using Shapefile.Categories property (returns instance of ShapefileCategories class). Each category is represented by instance of ShapefileCategory class and its drawing options can accessed with ShapefileCategory.DrawingOptions property.New category can be added with ShapefileCategories.Add method. Its drawing options will automatically be copied from Shapefile.DefaultDrawingOptions, i.e. if you set width to 2 previously, the new category will also have such width of outline.
// let's create a category with red fill color and green outlinestring categoryName = "red_shapes"; // any name can be used ShapefileCategory ct = sf.Categories.Add(categoryName); ct.DrawingOptions.FillColor = utils.ColorByName(tkMapColor.Red); ct.DrawingOptions.LineColor = utils.ColorByName(tkMapColor.Green);
6. Different ways to assign visualization category to shapes.
The category we created in previous section isn't assigned to any shapes, i.e. it's basically inactive. Here are various ways to change this:// let's assume that we want to assign our category to a 10-th shapeint shapeIndex = 10; // any of 3 overloads of Shapefile.ShapeCategoryint categoryIndex = sf.Categories.CategoryIndex[ct]; sf.set_ShapeCategory(shapeIndex, categoryIndex); // either (the fastest) sf.set_ShapeCategory2(shapeIndex, categoryName); // or sf.set_ShapeCategory3(shapeIndex, ct); // or// let's do the same in a cycle for shapes from 5th to 14thfor (int i = 5; i < 14; i++) { sf.set_ShapeCategory(i, categoryIndex); } // let's do the same based on attributes // the category will be assigned to shapes with Type field having value "hot"int fieldIndex = sf.get_FieldIndexByName("Type"); if (fieldIndex != -1) { for (int i = 0; i < sf.NumShapes; i++) { var value = sf.get_CellValue(fieldIndex, i).ToString(); if (value == "hot") sf.set_ShapeCategory(i, categoryIndex); } } // the same using expressions (under the hood this calls sf.set_ShapeCategory,// just like the previous one ct.Expression = "[Type] = \"hot\""; sf.Categories.ApplyExpression(categoryIndex);
7. Automatic generation of categories for a given field.
It's often needed to set colors for shapes based on an attribute to see how certain characteristic is distributed spatially. To get an idea what is meant you can maps on this wiki page for example. To generate something similar in MapWinGIS the following code can be used.// check if income field is thereint fieldIndex = sf.FieldIndexByName["Income"]; if (fieldIndex == -1) { Debug.Print("No Income field found."); return; } // this will add 8 ShapefileCategory objects to the Categories collection// but those categories won't be assigned to any shapes// plus no specific colors will be set for themif (!sf.Categories.Generate(fieldIndex, tkClassificationType.ctNaturalBreaks, 8)) { Debug.Print("Failed to generate categories."); return; } // let's check that something was generatedfor (int i = 0; i < sf.Categories.Count; i++) { Debug.Print("Category: " + sf.Categories.Item[i].Expression); } // now assign color gradient (more than 2 colors can be used as well)var scheme = new ColorScheme(); scheme.SetColors2(tkMapColor.Yellow, tkMapColor.Green); sf.Categories.ApplyColorScheme(tkColorSchemeType.ctSchemeGraduated, scheme); // apply categories to the shapes sf.Categories.ApplyExpressions();
8. Adding markers to the map from latitude / longitude pairs of values.
Let's assume that we have a list of locations with coordinates in latitude and longitude and a certain name for each location. The goal is to display this list on the map but not in WGS coordinate system (coordinates in degrees) but in web Mercator (aka Google Meractor; coordinates in meters), so that background tiles can be displayed without distortions.// here are our location, they can be obtained from any source; // we use array of anonymous objects for clarity;// if the language you use doesn't support such constructs nevermind;// the sample doesn't depend on itvar places = new[] { new {Lat = 42.3, Lng = 18.1, Name = "Shop"}, new {Lat = 20.5, Lng = 38.7, Name = "Pub"}, new {Lat = 59.0, Lng = 5.3, Name = "Hotel"} }; // it's default setting but just in casevar gs = new GlobalSettings() {AllowLayersWithoutProjections = true}; axMap1.Projection = tkMapProjection.PROJECTION_GOOGLE_MERCATOR; axMap1.TileProvider = tkTileProvider.OpenStreetMap; var sf = new Shapefile(); // use empty string to create in-memory shapefile sf.CreateNewWithShapeID("", ShpfileType.SHP_POINT); int fieldIndex = sf.EditAddField("Name", FieldType.STRING_FIELD, 0, 20); foreach (var place in places) { // convert our degrees to meters in map projectiondouble projX = 0.0, projY = 0.0; axMap1.DegreesToProj(place.Lng, place.Lat, ref projX, ref projY); // create shapes for each locationvar shape = new Shape(); shape.Create(ShpfileType.SHP_POINT); shape.AddPoint(projX, projY); // add it to shapefile along with nameint shapeIndex = sf.EditAddShape(shape); sf.EditCellValue(fieldIndex, shapeIndex, place.Name); } // check that everything went smooth Debug.Print("Number of points added: " + sf.NumShapes); // let's display labels sf.Labels.Generate("[Name]", tkLabelPositioning.lpCenter, true); // -1 will be returned on failureint layerHandle = axMap1.AddLayer(sf, true); // needed in case it's not the first layer axMap1.ZoomToLayer(layerHandle);
It was discussed above how to set custom markers / icons for points. Different icons for each type of location can be assigned as well using categories (also discussed above).
9. Calculating area of polygons.
Depending on the presence of information about coordinate system /projection for the map the area for polygons can be calculated:- precisely using the shape of Earth (Map.GeodesicArea(shape));
- in projected coordinates using Euclidean geometry (Shape.Area).
For geodesic calculations results will be returned in square meters. For planar calculations - in square map units, whatever they are, meters, decimal degrees or any others. Needless to say that "square degrees" is rather dubious unit of measuring.
This examples demonstrates how to calculate area for polygons and write it to the attribute table of shapefile. If possible geodesic area is calculated.
// let's check the type firstif (sf.ShapefileType2D != ShpfileType.SHP_POLYGON) { MessageBox.Show("Area can be calculated for polygon shapefiles only."); return; } // DBF table must be in edit modeif ( !sf.StartEditingTable()) { MessageBox.Show("Failed to start editing mode for table."); return; } // this property can be used to determine whether transformation to WGS84// coordinate system is possiblebool ellipsoid = axMap1.Measuring.IsUsingEllipsoid; // create filed to store the resultsstring fieldName = ellipsoid ? "GeoArea" : "Area"; int fieldIndex = sf.EditAddField(fieldName, FieldType.DOUBLE_FIELD, 6, 18); // loop through shapes, calculate area and write it to the tablefor (int i = 0; i < sf.NumShapes; i++) { double area = ellipsoid ? axMap1.GeodesicArea(sf.Shape[i]) : sf.Shape[i].Area; sf.EditCellValue(fieldIndex, i, area); } // save the changes to the fileif (!sf.StopEditingTable()) { MessageBox.Show("Failed to save calculated area to the datasource."); }