Skip to content

ShapeFileFeatureLayer Guide

Namespace: ThinkGeo.Core
Assembly: ThinkGeo.Core
Availability: All ThinkGeo products (WPF, WinForms, Blazor, MAUI, Web API, etc.)


Overview

ShapeFileFeatureLayer is one of the most commonly used layer types in ThinkGeo. It provides a direct interface for loading, rendering, querying, and editing Esri Shapefile data (.shp format) on a ThinkGeo map. It inherits from FeatureLayer, which in turn inherits from Layer, and internally delegates all data access to a ShapeFileFeatureSource.

Because it lives in the ThinkGeo.Core namespace, ShapeFileFeatureLayer is shared across all ThinkGeo UI products. The same class, the same API, and the same code patterns apply whether you are building a WPF desktop application, a WinForms app, a Blazor server app, a MAUI mobile app, or a headless server process.

What Is a Shapefile?

A Shapefile is a multi-file geospatial vector data format developed by Esri. At minimum, three files are required:

File Purpose
.shp Geometry (points, lines, polygons)
.shx Shape index for fast positional access
.dbf Attribute data in dBASE format

Optional but commonly present files:

File Purpose
.prj Coordinate reference system (WKT projection)
.idx ThinkGeo spatial index for performance
.ids ThinkGeo spatial index (companion to .idx)

Class Hierarchy

Layer
  └── FeatureLayer
        └── ShapeFileFeatureLayer

ShapeFileFeatureLayer exposes all of the base FeatureLayer and Layer functionality, including:

  • ZoomLevelSet — controls styles at each zoom scale
  • QueryTools — spatial and attribute queries
  • EditTools — feature add/update/delete (read-write mode only)
  • FeatureSource — direct access to the underlying ShapeFileFeatureSource
  • IsOpen, Open(), Close() — lifecycle management

Constructors

// Most common: load a shapefile by path
var layer = new ShapeFileFeatureLayer(@"./Data/Countries.shp");

// Specify both shapefile path and index path explicitly
var layer = new ShapeFileFeatureLayer(
    @"C:\MapData\Buildings.shp",
    @"C:\MapData\Buildings.idx");

Note: The ShapeFileReadWriteMode enum from the legacy ThinkGeo.MapSuite API does not exist in ThinkGeo.Core. Simply construct the layer with the path — no mode flag is needed to use EditTools.


Basic Usage Pattern

1. Minimal Setup (Read-Only Display)

using ThinkGeo.Core;

// Create the layer
var countriesLayer = new ShapeFileFeatureLayer(@"./Data/Countries.shp");

// Set a style (apply from ZoomLevel01 all the way to Level20)
// AreaStyle constructor takes (GeoPen outlinePen, GeoBrush fillBrush)
countriesLayer.ZoomLevelSet.ZoomLevel01.DefaultAreaStyle =
    AreaStyle.CreateSimpleAreaStyle(GeoColors.LightGreen, GeoColors.Gray);
countriesLayer.ZoomLevelSet.ZoomLevel01.ApplyUntilZoomLevel = ApplyUntilZoomLevel.Level20;

// Add to a LayerOverlay, then add the overlay to the map
var overlay = new LayerOverlay();
overlay.Layers.Add("countries", countriesLayer);
mapView.Overlays.Add(overlay);

2. With Projection Reprojection

Most shapefiles are in a local or geographic coordinate system (e.g., EPSG:4326 WGS84 or a state plane). ThinkGeo maps typically render in Spherical Mercator (EPSG:3857). Use ProjectionConverter to reproject on-the-fly:

var hotelsLayer = new ShapeFileFeatureLayer(@"./Data/Hotels.shp");

// Reproject from Texas State Plane (EPSG:2276) to Web Mercator (EPSG:3857)
hotelsLayer.FeatureSource.ProjectionConverter = new ProjectionConverter(2276, 3857);

hotelsLayer.ZoomLevelSet.ZoomLevel01.DefaultPointStyle =
    new PointStyle(PointSymbolType.Circle, 8, GeoBrushes.DarkRed, new GeoPen(GeoBrushes.White, 2));
hotelsLayer.ZoomLevelSet.ZoomLevel01.ApplyUntilZoomLevel = ApplyUntilZoomLevel.Level20;

Tip: Assign ProjectionConverter before the layer is opened or added to an overlay — it is read at Open() time.


Styling

Styles in ThinkGeo are assigned through the ZoomLevelSet property. Each zoom level (ZoomLevel01 through ZoomLevel20) can carry its own style. The ApplyUntilZoomLevel property cascades a style across a range of zoom levels.

AreaStyle (Polygons)

// Fill-only (no outline):
parksLayer.ZoomLevelSet.ZoomLevel01.DefaultAreaStyle =
    new AreaStyle(new GeoSolidBrush(GeoColors.PastelGreen));

// Fill + outline: constructor is AreaStyle(GeoPen outlinePen, GeoBrush fillBrush)
parksLayer.ZoomLevelSet.ZoomLevel01.DefaultAreaStyle =
    new AreaStyle(new GeoPen(GeoColors.DarkGreen, 1), new GeoSolidBrush(GeoColors.PastelGreen));

// Or use the static factory helper:
parksLayer.ZoomLevelSet.ZoomLevel01.DefaultAreaStyle =
    AreaStyle.CreateSimpleAreaStyle(GeoColors.PastelGreen, GeoColors.DarkGreen);

parksLayer.ZoomLevelSet.ZoomLevel01.ApplyUntilZoomLevel = ApplyUntilZoomLevel.Level20;

LineStyle (Lines / Roads)

streetsLayer.ZoomLevelSet.ZoomLevel14.DefaultLineStyle =
    new LineStyle(new GeoPen(GeoBrushes.Gray, 5), new GeoPen(GeoBrushes.White, 4));
streetsLayer.ZoomLevelSet.ZoomLevel14.ApplyUntilZoomLevel = ApplyUntilZoomLevel.Level16;

PointStyle (Points)

hotelsLayer.ZoomLevelSet.ZoomLevel12.DefaultPointStyle =
    new PointStyle(PointSymbolType.Circle, 4, GeoBrushes.DarkRed, new GeoPen(GeoBrushes.White, 2));
hotelsLayer.ZoomLevelSet.ZoomLevel12.ApplyUntilZoomLevel = ApplyUntilZoomLevel.Level13;

TextStyle (Labels)

hotelsLayer.ZoomLevelSet.ZoomLevel16.DefaultTextStyle =
    new TextStyle("NAME", new GeoFont("Segoe UI", 12, DrawingFontStyles.Bold), GeoBrushes.DarkRed)
    {
        TextPlacement = TextPlacement.Lower,
        YOffsetInPixel = 4,
        HaloPen = new GeoPen(GeoBrushes.White, 2),
        DrawingLevel = DrawingLevel.LabelLevel,
        AllowLineCarriage = true
    };
hotelsLayer.ZoomLevelSet.ZoomLevel16.ApplyUntilZoomLevel = ApplyUntilZoomLevel.Level20;

Scale-Dependent Rendering (Different Styles at Different Zoom Levels)

A powerful pattern is to progressively add detail as the user zooms in:

// Only show outline at low zoom
streetsLayer.ZoomLevelSet.ZoomLevel10.DefaultLineStyle =
    new LineStyle(new GeoPen(GeoBrushes.LightGray, 1));
streetsLayer.ZoomLevelSet.ZoomLevel10.ApplyUntilZoomLevel = ApplyUntilZoomLevel.Level11;

// Thicker + styled casing at mid zoom
streetsLayer.ZoomLevelSet.ZoomLevel12.DefaultLineStyle =
    new LineStyle(new GeoPen(GeoBrushes.LightGray, 2), new GeoPen(GeoBrushes.White, 1));

// Add labels at high zoom
streetsLayer.ZoomLevelSet.ZoomLevel16.DefaultTextStyle =
    new TextStyle("FULL_NAME", new GeoFont("Segoe UI", 9, DrawingFontStyles.Bold), GeoBrushes.Black)
    {
        SplineType = SplineType.StandardSplining,
        HaloPen = new GeoPen(GeoBrushes.White, 2),
        DrawingLevel = DrawingLevel.LabelLevel
    };
streetsLayer.ZoomLevelSet.ZoomLevel16.ApplyUntilZoomLevel = ApplyUntilZoomLevel.Level20;

ValueStyle (Data-Driven Styling)

Apply different styles based on an attribute column value:

// Use object initializer syntax to set ColumnName
var valueStyle = new ValueStyle
{
    ColumnName = "CATEGORY"
};

// ValueItem(matchValue, style) — AreaStyle(GeoPen outlinePen, GeoBrush fillBrush)
valueStyle.ValueItems.Add(new ValueItem("Park",
    new AreaStyle(new GeoPen(GeoColors.DarkGreen, 1), new GeoSolidBrush(GeoColors.PastelGreen))));
valueStyle.ValueItems.Add(new ValueItem("Hospital",
    new AreaStyle(new GeoPen(GeoColors.DarkRed, 1), new GeoSolidBrush(GeoColors.LightCoral))));
valueStyle.ValueItems.Add(new ValueItem("School",
    new AreaStyle(new GeoPen(GeoColors.DarkGoldenrod, 1), new GeoSolidBrush(GeoColors.LightYellow))));

layer.ZoomLevelSet.ZoomLevel01.CustomStyles.Add(valueStyle);
layer.ZoomLevelSet.ZoomLevel01.ApplyUntilZoomLevel = ApplyUntilZoomLevel.Level20;

Querying Features

The QueryTools property exposes spatial and attribute query methods. Always Open() the layer before querying, and Close() it when done.

layer.Open();

// Get all features (avoid on large datasets)
Collection<Feature> all =
    layer.QueryTools.GetAllFeatures(ReturningColumnsType.AllColumns);

// Query by bounding box
RectangleShape extent = new RectangleShape(-97.5, 30.5, -97.0, 31.0);
Collection<Feature> inBox =
    layer.QueryTools.GetFeaturesInsideBoundingBox(extent, ReturningColumnsType.AllColumns);

// Query by column value
Collection<Feature> byValue =
    layer.QueryTools.GetFeaturesByColumnValue("STATE_NAME", "Texas",
        ReturningColumnsType.AllColumns);

// Spatial: find the N features nearest to a point (within optional max distance)
PointShape clickPoint = new PointShape(-97.2, 30.7);
Collection<Feature> nearest =
    layer.QueryTools.GetFeaturesNearestTo(
        clickPoint,
        GeographyUnit.DecimalDegree,
        5,
        new Collection<string>(),   // returning column names
        100,                         // max distance
        DistanceUnit.Meter);        // distance unit

layer.Close();

Accessing Feature Attributes

Each Feature has a ColumnValues dictionary:

foreach (Feature feature in features)
{
    string name = feature.ColumnValues["NAME"];
    string population = feature.ColumnValues["POPULATION"];
    Console.WriteLine($"{name}: {population}");
}

Editing Features (Read-Write Mode)

To add, update, or delete features, open the layer and use EditTools within a transaction:

Adding a Feature

var layer = new ShapeFileFeatureLayer("./Data/Points.shp");
layer.Open();
layer.EditTools.BeginTransaction();

var newPoint = new PointShape(-97.3, 30.6);
var newFeature = new Feature(newPoint);
newFeature.ColumnValues["NAME"] = "New Location";
layer.EditTools.Add(newFeature);

layer.EditTools.CommitTransaction();
layer.Close();

Deleting a Feature

layer.Open();
layer.EditTools.BeginTransaction();

Collection<Feature> toDelete =
    layer.QueryTools.GetFeaturesByColumnValue("IDOBJ", "42",
        ReturningColumnsType.NoColumns);
if (toDelete.Count > 0)
{
    layer.EditTools.Delete(toDelete[0].Id);
}

layer.EditTools.CommitTransaction();
layer.Close();

Updating Feature Columns (Schema Changes)

Note: Column schema mutations (adding, renaming, or deleting columns on an existing shapefile) are not covered by the ThinkGeo HowDoI samples. The standard approach is to create a new shapefile with the desired schema using ShapeFileFeatureLayer.CreateShapeFile(), copy features into it, and replace the original file. This avoids the DBF format's limited schema-mutation support.

If you need to add a column and populate it, the recommended pattern is:

// 1. Create a new shapefile with the updated column schema
var newColumns = new Collection<DbfColumn>
{
    new DbfColumn("NAME", DbfColumnType.String, 255, 0),
    new DbfColumn("PRIORITY", DbfColumnType.Numeric, 10, 0)  // new column
};
ShapeFileFeatureLayer.CreateShapeFile(
    ShapeFileType.Point, "./Data/Points_new.shp", newColumns,
    System.Text.Encoding.UTF8, OverwriteMode.Overwrite);

// 2. Copy features from the old layer into the new one
var oldLayer = new ShapeFileFeatureLayer("./Data/Points.shp");
var newLayer = new ShapeFileFeatureLayer("./Data/Points_new.shp");
oldLayer.Open();
newLayer.Open();
newLayer.EditTools.BeginTransaction();
foreach (var feature in oldLayer.QueryTools.GetAllFeatures(ReturningColumnsType.AllColumns))
{
    // Carry over existing column values; new column defaults to empty
    newLayer.EditTools.Add(feature);
}
newLayer.EditTools.CommitTransaction();
oldLayer.Close();
newLayer.Close();

Creating a New Shapefile

Use the static ShapeFileFeatureLayer.CreateShapeFile() method to create a brand-new shapefile:

var columns = new Collection<DbfColumn>
{
    new DbfColumn("NAME", DbfColumnType.String, 255, 0),
    new DbfColumn("CATEGORY", DbfColumnType.String, 50, 0),
    new DbfColumn("VALUE", DbfColumnType.Numeric, 10, 2)
};

ShapeFileFeatureLayer.CreateShapeFile(
    ShapeFileType.Point,
    "./Data/NewPoints.shp",
    columns,
    System.Text.Encoding.UTF8,
    OverwriteMode.Overwrite);

ShapeFileType Values

Value Description
Point Single point features
Multipoint Collections of points
Polyline Line / polyline features
Polygon Area / polygon features
NullShape Null/empty geometry

Building a Spatial Index

For large shapefiles, a ThinkGeo spatial index (.idx / .ids) dramatically improves rendering and query performance by avoiding full file scans:

// Build the index once (typically at app startup or as a build step).
// Can be called on either ShapeFileFeatureLayer or ShapeFileFeatureSource.
ShapeFileFeatureLayer.BuildIndexFile(
    "./Data/LargeLayer.shp",
    BuildIndexMode.Rebuild);          // Rebuilds even if index already exists

// Use DoNotRebuild to skip the build if the index file is already present:
ShapeFileFeatureLayer.BuildIndexFile(
    "./Data/LargeLayer.shp",
    BuildIndexMode.DoNotRebuild);

Tip: Run BuildIndexFile during a setup/installation step rather than at runtime. Index building on a very large shapefile can take seconds to minutes.


Getting the Bounding Box

layer.Open();
RectangleShape bbox = layer.GetBoundingBox();
layer.Close();

// Use it to set the initial map extent
mapView.CurrentExtent = bbox;

Projection and Coordinate Systems

If your shapefile has a .prj file, ThinkGeo will read the projection automatically. However, you must still set the ProjectionConverter if the map is rendering in a different coordinate system.

Use ProjectionConverter with EPSG codes — the standard approach for all ThinkGeo projections:

// Create a new ProjectionConverter to convert between the shapefile's CRS and the map's CRS.
// Constructor: ProjectionConverter(internalEpsg, externalEpsg)
// "internal" = the projection the data is stored in (the shapefile's native CRS)
// "external" = the projection the map is displayed in (typically Spherical Mercator, 3857)

// Example: WGS84 (4326) shapefile on a Web Mercator (3857) map
var projectionConverter = new ProjectionConverter(4326, 3857);
layer.FeatureSource.ProjectionConverter = projectionConverter;

// Example: Texas State Plane North Central (2276) shapefile on a Web Mercator (3857) map
var projectionConverter = new ProjectionConverter(2276, 3857);
layer.FeatureSource.ProjectionConverter = projectionConverter;

Advanced: Custom FeatureSource (Performance Filtering)

For very large shapefiles (e.g., 800k+ features), you can subclass ShapeFileFeatureSource and override drawing methods to filter out features that are too small to be visible at the current scale:

public class FilteredShapeFileFeatureSource : ShapeFileFeatureSource
{
    public double MinDrawingPixelSize { get; set; } = 2.0;

    public FilteredShapeFileFeatureSource(string shapePathFilename)
    {
        ShapePathFileName = shapePathFilename;
    }

    protected override Collection<string> GetFeatureIdsForDrawingCore(
        RectangleShape boundingBox, double screenWidth, double screenHeight)
    {
        Collection<string> allIds =
            base.GetFeatureIdsForDrawingCore(boundingBox, screenWidth, screenHeight);

        double resolutionX = boundingBox.Width / screenWidth;
        double resolutionY = boundingBox.Height / screenHeight;

        var filteredIds = new Collection<string>();
        foreach (string id in allIds)
        {
            RectangleShape featureBbox = GetBoundingBoxById(id);
            double drawingWidth = featureBbox.Width / resolutionX;
            double drawingHeight = featureBbox.Height / resolutionY;

            if (Math.Max(drawingWidth, drawingHeight) > MinDrawingPixelSize)
            {
                filteredIds.Add(id);
            }
        }
        return filteredIds;
    }
}

// Companion custom layer
public class FilteredShapeFileFeatureLayer : ShapeFileFeatureLayer
{
    public FilteredShapeFileFeatureLayer(string shapePathFilename)
    {
        FeatureSource = new FilteredShapeFileFeatureSource(shapePathFilename);
    }
}

// Usage
var layer = new FilteredShapeFileFeatureLayer("./Data/LargePolygons.shp");
((FilteredShapeFileFeatureSource)layer.FeatureSource).MinDrawingPixelSize = 3.0;

Common Patterns and Best Practices

Always Pair Open() with Close()

layer.Open();
try
{
    // work with layer
}
finally
{
    layer.Close();
}

When a layer is added to a LayerOverlay on the map, ThinkGeo manages open/close automatically during rendering. You only need to manually call Open() / Close() when querying or editing outside of the rendering pipeline.

Use ReturningColumnsType to Limit Data Read

Fetching all columns from a large DBF is expensive. Only request what you need:

// Fetch only specific columns
var columnNames = new Collection<string> { "NAME", "POPULATION" };
Collection<Feature> features =
    layer.QueryTools.GetAllFeatures(columnNames);

// Or use the enum for common cases
Collection<Feature> noAttr =
    layer.QueryTools.GetAllFeatures(ReturningColumnsType.NoColumns);

Use Tile Caching for Static Layers

If a layer's data does not change at runtime, enable FileRasterTileCache to avoid re-rendering tiles on every map interaction:

var overlay = new LayerOverlay();
overlay.TileCache = new FileRasterTileCache(@"./TileCache", "countries_layer");
overlay.Layers.Add(countriesLayer);

Common Pitfalls and Gotchas

1. Missing .shx or .dbf File

ShapeFileFeatureLayer requires the .shp, .shx, and .dbf files to all be present in the same directory. A FileNotFoundException or unexpected empty map is often caused by deploying only the .shp.

2. Forgetting to Set ApplyUntilZoomLevel

If you set a style on ZoomLevel01 but do not set ApplyUntilZoomLevel = ApplyUntilZoomLevel.Level20, the style will only render at zoom level 1. At all other zoom levels the layer will appear empty or unstyled. This is one of the most common beginner mistakes.

// WRONG – only visible at zoom level 1
layer.ZoomLevelSet.ZoomLevel01.DefaultAreaStyle =
    AreaStyle.CreateSimpleAreaStyle(GeoColors.YellowGreen, GeoColors.Black);

// CORRECT – visible at all zoom levels
layer.ZoomLevelSet.ZoomLevel01.DefaultAreaStyle =
    AreaStyle.CreateSimpleAreaStyle(GeoColors.YellowGreen, GeoColors.Black);
layer.ZoomLevelSet.ZoomLevel01.ApplyUntilZoomLevel = ApplyUntilZoomLevel.Level20;

3. Projection Mismatch (Blank or Offset Map)

If features render in the wrong location or far off-screen, the most likely cause is a missing or incorrect ProjectionConverter. Always match the layer's native projection to the map's display projection.

4. Editing a Shapefile That Is Locked by Another Process

If another process (or another part of your application) has the shapefile open for writing, calling EditTools.BeginTransaction() can throw an IOException. Ensure no other code has an exclusive write lock on the .shp or .dbf files before editing.

// Correct pattern: open, transact, commit, close
layer.Open();
layer.EditTools.BeginTransaction();
// ... add / update / delete ...
layer.EditTools.CommitTransaction();
layer.Close();

5. GetBoundingBox() Returns Wrong Values After Deletes

When you delete features from a shapefile and then call GetBoundingBox(), the cached bounding box in the .shp header can contain stale values that do not reflect the deletion. Workaround: manually compute the bounding box from the remaining features:

layer.Open();
Collection<Feature> remaining =
    layer.FeatureSource.GetAllFeatures(ReturningColumnsType.NoColumns);
RectangleShape bbox = remaining.Count > 0 ? remaining[0].GetBoundingBox() : new RectangleShape();
foreach (Feature f in remaining)
    bbox.ExpandToInclude(f.GetBoundingBox());
layer.Close();

6. Multi-Thread Access Conflicts

In older ThinkGeo versions, opening the same shapefile from two threads simultaneously could throw an IOException ("file is being used by another process"). This was fixed in version 7.0.0.119+. In modern ThinkGeo.Core, multiple instances reading the same file in parallel are safe.

In modern versions, multiple read-only instances of the same file can be opened in parallel safely.

7. Performance Degradation on Large DBF Files

Rendering features that require attribute lookup (for TextStyle labels or ValueStyle coloring) requires reading from the .dbf file for each visible feature. On large datasets (hundreds of thousands of features), this can be extremely slow — even when only a few hundred features are in the current view. Solutions:

  • Use the FilteredShapeFileFeatureSource pattern above to reduce the number of features fetched.
  • Minimize the number of columns fetched via ReturningColumnsType or explicit column name lists.
  • Consider converting to a database-backed FeatureSource (e.g., SQLite) for large datasets.
  • Enable tile caching to avoid re-rendering unchanged tiles.

8. Styles Must Be Set Before Rendering

Styles are applied at draw time. Setting a style after the map has already rendered will not be visible until the next map refresh. Always configure styles before adding the overlay to the map, or call mapView.RefreshAsync() after changing styles.

9. ShapeFileType Must Match the Data

When creating a new shapefile with CreateShapeFile, the ShapeFileType must exactly match the geometry you intend to write. Writing PointShape features to a Polygon shapefile will throw an exception or produce corrupt data.


Key Properties Reference

Property Type Description
ShapePathFileName string Full path to the .shp file
IndexPathFileName string Full path to the spatial index .idx file

| FeatureSource | ShapeFileFeatureSource | Direct access to the underlying feature source | | ZoomLevelSet | ZoomLevelSet | Holds ZoomLevel01ZoomLevel20 for style assignment | | QueryTools | QueryTools | Spatial and attribute query methods | | EditTools | EditTools | Add/update/delete features (use within a transaction) | | IsOpen | bool | Whether the layer is currently open | | Name | string | Optional identifier for use in LayerOverlay.Layers dictionary | | IsVisible | bool | Controls whether the layer renders | | Transparency | float | 0 (opaque) to 1 (transparent) overlay transparency |


Key Static Methods

Method Description
CreateShapeFile(...) Creates a new empty shapefile on disk
BuildIndexFile(...) Builds a ThinkGeo spatial index for a shapefile
RebuildIndexFile(...) Rebuilds an existing spatial index

Class Description
ShapeFileFeatureSource The underlying data access object; can be subclassed
FeatureLayer Base class providing QueryTools, EditTools, ZoomLevelSet
LayerOverlay Container that holds one or more layers for rendering
ProjectionConverter Reprojects coordinates using EPSG codes
FileRasterTileCache Caches rendered tiles to disk for performance
ZoomLevelSet Manages styles per zoom level
Feature Represents a single geographic feature with geometry + attributes
DbfColumn Defines a column in a DBF attribute table

Further Reading