diff --git a/samples/AzureMapsControl.Sample/Components/Layout/NavMenu.razor b/samples/AzureMapsControl.Sample/Components/Layout/NavMenu.razor
index 4f33778..4a0e462 100644
--- a/samples/AzureMapsControl.Sample/Components/Layout/NavMenu.razor
+++ b/samples/AzureMapsControl.Sample/Components/Layout/NavMenu.razor
@@ -86,6 +86,9 @@
Toolbar update
+
+ Load data to drawing manager
+
Indoor
diff --git a/samples/AzureMapsControl.Sample/Components/Pages/Drawing/DrawingManagerLoadData.razor b/samples/AzureMapsControl.Sample/Components/Pages/Drawing/DrawingManagerLoadData.razor
new file mode 100644
index 0000000..6a88084
--- /dev/null
+++ b/samples/AzureMapsControl.Sample/Components/Pages/Drawing/DrawingManagerLoadData.razor
@@ -0,0 +1,103 @@
+@page "/Drawing/DrawingManagerLoadData"
+@rendermode InteractiveServer
+
+@using AzureMapsControl.Components.Atlas
+@using AzureMapsControl.Components.Drawing
+@using AzureMapsControl.Components.Map
+
+
+
+
+
+
+
+
+
+@code {
+ private DrawingManager? _drawingManager;
+ private Position _center = new Position(-122.33, 47.6);
+
+ public async Task MapReady(MapEventArgs eventArgs)
+ {
+ await eventArgs.Map.SetCameraOptionsAsync(options =>
+ {
+ options.Zoom = 10;
+ options.Center = _center;
+ });
+ await eventArgs.Map.AddDrawingToolbarAsync(new AzureMapsControl.Components.Drawing.DrawingToolbarOptions
+ {
+ Buttons = new[]
+ {
+ AzureMapsControl.Components.Drawing.DrawingButton.DrawCircle,
+ AzureMapsControl.Components.Drawing.DrawingButton.DrawLine,
+ AzureMapsControl.Components.Drawing.DrawingButton.EditGeometry
+ },
+ Position = AzureMapsControl.Components.Controls.ControlPosition.TopRight,
+ Style = AzureMapsControl.Components.Drawing.DrawingToolbarStyle.Dark
+ });
+
+ var lineString = new AzureMapsControl.Components.Atlas.LineString(new[]
+ {
+ new AzureMapsControl.Components.Atlas.Position(-122.27577, 47.55938),
+ new AzureMapsControl.Components.Atlas.Position(-122.29705, 47.60662),
+ new AzureMapsControl.Components.Atlas.Position(-122.22358, 47.6367)
+ });
+ var shape = new AzureMapsControl.Components.Atlas.Shape(lineString);
+ _drawingManager = eventArgs.Map.DrawingManager;
+ await _drawingManager.AddShapesAsync(new[] { shape });
+ }
+
+ private async Task AddRandomShape()
+ {
+ if (_drawingManager == null) return;
+
+ var random = new Random();
+ var shapeType = random.Next(3);
+ Shape shape;
+ var numberOfPoints = random.Next(3, 5);
+ var center = new Position(_center.Longitude + (random.NextDouble()-0.5) * 0.6, _center.Latitude + (random.NextDouble()-0.5) * 0.4);
+
+ switch (shapeType)
+ {
+ case 0: // Circle
+ var radius = random.NextDouble() * 2000;
+ shape = new Shape(new Point(center), new Dictionary
+ {
+ { "subType", "Circle" },
+ { "radius", radius }
+ });
+ break;
+ case 1: // Polygon
+ var polygonPositions = new List();
+ for (var i = 0; i < numberOfPoints; i++)
+ {
+ polygonPositions.Add(new Position(center.Longitude + (random.NextDouble()-0.5) * 0.1, center.Latitude + (random.NextDouble()-0.5) * 0.1));
+ }
+ polygonPositions.Add(polygonPositions[0]);
+ shape = new Shape(new Polygon(new[] { polygonPositions }));
+ break;
+ case 2: // Polyline
+ var polylinePositions = new List();
+ for (var i = 0; i < numberOfPoints; i++)
+ {
+ polylinePositions.Add(new Position(center.Longitude + (random.NextDouble()-0.5) * 0.1, center.Latitude + (random.NextDouble()-0.5) * 0.1));
+ }
+ shape = new Shape(new LineString(polylinePositions));
+ break;
+ default:
+ return;
+ }
+
+ await _drawingManager.AddShapesAsync(new[] { shape });
+ }
+
+ private async Task ClearShapes()
+ {
+ if (_drawingManager != null)
+ {
+ await _drawingManager.ClearAsync();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/AzureMapsControl.Components/Drawing/DrawingManager.cs b/src/AzureMapsControl.Components/Drawing/DrawingManager.cs
new file mode 100644
index 0000000..1fe6879
--- /dev/null
+++ b/src/AzureMapsControl.Components/Drawing/DrawingManager.cs
@@ -0,0 +1,151 @@
+namespace AzureMapsControl.Components.Drawing
+{
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Threading.Tasks;
+
+ using AzureMapsControl.Components.Atlas;
+ using AzureMapsControl.Components.Logger;
+ using AzureMapsControl.Components.Runtime;
+
+ using Microsoft.Extensions.Logging;
+
+
+ ///
+ /// DrawingManager for the DrawingToolbar
+ ///
+ public sealed class DrawingManager
+ {
+ internal IMapJsRuntime JSRuntime { get; set; }
+ internal ILogger Logger { get; set; }
+ public bool Disposed { get; private set; }
+
+ ///
+ /// List of shapes added to the data source
+ ///
+ private List _sourceShapes;
+
+ ///
+ /// Add shapes to the drawing manager data source
+ ///
+ /// Shapes to add
+ ///
+ /// The control has not been added to the map
+ /// The control has already been disposed
+ public async ValueTask AddShapesAsync(IEnumerable shapes)
+ {
+ if (shapes == null || !shapes.Any())
+ {
+ return;
+ }
+
+ EnsureJsRuntimeExists();
+ EnsureNotDisposed();
+
+ if (_sourceShapes == null)
+ {
+ _sourceShapes = new List();
+ }
+
+ var lineStrings = shapes.OfType>();
+ if (lineStrings.Any())
+ {
+ Logger?.LogAzureMapsControlDebug(AzureMapLogEvent.Source_AddAsync, $"{lineStrings.Count()} linestrings will be added");
+ await JSRuntime.InvokeVoidAsync(Constants.JsConstants.Methods.Source.AddShapes.ToDrawingNamespace(), lineStrings);
+ }
+
+ var multiLineStrings = shapes.OfType>();
+ if (multiLineStrings.Any())
+ {
+ Logger?.LogAzureMapsControlDebug(AzureMapLogEvent.Source_AddAsync, $"{multiLineStrings.Count()} multilinestrings will be added");
+ await JSRuntime.InvokeVoidAsync(Constants.JsConstants.Methods.Source.AddShapes.ToDrawingNamespace(), multiLineStrings);
+ }
+
+ var multiPoints = shapes.OfType>();
+ if (multiPoints.Any())
+ {
+ Logger?.LogAzureMapsControlDebug(AzureMapLogEvent.Source_AddAsync, $"{multiPoints.Count()} multipoints will be added");
+ await JSRuntime.InvokeVoidAsync(Constants.JsConstants.Methods.Source.AddShapes.ToDrawingNamespace(), multiPoints);
+ }
+
+ var multiPolygons = shapes.OfType>();
+ if (multiPolygons.Any())
+ {
+ Logger?.LogAzureMapsControlDebug(AzureMapLogEvent.Source_AddAsync, $"{multiPolygons.Count()} multipolygons will be added");
+ await JSRuntime.InvokeVoidAsync(Constants.JsConstants.Methods.Source.AddShapes.ToDrawingNamespace(), multiPolygons);
+ }
+
+ var points = shapes.OfType>();
+ if (points.Any())
+ {
+ Logger?.LogAzureMapsControlDebug(AzureMapLogEvent.Source_AddAsync, $"{points.Count()} points will be added");
+ await JSRuntime.InvokeVoidAsync(Constants.JsConstants.Methods.Source.AddShapes.ToDrawingNamespace(), points);
+ }
+
+ var polygons = shapes.OfType>();
+ if (polygons.Any())
+ {
+ Logger?.LogAzureMapsControlDebug(AzureMapLogEvent.Source_AddAsync, $"{polygons.Count()} polygons will be added");
+ await JSRuntime.InvokeVoidAsync(Constants.JsConstants.Methods.Source.AddShapes.ToDrawingNamespace(), polygons);
+ }
+
+ var routePoints = shapes.OfType>();
+ if (routePoints.Any())
+ {
+ Logger?.LogAzureMapsControlDebug(AzureMapLogEvent.Source_AddAsync, $"{routePoints.Count()} route points will be added");
+ await JSRuntime.InvokeVoidAsync(Constants.JsConstants.Methods.Source.AddShapes.ToDrawingNamespace(), routePoints);
+ }
+
+ _sourceShapes.AddRange(shapes);
+ }
+
+ ///
+ /// Clear the drawing manager source
+ ///
+ ///
+ /// The control has not been added to the map
+ /// The control has already been disposed
+ public async ValueTask ClearAsync()
+ {
+ Logger?.LogAzureMapsControlInfo(AzureMapLogEvent.Source_ClearAsync, "Clearing drawing manager source");
+
+ EnsureJsRuntimeExists();
+ EnsureNotDisposed();
+
+ _sourceShapes = null;
+ await JSRuntime.InvokeVoidAsync(Constants.JsConstants.Methods.Source.Clear.ToDrawingNamespace());
+ }
+
+ ///
+ /// Mark the control as disposed
+ ///
+ ///
+ /// The control has not been added to the map
+ /// The control has already been disposed
+ internal void Dispose()
+ {
+ Logger?.LogAzureMapsControlInfo(AzureMapLogEvent.Source_DisposeAsync, "DrawingManager - Dispose");
+
+ EnsureJsRuntimeExists();
+ EnsureNotDisposed();
+
+ Disposed = true;
+ }
+
+ private void EnsureJsRuntimeExists()
+ {
+ if (JSRuntime is null)
+ {
+ throw new Exceptions.ComponentNotAddedToMapException();
+ }
+ }
+
+ private void EnsureNotDisposed()
+ {
+ if (Disposed)
+ {
+ throw new Exceptions.ComponentDisposedException();
+ }
+ }
+ }
+}
diff --git a/src/AzureMapsControl.Components/Map/Map.cs b/src/AzureMapsControl.Components/Map/Map.cs
index 0bfee93..3b28206 100644
--- a/src/AzureMapsControl.Components/Map/Map.cs
+++ b/src/AzureMapsControl.Components/Map/Map.cs
@@ -67,6 +67,8 @@ public sealed class Map
public DrawingToolbarOptions DrawingToolbarOptions { get; internal set; }
+ public DrawingManager DrawingManager { get; internal set; }
+
public IEnumerable Controls => _controls;
public IEnumerable Layers => _layers;
@@ -352,6 +354,10 @@ await _jsRuntime.InvokeVoidAsync(Constants.JsConstants.Methods.Drawing.AddDrawin
Events = drawingToolbarOptions.Events?.EnabledEvents
},
DotNetObjectReference.Create(_drawingToolbarEventInvokeHelper));
+ DrawingManager = new DrawingManager() {
+ JSRuntime = _jsRuntime,
+ Logger = _logger
+ };
}
}
@@ -394,6 +400,8 @@ public async ValueTask RemoveDrawingToolbarAsync()
{
await _jsRuntime.InvokeVoidAsync(Constants.JsConstants.Methods.Drawing.RemoveDrawingToolbar.ToDrawingNamespace());
DrawingToolbarOptions = null;
+ DrawingManager?.Dispose();
+ DrawingManager = null;
}
}
diff --git a/src/AzureMapsControl.Components/typescript/drawing/drawing.ts b/src/AzureMapsControl.Components/typescript/drawing/drawing.ts
index 4959cac..69120bf 100644
--- a/src/AzureMapsControl.Components/typescript/drawing/drawing.ts
+++ b/src/AzureMapsControl.Components/typescript/drawing/drawing.ts
@@ -3,6 +3,8 @@ import * as azmaps from 'azure-maps-control';
import { EventHelper } from '../events/event-helper';
import { Core } from '../core/core';
import { DrawingEventArgs } from './drawing-event-args';
+import { Shape } from '../geometries/geometry';
+import { GeometryBuilder } from '../geometries/geometry-builder';
export class Drawing {
@@ -96,4 +98,13 @@ export class Drawing {
});
}
+ public static addShapes(shapes: Shape[]): void {
+ const mapsShapes = shapes.map(shape => GeometryBuilder.buildShape(shape));
+ this._drawingManager.getSource().add(mapsShapes);
+ }
+
+ public static clear(): void {
+ this._drawingManager.getSource().clear();
+ }
+
}
\ No newline at end of file
diff --git a/tests/AzureMapsControl.Components.Tests/Drawing/DrawingManager.cs b/tests/AzureMapsControl.Components.Tests/Drawing/DrawingManager.cs
new file mode 100644
index 0000000..f6eab30
--- /dev/null
+++ b/tests/AzureMapsControl.Components.Tests/Drawing/DrawingManager.cs
@@ -0,0 +1,313 @@
+namespace AzureMapsControl.Components.Tests.Drawing
+{
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Reflection;
+ using System.Threading.Tasks;
+
+ using AzureMapsControl.Components.Atlas;
+ using AzureMapsControl.Components.Drawing;
+ using AzureMapsControl.Components.Exceptions;
+ using AzureMapsControl.Components.Runtime;
+
+ using Microsoft.Extensions.Logging;
+
+ using Moq;
+
+ using Xunit;
+
+ public class DrawingManagerTests
+ {
+ private readonly Mock _jsRuntimeMock = new();
+ private readonly Mock _loggerMock = new();
+
+ [Fact]
+ public void Should_HaveDefaultProperties()
+ {
+ var drawingManager = new DrawingManager();
+
+ Assert.False(drawingManager.Disposed);
+ Assert.Null(drawingManager.JSRuntime);
+ Assert.Null(drawingManager.Logger);
+ }
+
+ [Fact]
+ public void Should_SetJSRuntimeAndLogger()
+ {
+ var drawingManager = CreateInitializedDrawingManager();
+
+ Assert.Equal(_jsRuntimeMock.Object, drawingManager.JSRuntime);
+ Assert.Equal(_loggerMock.Object, drawingManager.Logger);
+ }
+
+ [Fact]
+ public async Task Should_AddShapes_AllSupportedGeometryTypes_Async()
+ {
+ var drawingManager = CreateInitializedDrawingManager();
+
+ var shapes = new List
+ {
+ new Shape(new Point()),
+ new Shape(new LineString()),
+ new Shape(new MultiLineString()),
+ new Shape(new MultiPoint()),
+ new Shape(new MultiPolygon()),
+ new Shape(new Polygon()),
+ new Shape(new RoutePoint()),
+ };
+
+ await drawingManager.AddShapesAsync(shapes);
+
+ _jsRuntimeMock.Verify(runtime => runtime.InvokeVoidAsync(Constants.JsConstants.Methods.Source.AddShapes.ToDrawingNamespace(), It.Is>>(s => s.Count() == 1)), Times.Once);
+ _jsRuntimeMock.Verify(runtime => runtime.InvokeVoidAsync(Constants.JsConstants.Methods.Source.AddShapes.ToDrawingNamespace(), It.Is>>(s => s.Count() == 1)), Times.Once);
+ _jsRuntimeMock.Verify(runtime => runtime.InvokeVoidAsync(Constants.JsConstants.Methods.Source.AddShapes.ToDrawingNamespace(), It.Is>>(s => s.Count() == 1)), Times.Once);
+ _jsRuntimeMock.Verify(runtime => runtime.InvokeVoidAsync(Constants.JsConstants.Methods.Source.AddShapes.ToDrawingNamespace(), It.Is>>(s => s.Count() == 1)), Times.Once);
+ _jsRuntimeMock.Verify(runtime => runtime.InvokeVoidAsync(Constants.JsConstants.Methods.Source.AddShapes.ToDrawingNamespace(), It.Is>>(s => s.Count() == 1)), Times.Once);
+ _jsRuntimeMock.Verify(runtime => runtime.InvokeVoidAsync(Constants.JsConstants.Methods.Source.AddShapes.ToDrawingNamespace(), It.Is>>(s => s.Count() == 1)), Times.Once);
+ _jsRuntimeMock.Verify(runtime => runtime.InvokeVoidAsync(Constants.JsConstants.Methods.Source.AddShapes.ToDrawingNamespace(), It.Is>>(s => s.Count() == 1)), Times.Once);
+ _jsRuntimeMock.Verify(runtime => runtime.InvokeVoidAsync(Constants.JsConstants.Methods.Source.AddShapes.ToDrawingNamespace(), It.IsAny