Accent color
# Map
## Description
BitMap is a generic, provider-pluggable interactive map component. It supports Leaflet, MapLibre GL, Mapbox GL, OpenLayers, ArcGIS, Azure Maps, and CesiumJS.
## Parameters
| Name | Type | Default Value | Description |
| :--- | :--- | :------------ | :---------- |
| TMapProvider | `Type (generic)` | | The map provider type. One of: BitLeafletMapProvider, BitMapLibreMapProvider, BitMapboxMapProvider, BitOpenLayersMapProvider, BitArcGisMapProvider, BitAzureMapsMapProvider, BitCesiumMapProvider. |
| Provider | `TMapProvider?` | null | Provider configuration instance (center, zoom, tokens, etc.). When null a default instance is created. |
| ChildContent | `RenderFragment?` | null | Optional content rendered above the map canvas. |
| ReplayStateOnProviderSwap | `bool` | false | When true, imperatively-added markers, vector layers, and tile overlays are replayed after a destructive provider swap (different JsObjectName). |
| OnReady | `EventCallback` | | Fires after the map is ready for imperative calls. Fires once on initial mount, and fires again after a destructive provider swap each time the new provider becomes ready. |
| OnClick | `EventCallback<BitMapLatLng>` | | Fires when the user clicks the map canvas. |
| OnDoubleClick | `EventCallback<BitMapLatLng>` | | Fires when the user double-clicks the map. |
| OnViewChanged | `EventCallback<BitMapViewState>` | | Fires whenever the map view changes. |
| OnMarkerClick | `EventCallback<string>` | | Fires when the user clicks a marker (argument is the marker id). |
| OnMarkerDragEnd | `EventCallback<BitMapMarkerDragEndArgs>` | | Fires when a draggable marker is dropped. |
| OnVectorClick | `EventCallback<BitMapVectorClickArgs>` | | Fires when the user clicks a vector layer. |
| OnGeoJsonFeatureClick | `EventCallback<BitMapGeoJsonFeatureClickArgs>` | | Fires when the user clicks a GeoJSON feature. |
| OnInteropError | `EventCallback<BitMapInteropErrorArgs>` | | Fires when an interop call into the underlying provider fails. Lets consumers surface errors that the component would otherwise swallow to prevent circuit-breaking exceptions. |
| AriaLabel | `string?` | null | Gets or sets the accessible label for the component, used by assistive technologies. |
| Class | `string?` | null | Gets or sets the CSS class name(s) to apply to the rendered element. |
| Dir | `BitDir?` | null | Gets or sets the text directionality for the component's content. |
| ForceAnimation | `bool` | false | Gets or sets a value indicating whether the component's animations play at their full duration even when reduced motion is requested. |
| HtmlAttributes | `Dictionary<string, object>` | new Dictionary<string, object>() | Captures additional HTML attributes to be applied to the rendered element, in addition to the component's parameters. |
| Id | `string?` | null | Gets or sets the unique identifier for the component's root element. |
| IsEnabled | `bool` | true | Gets or sets a value indicating whether the component is enabled and can respond to user interaction. |
| Style | `string?` | null | Gets or sets the CSS style string to apply to the rendered element. |
| TabIndex | `string?` | null | Gets or sets the tab order index for the component when navigating with the keyboard. |
| Visibility | `BitVisibility` | BitVisibility.Visible | Gets or sets the visibility state (visible, hidden, or collapsed) of the component. |
## Public Members
| Name | Type | Default Value | Description |
| :--- | :--- | :------------ | :---------- |
| IsReady | `bool` | false | True after the map is ready for interop calls. |
| GetView | `Func<ValueTask<BitMapViewState>>` | | Returns a snapshot of the current viewport. |
| SetView | `Func<BitMapLatLng, double?, bool, ValueTask>` | | Pan and optionally zoom to the given center. |
| FlyTo | `Func<BitMapLatLng, double?, ValueTask>` | | Animated pan/zoom to the given center. |
| FitBounds | `Func<BitMapLatLngBounds, int, ValueTask>` | | Fit the view to the given bounding box. |
| FitBoundsToMarkers | `Func<int, ValueTask>` | | Fit the view to include all current markers. |
| InvalidateSize | `Func<ValueTask>` | | Recalculate map size after a container resize. |
| AddMarker | `Func<BitMapMarker, ValueTask>` | | Add a marker to the map. |
| RemoveMarker | `Func<string, ValueTask>` | | Remove a marker by id. |
| ClearMarkers | `Func<ValueTask>` | | Remove all markers. |
| SetMarkerPosition | `Func<string, BitMapLatLng, ValueTask>` | | Move a marker to a new position. |
| OpenMarkerPopup | `Func<string, ValueTask>` | | Open a marker's popup. |
| SyncMarkers | `Func<IEnumerable<BitMapMarker>, ValueTask>` | | Replace all markers in one batch. |
| AddPolyline | `Func<string, IReadOnlyList<BitMapLatLng>, BitMapVectorPathStyle?, ValueTask>` | | Add a polyline. |
| AddPolygon | `Func<string, IReadOnlyList<BitMapLatLng>, BitMapVectorPathStyle?, ValueTask>` | | Add a polygon. |
| AddCircle | `Func<string, BitMapLatLng, double, BitMapVectorPathStyle?, ValueTask>` | | Add a circle (radius in meters). |
| AddRectangle | `Func<string, BitMapLatLngBounds, BitMapVectorPathStyle?, ValueTask>` | | Add a rectangle. |
| AddGeoJson | `Func<string, string, BitMapVectorPathStyle?, ValueTask>` | | Add a GeoJSON layer. |
| RemoveLayer | `Func<string, ValueTask>` | | Remove a vector layer by id. |
| ClearVectorLayers | `Func<ValueTask>` | | Remove all vector layers. |
| AddTileOverlay | `Func<BitMapTileOverlay, ValueTask>` | | Add a tile overlay above the base map. |
| RemoveTileOverlay | `Func<string, ValueTask>` | | Remove a tile overlay by id. |
| UniqueId | `Guid` | Guid.NewGuid() | Gets the readonly unique identifier for the component's root element, assigned when the component instance is constructed. |
| RootElement | `ElementReference` | | Gets the reference to the root HTML element associated with this component. |
## Enums
### BitVisibility Enum
| Name | Value | Description |
| :--- | :--- | :---------- |
| Visible | 0 | The content of the component is visible. |
| Hidden | 1 | The content of the component is hidden, but the space it takes on the page remains (visibility:hidden). |
| Collapsed | 2 | The component is hidden (display:none). |
### BitDir Enum
| Name | Value | Description |
| :--- | :--- | :---------- |
| Ltr | 0 | Ltr (left to right) is to be used for languages that are written from the left to the right (like English). |
| Rtl | 1 | Rtl (right to left) is to be used for languages that are written from the right to the left (like Arabic). |
| Auto | 2 | Auto lets the user agent decide. It uses a basic algorithm as it parses the characters inside the element until it finds a character with a strong directionality, then applies that directionality to the whole element. |
## Examples
\n**Basic**:
```razor
```
\n**Markers**:
```razor
Add random marker
Clear all
Open London popup
Fit to markers
markersMapRef = default!;
private readonly BitLeafletMapProvider markersProvider = new() { Center = new(48.8566, 2.3522), Zoom = 5 };
private string markersLog = "Seed markers are added on OnReady. Try the buttons.";
private int _markerCounter;
private async Task OnMarkersReady()
{
await markersMapRef.AddMarker(new BitMapMarker
{
Id = "paris", Position = new(48.8566, 2.3522),
Title = "Paris", PopupHtml = (MarkupString)"Paris
Click to open popup.", }); await markersMapRef.AddMarker(new BitMapMarker { Id = "london", Position = new(51.5074, -0.1278), Title = "London", PopupHtml = (MarkupString)"London
Draggable marker.", Draggable = true, TooltipHtml = (MarkupString)"Drag me!", }); await markersMapRef.FitBoundsToMarkers(); } private async Task AddRandomMarker() { _markerCounter++; var id = $"m{_markerCounter}"; // Scatter inside the current viewport so new markers are always visible. var view = await markersMapRef.GetView(); var sw = view.Bounds.SouthWest; var ne = view.Bounds.NorthEast; var latSpan = ne.Latitude - sw.Latitude; var lngSpan = ne.Longitude - sw.Longitude; if (lngSpan < 0) lngSpan += 360; // antimeridian const double inset = 0.1; var lat = sw.Latitude + (inset + Random.Shared.NextDouble() * (1 - 2 * inset)) * latSpan; var lng = sw.Longitude + (inset + Random.Shared.NextDouble() * (1 - 2 * inset)) * lngSpan; lat = Math.Clamp(lat, -85, 85); if (lng > 180) lng -= 360; else if (lng < -180) lng += 360; // Roll a coin so some markers come in draggable. var draggable = Random.Shared.Next(2) == 0; await markersMapRef.AddMarker(new BitMapMarker { Id = id, Position = new(lat, lng), Title = $"Marker {id}{(draggable ? " (draggable)" : "")}", PopupHtml = (MarkupString)($"Marker
{lat:F4}, {lng:F4}" + (draggable ? "
Drag me!" : "")), Draggable = draggable, TooltipHtml = draggable ? (MarkupString?)(MarkupString)"Drag me!" : null, }); markersLog = $"Added {id}{(draggable ? " (draggable)" : "")} at {lat:F4}, {lng:F4}"; } private async Task ClearMarkers() { await markersMapRef.ClearMarkers(); markersLog = "All markers cleared."; } private async Task OpenLondonPopup() { await markersMapRef.OpenMarkerPopup("london"); markersLog = "Opened London popup."; } private async Task FitToMarkers() { await markersMapRef.FitBoundsToMarkers(); markersLog = "Fitted view to all markers."; } private Task OnMarkerClick(string id) { markersLog = $"Marker click: {id}"; return Task.CompletedTask; } private Task OnMarkerDragEnd(BitMapMarkerDragEndArgs e) { markersLog = $"Drag end {e.Id} → {e.Position.Latitude:F5}, {e.Position.Longitude:F5}"; return Task.CompletedTask; } ``` \n**Vectors**: ```razor
Redraw
Clear vectors
vectorsMapRef = default!;
private readonly BitLeafletMapProvider vectorsProvider = new() { Center = new(37.7749, -122.4194), Zoom = 12 };
private string vectorsLog = "Click Redraw to draw shapes, then click a shape.";
private async Task OnVectorsReady() => await DrawVectors();
private async Task DrawVectors()
{
await vectorsMapRef.AddPolyline("route",
[
new(37.80, -122.42), new(37.79, -122.41),
new(37.78, -122.40), new(37.77, -122.395),
], new BitMapVectorPathStyle { Color = "#f85149", Weight = 5, Opacity = 0.9 });
await vectorsMapRef.AddPolygon("park",
[
new(37.769, -122.486), new(37.771, -122.475),
new(37.765, -122.472), new(37.762, -122.482),
], new BitMapVectorPathStyle { Color = "#3fb950", FillOpacity = 0.35, Weight = 2 });
await vectorsMapRef.AddCircle("radius", new(37.7849, -122.4094), 900,
new BitMapVectorPathStyle { Color = "#58a6ff", FillOpacity = 0.15, Weight = 2 });
await vectorsMapRef.AddRectangle("box",
new BitMapLatLngBounds(new(37.748, -122.44), new(37.756, -122.42)),
new BitMapVectorPathStyle { Color = "#d29922", FillOpacity = 0.12, Weight = 2, DashArray = "6,4" });
await vectorsMapRef.FitBounds(
new BitMapLatLngBounds(new(37.755, -122.49), new(37.805, -122.38)));
}
private async Task RedrawVectors()
{
await vectorsMapRef.ClearVectorLayers();
await DrawVectors();
vectorsLog = "Vectors redrawn.";
}
private async Task ClearVectors()
{
await vectorsMapRef.ClearVectorLayers();
vectorsLog = "All vector layers cleared.";
}
private Task OnVectorClick(BitMapVectorClickArgs e)
{
// e.Kind = "polyline" | "polygon" | "circle" | "rectangle"
// e.LayerId = the id you passed to AddPolyline/AddPolygon/…
vectorsLog = $"{e.Kind} \"{e.LayerId}\" @ {e.Position.Latitude:F5}, {e.Position.Longitude:F5}";
return Task.CompletedTask;
}
```
\n**GeoJSON**:
```razor
Load GeoJSON
Remove layer
geoJsonMapRef = default!;
private readonly BitLeafletMapProvider geoJsonProvider = new() { Center = new(40.7128, -74.0060), Zoom = 11 };
private string geoJsonLog = "Click 'Load GeoJSON', then click a feature.";
private async Task LoadGeoJson()
{
await geoJsonMapRef.RemoveLayer("demo");
await geoJsonMapRef.AddGeoJson("demo", SampleGeoJson,
new BitMapVectorPathStyle { Color = "#a371f7", Weight = 3, FillOpacity = 0.25 });
await geoJsonMapRef.FitBounds(new BitMapLatLngBounds(new(40.71, -74.03), new(40.83, -73.96)));
geoJsonLog = "GeoJSON loaded. Click a feature.";
}
private async Task RemoveGeoJson()
{
await geoJsonMapRef.RemoveLayer("demo");
geoJsonLog = "Layer \"demo\" removed.";
}
private Task OnGeoJsonFeatureClick(BitMapGeoJsonFeatureClickArgs e)
{
// e.LayerId = "demo"
// e.Properties = JsonElement of feature.properties
var name = "(no name)";
if (e.Properties.ValueKind == System.Text.Json.JsonValueKind.Object
&& e.Properties.TryGetProperty("name", out var n))
{
name = n.ValueKind == System.Text.Json.JsonValueKind.String ? n.GetString() : n.ToString();
}
geoJsonLog = $"Layer {e.LayerId} - properties.name = {name}";
return Task.CompletedTask;
}
// Minimal GeoJSON FeatureCollection used by LoadGeoJson above.
private const string SampleGeoJson = """
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": { "name": "Central Park" },
"geometry": {
"type": "Polygon",
"coordinates": [[
[-73.981, 40.768], [-73.958, 40.768],
[-73.958, 40.800], [-73.981, 40.800],
[-73.981, 40.768]
]]
}
},
{
"type": "Feature",
"properties": { "name": "Brooklyn Bridge" },
"geometry": {
"type": "LineString",
"coordinates": [[-73.9969, 40.7061], [-73.9875, 40.7026]]
}
}
]
}
""";
```
\n**Custom tiles**:
```razor
OSM default
Carto Voyager
OpenTopoMap
@* @key forces a new map instance when the provider changes *@
```
```csharp
private string tileProvider = "osm";
private BitLeafletMapProvider currentTileLeafletProvider = new() { Center = new(51.505, -0.09), Zoom = 13 };
private void SetTileProvider(string p)
{
tileProvider = p;
currentTileLeafletProvider = p switch
{
"carto" => new BitLeafletMapProvider
{
Center = new(20, 0), Zoom = 2,
TileUrl = "https://{s}.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}{r}.png",
TileAttribution = "© OpenStreetMap contributors © CARTO",
},
"topo" => new BitLeafletMapProvider
{
Center = new(46.5, 11.3), Zoom = 10,
TileUrl = "https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png",
TileAttribution = "Map data: © OpenStreetMap contributors, SRTM | Map style: © OpenTopoMap",
TileMaxZoom = 17,
},
_ => new BitLeafletMapProvider { Center = new(51.505, -0.09), Zoom = 13 },
};
}
```
\n**Events**:
```razor
Fly to Tokyo
Log viewport
eventsMapRef = default!;
private readonly BitLeafletMapProvider eventsProvider = new() { Center = new(35.6762, 139.6503), Zoom = 11 };
private string eventsLog = "Pan/zoom or click the map.";
private Task OnMapClick(BitMapLatLng p)
{
eventsLog = $"Click → {p.Latitude:F5}, {p.Longitude:F5}";
return Task.CompletedTask;
}
private Task OnMapDoubleClick(BitMapLatLng p)
{
eventsLog = $"Double-click → {p.Latitude:F5}, {p.Longitude:F5}";
return Task.CompletedTask;
}
private Task OnViewChanged(BitMapViewState v)
{
eventsLog = $"View: zoom {v.Zoom:F1}, center {v.Center.Latitude:F4},{v.Center.Longitude:F4}";
return Task.CompletedTask;
}
private async Task FlyToTokyo()
{
await eventsMapRef.FlyTo(new(35.6762, 139.6503), 12);
eventsLog = "Flying to Tokyo…";
}
private async Task ReadView()
{
var v = await eventsMapRef.GetView();
eventsLog = $"GetView → zoom {v.Zoom:F2}, center {v.Center.Latitude:F4},{v.Center.Longitude:F4}, " +
$"NE {v.Bounds.NorthEast.Latitude:F4},{v.Bounds.NorthEast.Longitude:F4}";
}
```
\n**Advanced**:
```razor
@* Bind a stable field, not a method call: a method call reallocates the provider on every render. *@
Add tooltip markers + fit
@(advOverlayOn ? "Remove overlay" : "Add tile overlay")
Log viewport
advMapRef = default!;
private bool advScrollWheel = true;
private bool advDragging = true;
private bool advScaleBar = true;
private bool advMaxBounds;
private bool advOverlayOn;
private string advLog = "Toggle options or use the buttons.";
private BitLeafletMapProvider advProvider = new()
{
Center = new(51.5074, -0.1278), Zoom = 11,
ScrollWheelZoom = true,
Dragging = true,
ShowScaleControl = true,
MaxBounds = null,
};
// Mutate the stable field only when an option actually changes - not on every render.
private BitLeafletMapProvider BuildAdvancedProvider()
{
advProvider = new BitLeafletMapProvider
{
Center = new(51.5074, -0.1278), Zoom = 11,
ScrollWheelZoom = advScrollWheel,
Dragging = advDragging,
ShowScaleControl = advScaleBar,
MaxBounds = advMaxBounds
? new BitMapLatLngBounds(new(51.25, -0.55), new(51.75, 0.35))
: null,
};
// Rebuilding the provider replaces the underlying Leaflet map instance,
// so any previously-added overlays no longer exist on the new map.
// Reset the toggle state so the UI label/branch reflects that.
advOverlayOn = false;
return advProvider;
}
private async Task OnAdvancedReady() => await AddTooltipMarkers();
private async Task AddTooltipMarkers()
{
await advMapRef.ClearMarkers();
await advMapRef.AddMarker(new BitMapMarker { Id = "a", Position = new(51.52, -0.10), TooltipHtml = (MarkupString)"West End", PopupHtml = (MarkupString)"Popup A", ZIndexOffset = 10 });
await advMapRef.AddMarker(new BitMapMarker { Id = "b", Position = new(51.50, -0.08), TooltipHtml = (MarkupString)"City", PopupHtml = (MarkupString)"Popup B" });
await advMapRef.AddMarker(new BitMapMarker { Id = "c", Position = new(51.48, -0.06), TooltipHtml = (MarkupString)"South Bank", PopupHtml = (MarkupString)"Popup C" });
await advMapRef.FitBoundsToMarkers(56);
advLog = "Three tooltip markers added; view fitted.";
}
private async Task ToggleTileOverlay()
{
if (advOverlayOn)
{
await advMapRef.RemoveTileOverlay("labels");
advOverlayOn = false;
advLog = "Tile overlay removed.";
}
else
{
await advMapRef.AddTileOverlay(new BitMapTileOverlay
{
Id = "labels",
UrlTemplate = "https://tiles.stadiamaps.com/tiles/stamen_toner_labels/{z}/{x}/{y}{r}.png",
Attribution = "Map tiles by Stamen Design, hosted by Stadia Maps. Data by OpenStreetMap.",
Opacity = 0.85,
ZIndex = 400,
MaxZoom = 20,
});
advOverlayOn = true;
advLog = "Tile overlay added (may fail if the tile host blocks your origin).";
}
}
private async Task ReadAdvancedView()
{
var v = await advMapRef.GetView();
advLog = $"GetView → zoom {v.Zoom:F2}, center {v.Center.Latitude:F4},{v.Center.Longitude:F4}, " +
$"NE {v.Bounds.NorthEast.Latitude:F4},{v.Bounds.NorthEast.Longitude:F4}";
}
private Task OnAdvancedDoubleClick(BitMapLatLng p)
{
advLog = $"Double-click at {p.Latitude:F4}, {p.Longitude:F4}";
return Task.CompletedTask;
}
```
\n**MapLibre GL**:
```razor
```
```csharp
// Bind a stable field so the provider isn't reallocated on every render.
private readonly BitMapLibreMapProvider maplibreProvider = new() { Center = new(48.8566, 2.3522), Zoom = 5 };
```
\n**OpenLayers**:
```razor
```
```csharp
// Bind a stable field so the provider isn't reallocated on every render.
private readonly BitOpenLayersMapProvider olProvider = new() { Center = new(35.6762, 139.6503), Zoom = 4 };
```
\n**Mapbox GL (token required)**:
```razor
```
```csharp
// Get your token from https://account.mapbox.com/access-tokens/
// and pass it via the AccessToken property on BitMapboxMapProvider.
// Bind a stable field so the provider isn't reallocated on every render.
private readonly BitMapboxMapProvider mapboxProvider = new()
{
AccessToken = "YOUR_MAPBOX_TOKEN",
Center = new(40, 0),
Zoom = 2,
};
```
\n**ArcGIS Maps SDK**:
```razor
```
```csharp
// Bind a stable field so the provider isn't reallocated on every render.
private readonly BitArcGisMapProvider arcGisProvider = new() { Center = new(40, 0), Zoom = 2, BasemapId = "osm" };
```
\n**Azure Maps (subscription key required)**:
```razor
```
```csharp
// Get your key from Azure Portal > Maps account > Authentication > Shared Key
// and pass it via the SubscriptionKey property on BitAzureMapsMapProvider.
// Bind a stable field so the provider isn't reallocated on every render.
private readonly BitAzureMapsMapProvider azureMapsProvider = new()
{
SubscriptionKey = "YOUR_AZURE_MAPS_KEY",
Center = new(40, 0),
Zoom = 2,
};
```
\n**CesiumJS 3D globe**:
```razor
```
```csharp
// Bind a stable field so the provider isn't reallocated on every render.
private readonly BitCesiumMapProvider cesiumProvider = new() { Center = new(20, 0), Zoom = 2, SceneMode = "scene3d" };
```
@markersLog``` ```csharp private BitMap
Click to open popup.", }); await markersMapRef.AddMarker(new BitMapMarker { Id = "london", Position = new(51.5074, -0.1278), Title = "London", PopupHtml = (MarkupString)"London
Draggable marker.", Draggable = true, TooltipHtml = (MarkupString)"Drag me!", }); await markersMapRef.FitBoundsToMarkers(); } private async Task AddRandomMarker() { _markerCounter++; var id = $"m{_markerCounter}"; // Scatter inside the current viewport so new markers are always visible. var view = await markersMapRef.GetView(); var sw = view.Bounds.SouthWest; var ne = view.Bounds.NorthEast; var latSpan = ne.Latitude - sw.Latitude; var lngSpan = ne.Longitude - sw.Longitude; if (lngSpan < 0) lngSpan += 360; // antimeridian const double inset = 0.1; var lat = sw.Latitude + (inset + Random.Shared.NextDouble() * (1 - 2 * inset)) * latSpan; var lng = sw.Longitude + (inset + Random.Shared.NextDouble() * (1 - 2 * inset)) * lngSpan; lat = Math.Clamp(lat, -85, 85); if (lng > 180) lng -= 360; else if (lng < -180) lng += 360; // Roll a coin so some markers come in draggable. var draggable = Random.Shared.Next(2) == 0; await markersMapRef.AddMarker(new BitMapMarker { Id = id, Position = new(lat, lng), Title = $"Marker {id}{(draggable ? " (draggable)" : "")}", PopupHtml = (MarkupString)($"Marker
{id}{lat:F4}, {lng:F4}" + (draggable ? "
Drag me!" : "")), Draggable = draggable, TooltipHtml = draggable ? (MarkupString?)(MarkupString)"Drag me!" : null, }); markersLog = $"Added {id}{(draggable ? " (draggable)" : "")} at {lat:F4}, {lng:F4}"; } private async Task ClearMarkers() { await markersMapRef.ClearMarkers(); markersLog = "All markers cleared."; } private async Task OpenLondonPopup() { await markersMapRef.OpenMarkerPopup("london"); markersLog = "Opened London popup."; } private async Task FitToMarkers() { await markersMapRef.FitBoundsToMarkers(); markersLog = "Fitted view to all markers."; } private Task OnMarkerClick(string id) { markersLog = $"Marker click: {id}"; return Task.CompletedTask; } private Task OnMarkerDragEnd(BitMapMarkerDragEndArgs e) { markersLog = $"Drag end {e.Id} → {e.Position.Latitude:F5}, {e.Position.Longitude:F5}"; return Task.CompletedTask; } ``` \n**Vectors**: ```razor
@vectorsLog``` ```csharp private BitMap
@geoJsonLog``` ```csharp private BitMap
@eventsLog``` ```csharp private BitMap
@advLog``` ```csharp private BitMap