Skip to content

InMemoryFeatureLayer Guide

Namespace: ThinkGeo.Core
Applies to: All ThinkGeo products (WPF, WinForms, Blazor, MAUI, GIS Server, etc.)


Overview

InMemoryFeatureLayer is a FeatureLayer whose data lives entirely in RAM. There is no file on disk, no database, and no network connection. Features are added, updated, and removed by directly manipulating the layer's InternalFeatures collection or by calling the layer's Clear() method.

It is the standard choice for:

  • Displaying query results, geocoding hits, or geoprocessing output on the map
  • Holding shapes drawn by the user via TrackOverlay before they are persisted
  • Acting as a staging layer when moving features into or out of EditOverlay
  • Temporary highlight, search-radius, or route-display layers that change on every interaction
  • Any in-process analysis where features are built from code rather than read from disk

Because its data is fully in-process, InMemoryFeatureLayer always goes inside a LayerOverlay with TileType.SingleTile. Tiled rendering makes no sense for a layer whose content changes at runtime.


Basic Construction and Setup

Construction is always the no-argument constructor. Styles are applied to ZoomLevelSet immediately after construction, before the layer is opened or added to an overlay.

var featureLayer = new InMemoryFeatureLayer();

// Style for points, lines, and areas — all visible at every zoom level
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;

// Add to a SingleTile LayerOverlay and register it with the map
var layerOverlay = new LayerOverlay();
layerOverlay.Layers.Add("featureLayer", featureLayer);
MapView.Overlays.Add("layerOverlay", layerOverlay);

Always use TileType.SingleTile on any LayerOverlay that contains an InMemoryFeatureLayer. The default tile type splits the map into tiles and caches them; a dynamic layer whose contents change at runtime must be rendered as a single image on every refresh. For validation and topology layers confirmed in the samples, TileType.SingleTile is set explicitly:

var featuresOverlay = new LayerOverlay();
featuresOverlay.TileType = TileType.SingleTile;
featuresOverlay.Layers.Add("Result Features", resultFeaturesLayer);

Adding Features via InternalFeatures

InternalFeatures is the direct-access collection for the layer's data. It accepts both keyed and unkeyed adds.

Unkeyed add — anonymous features, ID auto-assigned

Use this when you don't need to look up or remove individual features by key later:

// From a WKT string
layer.InternalFeatures.Add(new Feature("POINT(-10773220 3913230)"));
layer.InternalFeatures.Add(new Feature("POLYGON((-10778500 3915600,-10778500 3913000,-10775000 3913000,-10775000 3915600,-10778500 3915600))"));

// From a BaseShape
layer.InternalFeatures.Add(new Feature(new EllipseShape(centerPoint, radiusInMeters)));
layer.InternalFeatures.Add(new Feature(someBaseShape));

Keyed add — ID-preserving, required for round-trips with TrackOverlay / EditOverlay

Use this when you need to track identity or when transferring features that already have IDs:

// Preserve the feature's own ID as the dictionary key
layer.InternalFeatures.Add(feature.Id, feature);

This is the required pattern when moving features from TrackOverlay.TrackShapeLayer or EditOverlay.EditShapesLayer back into an InMemoryFeatureLayer, so that edit and delete operations later can find the correct feature:

// Move drawn features from TrackOverlay into the persistent layer
foreach (var feature in MapView.TrackOverlay.TrackShapeLayer.InternalFeatures)
{
    featureLayer.InternalFeatures.Add(feature.Id, feature);
}
MapView.TrackOverlay.TrackShapeLayer.InternalFeatures.Clear();

// Move features back from EditOverlay after editing is complete
foreach (var feature in MapView.EditOverlay.EditShapesLayer.InternalFeatures)
{
    featureLayer.InternalFeatures.Add(feature.Id, feature);
}
MapView.EditOverlay.EditShapesLayer.InternalFeatures.Clear();

Bulk load from another layer

When populating an InMemoryFeatureLayer from a ShapeFileFeatureLayer or other source, enumerate the source's features and add them one by one:

var shapeFileLayer = new ShapeFileFeatureLayer(@"./Data/Shapefile/Frisco_Mosquitos.shp");
shapeFileLayer.Open();
var features = shapeFileLayer.FeatureSource.GetAllFeatures(ReturningColumnsType.AllColumns);
shapeFileLayer.Close();

var inMemoryFeatureLayer = new InMemoryFeatureLayer();
foreach (var feature in features)
{
    inMemoryFeatureLayer.InternalFeatures.Add(feature);
}

Clearing and Refreshing Features

layer.Clear()

Clear() is the method on the layer itself. It requires the layer to be open first. Use this in event handlers and result-display routines where you want to replace the layer's entire contents each time a new result arrives:

var layer = (InMemoryFeatureLayer)MapView.FindFeatureLayer("Search Radius");
layer.Open();
layer.Clear();
layer.InternalFeatures.Add(new Feature(new EllipseShape(searchPoint, searchRadius)));
layer.InternalFeatures.Add(new Feature(searchPoint));

InternalFeatures.Clear()

InternalFeatures.Clear() works directly on the collection, without requiring Open(). Use this to empty TrackOverlay.TrackShapeLayer and EditOverlay.EditShapesLayer, which are themselves InMemoryFeatureLayer instances:

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

Open() and GetBoundingBox()

InMemoryFeatureLayer must be opened before calling GetBoundingBox() or before calling layer.Clear(). In display workflows where you want to zoom the map to fit the layer's data, the pattern is:

inMemoryFeatureLayer.Open();
MapView.CurrentExtent = inMemoryFeatureLayer.GetBoundingBox();
await MapView.RefreshAsync();

In WPF, the equivalent using MapUtil.GetScale:

inMemoryFeatureLayer.Open();
var bbox = inMemoryFeatureLayer.GetBoundingBox();
MapView.CenterPoint = bbox.GetCenterPoint();
MapView.CurrentScale = MapUtil.GetScale(MapView.MapUnit, bbox, MapView.MapWidth, MapView.MapHeight);

Columns and ColumnValues

InMemoryFeatureLayer supports a Columns collection that declares named attributes. Columns must be declared before features with ColumnValues for those columns are added. This is required when using ValueStyle, TextStyle with attribute-based labels, or any other style that reads a column value.

var layer = new InMemoryFeatureLayer();
layer.Open();
layer.Columns.Add(new FeatureSourceColumn("Name", "character", 100));
layer.Columns.Add(new FeatureSourceColumn("Display", "character", 100));

FeatureSourceColumn takes a column name, type string ("character", "integer", "double", etc.), and maximum length.

Once columns are declared, set values on features before or after adding them to the layer:

var feature = new Feature(wktString);
feature.ColumnValues["Name"] = "Buffering";
feature.ColumnValues["Display"] = "500m Buffer";
layer.InternalFeatures.Add(feature);

In the Blazor samples, column values are loaded from XML attributes and set directly on the feature before it is added to a GeoCollection:

var feature = new Feature(featureXElement.Value);
foreach (var xAttribute in featureXElement.Attributes())
{
    feature.ColumnValues[xAttribute.Name.LocalName] = xAttribute.Value;
}
features.Add(featureXElement.Attribute("id").Value, feature);

Using ValueStyle with InMemoryFeatureLayer

When a single layer needs to display multiple different visual styles based on a column value — for example, geoprocessing output where each result type gets its own color — use a ValueStyle driven by one of the layer's columns:

var layer = new InMemoryFeatureLayer();
layer.Open();
layer.Columns.Add(new FeatureSourceColumn("Name", "character", 100));

var valueStyle = new ValueStyle();
valueStyle.ColumnName = "Name";

// Default appearance — applied when no named value matches
var defaultItem = new ValueItem();
defaultItem.Value = "";
defaultItem.CustomStyles.Add(new AreaStyle(new GeoPen(GeoColors.Green, 3),
    new GeoSolidBrush(GeoColor.FromArgb(100, 0, 147, 221))));
valueStyle.ValueItems.Add(defaultItem);

// Named result types get their own style
valueStyle.ValueItems.Add(new ValueItem("Buffering",
    new AreaStyle(new GeoSolidBrush(GeoColor.FromArgb(140, 255, 155, 13)))));
valueStyle.ValueItems.Add(new ValueItem("ClippingResult",
    new AreaStyle(new GeoPen(GeoColors.Black, 1),
        new GeoSolidBrush(new GeoColor(160, 255, 248, 172)))));

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

Features are then tagged with the matching column value when they are added to the layer:

var bufferFeature = new Feature(bufferShape);
bufferFeature.ColumnValues["Name"] = "Buffering";
layer.InternalFeatures.Add(bufferFeature);

Retrieving a Layer from the Map

Use MapView.FindFeatureLayer(name) to get a reference to an InMemoryFeatureLayer that was previously added to the map, then cast it:

var layer = (InMemoryFeatureLayer)MapView.FindFeatureLayer("Search Radius");

You can also retrieve it by navigating through the overlay's Layers collection:

var layerOverlay = (LayerOverlay)MapView.Overlays["layerOverlay"];
var featureLayer = (InMemoryFeatureLayer)layerOverlay.Layers["featureLayer"];

Querying with QueryTools

InMemoryFeatureLayer inherits QueryTools from FeatureLayer, giving it the same spatial query API available on any FeatureLayer.

Find the nearest feature within a maximum distance

Used in delete-on-click workflows to identify which feature the user clicked on:

var nearest = featureLayer.QueryTools.GetFeaturesNearestTo(
    e.WorldLocation,        // PointShape — the click location in world coordinates
    GeographyUnit.Meter,    // units of the map
    1,                      // return at most 1 feature
    new Collection<string>(), // return no column data
    100,                    // maximum search radius
    DistanceUnit.Meter);

if (nearest.Count > 0)
{
    featureLayer.InternalFeatures.Remove(nearest[0]);
    await MapView.RefreshAsync(layerOverlay);
}

Remove a feature by reference

Once you have the feature object, remove it directly from InternalFeatures:

featureLayer.InternalFeatures.Remove(nearest[0]);

Moving Features to and from EditOverlay

The standard pattern for entering edit mode with features that are stored in an InMemoryFeatureLayer:

// Enter edit mode: move all features from the layer into EditOverlay
foreach (var feature in featureLayer.InternalFeatures)
{
    MapView.EditOverlay.EditShapesLayer.InternalFeatures.Add(feature.Id, feature);
}
// Clear the layer so edited and original shapes don't overlap on screen
featureLayer.InternalFeatures.Clear();

// Recalculate all edit handles
MapView.EditOverlay.CalculateAllControlPoints();
await MapView.RefreshAsync(new Overlay[] { MapView.EditOverlay, layerOverlay });

When the user switches out of edit mode, move them back:

foreach (var feature in MapView.EditOverlay.EditShapesLayer.InternalFeatures)
{
    featureLayer.InternalFeatures.Add(feature.Id, feature);
}
MapView.EditOverlay.EditShapesLayer.InternalFeatures.Clear();

Common Pitfalls

1. Forgetting TileType.SingleTile

If an InMemoryFeatureLayer goes into a default LayerOverlay (which uses tiled rendering), the map tiles are cached. When you add or clear features and call RefreshAsync, some tiles may still show stale cached content. Always set TileType.SingleTile on overlays that contain dynamic layers:

var overlay = new LayerOverlay();
overlay.TileType = TileType.SingleTile;  // required for InMemoryFeatureLayer
overlay.Layers.Add(inMemoryFeatureLayer);

2. Calling layer.Clear() on a Closed Layer

layer.Clear() requires the layer to be open. If the layer was constructed and added to a map but has not yet been explicitly opened, call layer.Open() before layer.Clear():

layer.Open();
layer.Clear();
layer.InternalFeatures.Add(new Feature(newShape));

3. Forgetting to Declare Columns Before Adding Features

If you use a TextStyle with an attribute token (e.g., "Trap: [TrapID]") or a ValueStyle driven by a column name, but the column was never declared via layer.Columns.Add(...), the style will silently fail to produce labels or value-based styling. Always declare columns on an open layer before adding features that carry those column values.

4. Missing RefreshAsync After Mutating InternalFeatures

InternalFeatures is a plain collection. Mutations — adds, removes, clears — do not automatically trigger a map redraw. Always follow mutations with MapView.RefreshAsync(layerOverlay) or MapView.RefreshAsync() to update what's visible on screen:

featureLayer.InternalFeatures.Remove(nearest[0]);
await MapView.RefreshAsync(layerOverlay);  // required — nothing redraws automatically

Reference Table

Member Type Description
InternalFeatures GeoCollection<Feature> Direct-access collection of all features in the layer. Supports both unkeyed Add(feature) and keyed Add(id, feature).
Columns Collection<FeatureSourceColumn> Declares named attribute columns. Must be populated (on an open layer) before features with ColumnValues are added.
Open() method Opens the layer. Required before calling GetBoundingBox() or Clear().
Close() method Closes the layer. Optional for in-memory layers; typically omitted in interactive use.
Clear() method Removes all features from the layer. Layer must be open.
GetBoundingBox() method Returns the minimum bounding rectangle of all features currently in the layer. Layer must be open.
QueryTools QueryTools Provides spatial query methods inherited from FeatureLayer, including GetFeaturesNearestTo and GetFeaturesWithinDistanceOf.
ZoomLevelSet ZoomLevelSet Controls the visual styling of the layer at each zoom level. Same as any other FeatureLayer.