Skip to content

TrackOverlay Guide (WPF & WinForms)

Namespace: ThinkGeo.UI.Wpf / ThinkGeo.UI.WinForms
Accessed via: MapView.TrackOverlay
Applies to: ThinkGeo Desktop (WPF and WinForms)


Overview

TrackOverlay is the built-in interactive overlay that lets users draw new shapes directly on the map using the mouse. You set a TrackMode to tell the overlay what shape to draw, and the user clicks and/or drags to produce it. When the user finishes drawing, a TrackEnded event fires, giving you the completed shape to work with.

The key distinction from EditOverlay (which modifies existing shapes) is that TrackOverlay creates new shapes from scratch. The typical workflow is:

  1. Set TrackMode to activate drawing.
  2. The user draws a shape on the map.
  3. Handle TrackEnded to receive the completed shape and do something with it — save it, run a spatial query, trigger a cloud API call, etc.
  4. Set TrackMode = TrackMode.None to return to normal navigation.

TrackOverlay is a singleton property on MapView — you do not construct it yourself.


How It Works: Drawing Mechanics

When TrackMode is set to anything other than None, the map intercepts mouse clicks and routes them into the overlay's drawing logic instead of the default pan-and-zoom behavior. The in-progress shape is rendered live in TrackShapeLayer as the user moves the mouse and adds vertices.

Drawing gestures differ by shape type:

  • Point — single left-click places the point immediately. TrackEnded fires at once.
  • Line — each left-click adds a vertex. Double-click closes the shape and fires TrackEnded.
  • Polygon — each left-click adds a vertex. Double-click closes the polygon (connecting the last vertex back to the first) and fires TrackEnded.

While drawing a multi-vertex shape (Line or Polygon), the user can hold the middle mouse button (scroll wheel) and drag to pan the map without interrupting the drawing session. In WPF, the user can also hold Shift while adding vertices to constrain movement to the North–South or East–West axis — a useful snapping aid for orthogonal shapes.


Key Members

Properties

Property Type Description
TrackMode TrackMode Controls what shape the overlay draws. Set to TrackMode.None to disable drawing and restore normal map navigation.
TrackShapeLayer InMemoryFeatureLayer The layer holding the in-progress and completed drawn shapes. Clear this after consuming a shape to reset the drawing state.

Events

Event Event Args Type Fired When
TrackEnded TrackEndedTrackInteractiveOverlayEventArgs The user finishes drawing a shape (double-click for Line/Polygon, single click for Point).
MouseMoved MouseMovedTrackInteractiveOverlayEventArgs The mouse moves while a drawing is in progress. Useful for live measurement feedback.

TrackEndedTrackInteractiveOverlayEventArgs

Member Type Description
TrackShape BaseShape The completed shape. Cast to PointShape, LineShape, or PolygonShape as appropriate.

MouseMovedTrackInteractiveOverlayEventArgs

Member Type Description
AffectedFeature Feature The in-progress shape as it currently stands. Call GetShape() on it to measure the partial geometry during drawing.

TrackMode Enum

The following TrackMode values are confirmed in use across the HowDoI samples:

Value Behavior
TrackMode.None Drawing disabled; normal map navigation (pan and zoom). The default state.
TrackMode.Point One click places a point; TrackEnded fires immediately.
TrackMode.Line Click to add vertices; double-click to finish the line.
TrackMode.Polygon Click to add vertices; double-click to close and finish the polygon.

Usage Patterns

Pattern 1: Basic Draw-and-Collect Workflow

This is the canonical workflow when building an editing tool — the user draws shapes freely, and everything accumulates in TrackShapeLayer until the mode changes. When the user switches away from a drawing mode, you flush TrackShapeLayer into your own InMemoryFeatureLayer for persistent storage.

// ---- Setup ----
MapView.MapUnit = GeographyUnit.Meter;

var featureLayer = new InMemoryFeatureLayer();
featureLayer.ZoomLevelSet.ZoomLevel01.DefaultPointStyle =
    PointStyle.CreateSimpleCircleStyle(GeoColors.Blue, 8, GeoColors.Black);
featureLayer.ZoomLevelSet.ZoomLevel01.DefaultLineStyle =
    LineStyle.CreateSimpleLineStyle(GeoColors.Blue, 4, true);
featureLayer.ZoomLevelSet.ZoomLevel01.DefaultAreaStyle =
    AreaStyle.CreateSimpleAreaStyle(GeoColors.Blue, GeoColors.Black);
featureLayer.ZoomLevelSet.ZoomLevel01.ApplyUntilZoomLevel = ApplyUntilZoomLevel.Level20;

var layerOverlay = new LayerOverlay();
layerOverlay.Layers.Add("featureLayer", featureLayer);
MapView.Overlays.Add("layerOverlay", layerOverlay);
// ---- Activate a drawing mode ----
// Call from a button handler. Only one mode should be active at a time.
MapView.TrackOverlay.TrackMode = TrackMode.Point;    // draw points
MapView.TrackOverlay.TrackMode = TrackMode.Line;     // draw lines
MapView.TrackOverlay.TrackMode = TrackMode.Polygon;  // draw polygons
// ---- Deactivate drawing and collect drawn shapes ----
// Call when the user switches to a different mode (Navigation, Edit, etc.)
private void ExitDrawMode(InMemoryFeatureLayer featureLayer, LayerOverlay layerOverlay)
{
    // Move everything accumulated in TrackShapeLayer into the persistent layer
    foreach (var feature in MapView.TrackOverlay.TrackShapeLayer.InternalFeatures)
        featureLayer.InternalFeatures.Add(feature.Id, feature);

    MapView.TrackOverlay.TrackShapeLayer.InternalFeatures.Clear();
    MapView.TrackOverlay.TrackMode = TrackMode.None;

    _ = MapView.RefreshAsync(new Overlay[] { MapView.TrackOverlay, layerOverlay });
}

Why flush into a separate layer? TrackShapeLayer is transient — it is cleared when the mode changes or when the EditOverlay takes over. Copying features into your own InMemoryFeatureLayer before switching modes is how you preserve them across mode transitions.


Pattern 2: React-on-Completion with TrackEnded

TrackEnded is the right hook when you want to immediately act on each completed shape rather than accumulate them. Common uses include triggering spatial queries, calling cloud APIs, or running geometry analysis. Every sample that uses TrackEnded follows the same three-step handler pattern:

  1. Set TrackMode = TrackMode.None to stop drawing.
  2. Clear TrackShapeLayer (the in-progress shape is now in e.TrackShape; the layer copy is no longer needed).
  3. Act on e.TrackShape.
// ---- Subscribe during initialization ----
MapView.TrackOverlay.TrackEnded += OnShapeDrawn;
MapView.TrackOverlay.TrackMode = TrackMode.Polygon; // start in polygon draw mode
// ---- Handler: runs your logic on the completed shape ----
private async void OnShapeDrawn(object sender, TrackEndedTrackInteractiveOverlayEventArgs e)
{
    // Step 1: disable further drawing
    MapView.TrackOverlay.TrackMode = TrackMode.None;

    // Step 2: clear the in-progress shape from the draw layer
    MapView.TrackOverlay.TrackShapeLayer.InternalFeatures.Clear();

    // Step 3: use e.TrackShape — cast to the type you expect
    var polygon = (PolygonShape)e.TrackShape;

    // Example: run a spatial query against your feature layer
    await RunSpatialQueryAsync(polygon);
}

If you want to allow the user to re-draw after each completed shape, re-enable TrackMode at the end of your handler (or hook MapView.MapClick to re-enable it):

// Re-enable drawing after handling the completed shape
private void mapView_MapClick(object sender, MapClickMapViewEventArgs e)
{
    if (MapView.TrackOverlay.TrackMode != TrackMode.Polygon)
        MapView.TrackOverlay.TrackMode = TrackMode.Polygon;
}

Pattern 3: Validate the Shape in TrackEnded Before Using It

When shapes are used as inputs to distance- or area-sensitive operations (such as cloud elevation queries or area-based analysis), validate size immediately in TrackEnded and reject oversized shapes before proceeding:

private async void OnShapeDrawn(object sender, TrackEndedTrackInteractiveOverlayEventArgs e)
{
    MapView.TrackOverlay.TrackMode = TrackMode.None;
    MapView.TrackOverlay.TrackShapeLayer.InternalFeatures.Clear();

    // Validate before passing to your processing method
    switch (e.TrackShape.GetWellKnownType())
    {
        case WellKnownType.Polygon:
            var polygon = (PolygonShape)e.TrackShape;
            if (polygon.GetArea(GeographyUnit.Meter, AreaUnit.SquareKilometers) > 5)
            {
                MessageBox.Show("Please draw a smaller polygon (limit: 5km²)");
                return;
            }
            break;

        case WellKnownType.Line:
            var line = (LineShape)e.TrackShape;
            if (line.GetLength(GeographyUnit.Meter, DistanceUnit.Kilometer) > 5)
            {
                MessageBox.Show("Please draw a shorter line (limit: 5km)");
                return;
            }
            break;
    }

    await ProcessShapeAsync(e.TrackShape);
}

GetWellKnownType() is the idiomatic way to branch on shape type when you may receive different geometry types from the same TrackEnded handler. It avoids a type-check chain with is and as casts.


Pattern 4: Live Measurement with MouseMoved (WPF)

TrackOverlay.MouseMoved fires continuously as the user moves the mouse while drawing. The AffectedFeature in the event args holds the partially-drawn shape, letting you display a running area or length readout in the UI before the user finishes.

This event is confirmed in WPF HowDoI samples. In WinForms the same measurement approach works via TrackEnded (after the shape is complete), since the WinForms samples do not use MouseMoved for live feedback.

// Subscribe during initialization (WPF)
MapView.TrackOverlay.MouseMoved += TrackOverlay_MouseMoved;
private void TrackOverlay_MouseMoved(object sender, MouseMovedTrackInteractiveOverlayEventArgs e)
{
    var shape = e.AffectedFeature.GetShape();
    MeasureResult.Text = GetMeasureResult(shape);
}

private static string GetMeasureResult(BaseShape shape)
{
    // Pass the map's Spherical Mercator projection for accurate measurements.
    var projection = new Projection(Projection.GetSphericalMercatorProjString());

    switch (shape)
    {
        case AreaBaseShape polygon:
            var area = polygon.GetArea(projection, AreaUnit.SquareMiles);
            return $"{area:N2} square miles";

        case LineBaseShape line:
            var length = line.GetLength(projection, DistanceUnit.Mile);
            return $"{length:N2} miles";

        default:
            return string.Empty;
    }
}

Projection-aware measurement: GetArea() and GetLength() each have an overload that accepts a Projection. Always pass one when the map uses Web Mercator (EPSG:3857), as the raw coordinate values produce distorted measurements without it. Projection.GetSphericalMercatorProjString() is the correct Proj4 string for this projection.


Pattern 5: Draw-Then-Query (Spatial Query Trigger)

A very common pattern: TrackOverlay in Polygon or Line mode is used as a free-form selection tool. The user draws a shape, and TrackEnded triggers a spatial query against a loaded feature layer. The drawn shape is cleared from TrackShapeLayer immediately — it is only needed as a query input, not as a persistent feature.

// ---- Setup ----
MapView.TrackOverlay.TrackEnded += OnPolygonDrawn;
MapView.TrackOverlay.TrackMode = TrackMode.Polygon;
// ---- TrackEnded handler ----
private async void OnPolygonDrawn(object sender, TrackEndedTrackInteractiveOverlayEventArgs e)
{
    await RunQueryAsync((PolygonShape)e.TrackShape);
}
// ---- Query method ----
private async Task RunQueryAsync(PolygonShape queryPolygon)
{
    var queryFeaturesOverlay = (LayerOverlay)MapView.Overlays["Query Features Overlay"];
    var queryFeatureLayer = (InMemoryFeatureLayer)queryFeaturesOverlay.Layers["Query Feature"];
    var dataLayer = (ShapeFileFeatureLayer)MapView.FindFeatureLayer("My Data Layer");

    // Replace the query shape shown on the map
    queryFeatureLayer.InternalFeatures.Clear();
    queryFeatureLayer.InternalFeatures.Add(new Feature(queryPolygon));
    await queryFeaturesOverlay.RefreshAsync();

    // Run the spatial query
    dataLayer.Open();
    var results = dataLayer.QueryTools.GetFeaturesWithin(queryPolygon, ReturningColumnsType.AllColumns);
    dataLayer.Close();

    // Display the results
    await DisplayResultsAsync(results);

    // Clear the drawn shape — it has been consumed
    MapView.TrackOverlay.TrackShapeLayer.InternalFeatures.Clear();
    await MapView.RefreshAsync();
}

For a Line mode spatial query (e.g., finding features that cross a drawn line):

MapView.TrackOverlay.TrackEnded += OnLineDrawn;
MapView.TrackOverlay.TrackMode = TrackMode.Line;

private async void OnLineDrawn(object sender, TrackEndedTrackInteractiveOverlayEventArgs e)
{
    await RunCrossingQueryAsync((LineShape)e.TrackShape);
}

Pattern 6: Accept a Completed Shape into Another Layer

When you want the drawn shape to become a persistent, user-visible feature (rather than just a query input), add it to your own InMemoryFeatureLayer directly from the TrackEnded handler. The pattern matches what the WorldMapsQuery sample does — the drawn shape is transferred into a display layer, then TrackShapeLayer is cleared:

private async void OnShapeDrawn(object sender, TrackEndedTrackInteractiveOverlayEventArgs e)
{
    // Stop drawing
    MapView.TrackOverlay.TrackMode = TrackMode.None;

    // Clear the TrackOverlay's own rendering of the shape
    MapView.TrackOverlay.TrackShapeLayer.InternalFeatures.Clear();

    // Add the completed shape to your persistent display layer
    var overlay = (LayerOverlay)MapView.Overlays["My Overlay"];
    var displayLayer = (InMemoryFeatureLayer)overlay.Layers["My Layer"];
    displayLayer.InternalFeatures.Add(new Feature(e.TrackShape));

    // Refresh the display layer to show the new feature
    await overlay.RefreshAsync();

    // Run any follow-on logic (e.g., cloud query, analysis, etc.)
    await DoSomethingWithShapeAsync(e.TrackShape);
}

Styling the Draw Layer

TrackShapeLayer is an InMemoryFeatureLayer, and like any other feature layer you can override its default draw styles through its ZoomLevelSet. The overlay has built-in default styles (a thin blue outline), but you can replace them if you want the in-progress shape to appear differently — for example, to match your application's color scheme or to show a dashed rubber-band line.

// Apply custom styles to the in-progress draw layer
var drawLayer = MapView.TrackOverlay.TrackShapeLayer;
drawLayer.ZoomLevelSet.ZoomLevel01.DefaultPointStyle =
    PointStyle.CreateSimpleCircleStyle(GeoColors.OrangeRed, 10, GeoColors.DarkRed);
drawLayer.ZoomLevelSet.ZoomLevel01.DefaultLineStyle =
    LineStyle.CreateSimpleLineStyle(GeoColors.OrangeRed, 2, true);
drawLayer.ZoomLevelSet.ZoomLevel01.DefaultAreaStyle =
    AreaStyle.CreateSimpleAreaStyle(GeoColor.FromArgb(60, GeoColors.OrangeRed), GeoColors.OrangeRed);
drawLayer.ZoomLevelSet.ZoomLevel01.ApplyUntilZoomLevel = ApplyUntilZoomLevel.Level20;

Style the layer during initialization, before any drawing begins. Changes made mid-drawing may not take effect until the next shape starts.


Interaction with EditOverlay

TrackOverlay and EditOverlay are companion overlays that are almost always used together. The relationship is:

  • TrackOverlay creates new shapes.
  • EditOverlay modifies existing shapes.

Only one should be "active" at a time. The standard handoff pattern when switching to edit mode is:

  1. Flush TrackShapeLayer into your InMemoryFeatureLayer.
  2. Clear TrackShapeLayer and set TrackMode = TrackMode.None.
  3. Move features from InMemoryFeatureLayer into EditOverlay.EditShapesLayer.
  4. Call EditOverlay.CalculateAllControlPoints().

The reverse handoff (edit → draw) clears EditShapesLayer back into the feature layer first. See the EditOverlay Developer Guide for the complete flush helper implementation.


Common Pitfalls

1. Not Clearing TrackShapeLayer After TrackEnded

After TrackEnded fires, the completed shape remains in TrackShapeLayer. If you do not clear it, the overlay continues to render it on the map while the next shape is being drawn — producing a confusing visual where multiple shapes appear to be active simultaneously. Always clear in your TrackEnded handler:

MapView.TrackOverlay.TrackShapeLayer.InternalFeatures.Clear();

2. Forgetting to Set TrackMode Back to None

After handling a completed shape, remember to set TrackMode = TrackMode.None unless you explicitly want the user to be able to immediately draw another shape. Leaving a drawing mode active means the user cannot pan the map normally.

3. Casting TrackShape to the Wrong Type

e.TrackShape is typed as BaseShape. Casting it directly to PolygonShape when the user drew a line will throw at runtime. Either guard with GetWellKnownType() (Pattern 3 above) or only subscribe to TrackEnded when a single known TrackMode is active.

4. Measuring Without a Projection

Calling GetArea(GeographyUnit.Meter, AreaUnit.SquareKilometers) or GetLength(GeographyUnit.Meter, DistanceUnit.Kilometer) on a Web Mercator shape without passing a Projection gives you a raw coordinate-space result, which is distorted. Pass a Projection built from Projection.GetSphericalMercatorProjString() when your map is in EPSG:3857. See Pattern 3 above for the correct overloads.

5. Subscribing to TrackEnded Multiple Times

TrackEnded is an instance event on the singleton TrackOverlay. If your initialization code can run more than once, guard with a flag or use -= before += to avoid accumulating duplicate handlers that each fire on every completed shape.


Class / Member Description
EditOverlay Companion overlay for modifying existing shapes. Accessed via MapView.EditOverlay.
TrackMode Enum controlling what TrackOverlay draws: None, Point, Line, Polygon.
TrackEndedTrackInteractiveOverlayEventArgs Event args for TrackEnded. Provides TrackShape — the completed BaseShape.
MouseMovedTrackInteractiveOverlayEventArgs Event args for MouseMoved. Provides AffectedFeature — the in-progress partial shape.
InMemoryFeatureLayer The type of TrackShapeLayer. Supports full ZoomLevelSet-based styling.
BaseShape.GetWellKnownType() Returns the geometry type (WellKnownType.Point, Line, Polygon) — use to safely branch on shape type in TrackEnded.
Projection.GetSphericalMercatorProjString() Returns the Proj4 string for EPSG:3857. Pass to GetArea() / GetLength() for accurate real-world measurements on a Web Mercator map.