This project is read-only.

COMMON TASKS FOR SHAPEFILES AND OTHER VECTOR FORMATS.

The detailed API reference on classes, properties and methods used in the samples can be found here.

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.

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 an instance of Shapefile class

2. Adding layers to the map and retrieving them back:

For ESRI shapefiles:

// -1 will be returned on failure
int layerHandle = axMap1.AddLayer(sf, true);

// somewhere else in code to get this layer
var sf = axMap1.get_Shapefile(layerHandle);

For other vector formats:

// -1 will be returned on failure
int layerHandle = axMap1.AddLayer(ogrLayer, true);

// somewhere else in code to get this layer
var ogrLayer = axMap1.get_OgrLayer(layerHandle);

// if you only need an underlying buffer with data
var sf = 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.

// In .NET it's not possible to assign System.Drawing.Color for MapWinGIS properties directly,
// so we are using Utils class;  other languages may set the colors directly.
var utils = new Utils();

var options = sf.DefaultDrawingOptions;
options.FillColor = utils.ColorByName(tkMapColor.Blue);
options.LineColor = utils.ColorByName(tkMapColor.Gray);
options.LineWidth = 2.0f;
// etc.

axMap1.Redraw();     // in case the layer was already added to the map

4. Icons / markers for point layers.

Icons for point layers are set using this same DefaultDrawingOptions property demonstrated above. However make sure to change the value of ShapeDrawingOptions.PointType property. A point can be represented by:
  • vector marker (ptSymbolStandard);
  • bitmap (ptSymbolPicture);
  • font character (ptSymbolFontCharacter) .
Only one of the alternatives can be used at a time.

Built-in vector markers:

// a triangle; in fact first 2 values are default, so they can be skipped
var 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);

axMap1.Redraw();     // in case the layer was already added to the map

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;

axMap1.Redraw();     // in case the layer was already added to the map

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.

5. Generation of labels.

Typically labels are generated based on value from the 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] 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

axMap1.Redraw();     // in case the layer was already added to the map

By default MapWinGIS prevents labels from overlapping each other. This behavior is controlled by Labels.AvoidCollisions property.

If you are running the code after the layer was added to the map, don't forget to call Map.Redraw() method to see the changes.

6. 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. New category can be added with ShapefileCategories.Add method. Its drawing options will automatically be copied from Shapefile.DefaultDrawingOptions, i.e. if you set default outline width equal to 2, the new category will also have such outline width.

// let's create a category with red fill color and green outline
string 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);

7. Assigning visualization category to shapes.

The category we created in previous section isn't assigned to any shapes, i.e. it's basically inactive. There are 2 ways to assign category to shapes:
  • manually setting Shaefile.ShapeCategory property;
  • automatically using expressions;
Here are various ways to do it manually, which is generally more flexible approach.

// let's assume that we want to assign our category to a 10-th shape
int shapeIndex = 10;

//  any of 3 overloads of Shapefile.ShapeCategory can be used
int 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
            
// the same for each shape with an odd index
for (int i = 0; i < sf.NumShapes; i++) 
{
    if (i % 2 != 0) {
        sf.set_ShapeCategory(i, categoryIndex);
    }
}

// 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);
    }
}

axMap1.Redraw();     // in case the layer was already added to the map

Now the second approach based on expressions (under the hood it uses sf.set_ShapeCategory, just like the previous one).

// field names should be in square brackets, string constants should be quotes
ct.Expression = "[Type] = \"hot\"";
sf.Categories.ApplyExpression(categoryIndex);    // sets Shapefile.ShapeCategory property

8. 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 of what is meant you can check maps on this wiki page for example. To generate something similar in MapWinGIS the following code can be used.

// check if income field is there
int 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 them
if (!sf.Categories.Generate(fieldIndex, tkClassificationType.ctNaturalBreaks, 8))
{
    Debug.Print("Failed to generate categories.");
    return;
}

// let's check that something was actually generated
for (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);

// finally, apply categories to shapes
sf.Categories.ApplyExpressions();

axMap1.Redraw();     // in case the layer was already added to the map

9. 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 WGS84 coordinate system (i.e. 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 it.
var 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 case
var 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 projection
    double projX = 0.0, projY = 0.0;
    axMap1.DegreesToProj(place.Lng, place.Lat, ref projX, ref projY);

    // create shapes for each location
    var shape = new Shape();
    shape.Create(ShpfileType.SHP_POINT);
    shape.AddPoint(projX, projY);

    // add it to shapefile along with name
    int 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 failure
int 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).

10. Calculating area of polygons.

Depending on the presence of information about coordinate system / projection for the map the area of polygons can be calculated:
  • precisely using the shape of Earth (Map.GeodesicArea(shape));
  • in projected coordinates using Euclidean geometry (Shape.Area).
The latter option may give satisfactory results for small objects. However for some projections, like commonly used Mercator on sphere (aka Google Mercator), it may be very inaccurate. Therefore much better approach is to use precise geodesic calculations whenever possible.

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 other. Needless to say that "square degrees" is rather a 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 first
if (sf.ShapefileType2D != ShpfileType.SHP_POLYGON)
{
    MessageBox.Show("Area can be calculated for polygon shapefiles only.");
    return;
}

// DBF table must be in edit mode
if ( !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 possible
bool ellipsoid = axMap1.Measuring.IsUsingEllipsoid;

// create filed to store the results
string fieldName = ellipsoid ? "GeoArea" : "Area";
int fieldIndex = sf.EditAddField(fieldName, FieldType.DOUBLE_FIELD, 6, 18);

// loop through shapes, calculate area and write it to the table
for (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 file
if (!sf.StopEditingTable()) 
{
     MessageBox.Show("Failed to save calculated area to the datasource.");
}

Last edited Jan 6, 2015 at 3:42 PM by sleschinski, version 17

Comments

No comments yet.