diff --git a/BlazorLeaflet/BlazorLeaflet.Samples/Pages/Index.razor b/BlazorLeaflet/BlazorLeaflet.Samples/Pages/Index.razor
index a1183e9..c8d693b 100644
--- a/BlazorLeaflet/BlazorLeaflet.Samples/Pages/Index.razor
+++ b/BlazorLeaflet/BlazorLeaflet.Samples/Pages/Index.razor
@@ -14,11 +14,14 @@
+
+Map bounds: @_map.Bounds?.ToString()
+
@code
{
@@ -67,6 +70,7 @@
};
_map.AddLayer(_circle);
};
+ _map.OnBoundsChanged += (s, e) => this.StateHasChanged();
}
private LatLng _startAt = new LatLng(47.5574007f, 16.3918687f);
@@ -102,12 +106,18 @@
private void ZoomMap()
{
- _map.FitBounds(new PointF(45.943f, 24.967f), new PointF(46.943f, 25.967f), maxZoom: 5f);
- }
-
- private void PanToNY()
- {
- _map.PanTo(new PointF(40.713185f, -74.0072333f), animate: true, duration: 10f);
+ _map.FitBounds(new PointF(45.943f, 24.967f), new PointF(46.943f, 25.967f), maxZoom: 5f);
+ }
+
+ private void PanToNY()
+ {
+ _map.PanTo(new PointF(40.713185f, -74.0072333f), animate: true, duration: 10f);
+ }
+
+ private void SetToNY()
+ {
+ _map.Center = new LatLng(new PointF(40.713185f, -74.0072333f));
+ _map.Zoom = 15;
}
}
diff --git a/BlazorLeaflet/BlazorLeaflet/LeafletInterops.cs b/BlazorLeaflet/BlazorLeaflet/LeafletInterops.cs
index 369338b..94a726e 100644
--- a/BlazorLeaflet/BlazorLeaflet/LeafletInterops.cs
+++ b/BlazorLeaflet/BlazorLeaflet/LeafletInterops.cs
@@ -1,6 +1,7 @@
-using BlazorLeaflet.Models;
+using BlazorLeaflet.Models;
using Microsoft.JSInterop;
using System;
+using System.Collections.Generic;
using System.Drawing;
using System.Threading.Tasks;
using Rectangle = BlazorLeaflet.Models.Rectangle;
@@ -10,29 +11,56 @@ namespace BlazorLeaflet
public static class LeafletInterops
{
+ private static Dictionary LayerReferences { get; }
+ = new Dictionary();
+
private static readonly string _BaseObjectContainer = "window.leafletBlazor";
public static ValueTask Create(IJSRuntime jsRuntime, Map map) =>
jsRuntime.InvokeVoidAsync($"{_BaseObjectContainer}.create", map, DotNetObjectReference.Create(map));
- public static ValueTask AddLayer(IJSRuntime jsRuntime, string mapId, Layer layer) =>
- layer switch
+ private static DotNetObjectReference CreateLayerReference(string mapId, T layer) where T : Layer
+ {
+ var result = DotNetObjectReference.Create(layer);
+ LayerReferences.Add(layer.Id, (result, mapId, layer));
+ return result;
+ }
+
+ private static void DisposeLayerReference(string layerId)
+ {
+ if (LayerReferences.TryGetValue(layerId, out var value))
+ {
+ value.Item1.Dispose();
+ LayerReferences.Remove(layerId);
+ }
+ }
+
+ public static ValueTask AddLayer(IJSRuntime jsRuntime, string mapId, Layer layer)
+ {
+ return layer switch
{
- TileLayer tileLayer => jsRuntime.InvokeVoidAsync($"{_BaseObjectContainer}.addTilelayer", mapId, tileLayer, DotNetObjectReference.Create(tileLayer)),
- MbTilesLayer mbTilesLayer => jsRuntime.InvokeVoidAsync($"{_BaseObjectContainer}.addMbTilesLayer", mapId, mbTilesLayer, DotNetObjectReference.Create(mbTilesLayer)),
- ShapefileLayer shapefileLayer => jsRuntime.InvokeVoidAsync($"{_BaseObjectContainer}.addShapefileLayer", mapId, shapefileLayer, DotNetObjectReference.Create(shapefileLayer)),
- Marker marker => jsRuntime.InvokeVoidAsync($"{_BaseObjectContainer}.addMarker", mapId, marker, DotNetObjectReference.Create(marker)),
- Rectangle rectangle => jsRuntime.InvokeVoidAsync($"{_BaseObjectContainer}.addRectangle", mapId, rectangle, DotNetObjectReference.Create(rectangle)),
- Circle circle => jsRuntime.InvokeVoidAsync($"{_BaseObjectContainer}.addCircle", mapId, circle, DotNetObjectReference.Create(circle)),
- Polygon polygon => jsRuntime.InvokeVoidAsync($"{_BaseObjectContainer}.addPolygon", mapId, polygon, DotNetObjectReference.Create(polygon)),
- Polyline polyline => jsRuntime.InvokeVoidAsync($"{_BaseObjectContainer}.addPolyline", mapId, polyline, DotNetObjectReference.Create(polyline)),
- ImageLayer image => jsRuntime.InvokeVoidAsync($"{_BaseObjectContainer}.addImageLayer", mapId, image, DotNetObjectReference.Create(image)),
- GeoJsonDataLayer geo => jsRuntime.InvokeVoidAsync($"{_BaseObjectContainer}.addGeoJsonLayer", mapId, geo, DotNetObjectReference.Create(geo)),
+ TileLayer tileLayer => jsRuntime.InvokeVoidAsync($"{_BaseObjectContainer}.addTilelayer", mapId, tileLayer, CreateLayerReference(mapId, tileLayer)),
+ MbTilesLayer mbTilesLayer => jsRuntime.InvokeVoidAsync($"{_BaseObjectContainer}.addMbTilesLayer", mapId, mbTilesLayer, CreateLayerReference(mapId, mbTilesLayer)),
+ ShapefileLayer shapefileLayer => jsRuntime.InvokeVoidAsync($"{_BaseObjectContainer}.addShapefileLayer", mapId, shapefileLayer, CreateLayerReference(mapId, shapefileLayer)),
+ Marker marker => jsRuntime.InvokeVoidAsync($"{_BaseObjectContainer}.addMarker", mapId, marker, CreateLayerReference(mapId, marker)),
+ Rectangle rectangle => jsRuntime.InvokeVoidAsync($"{_BaseObjectContainer}.addRectangle", mapId, rectangle, CreateLayerReference(mapId, rectangle)),
+ Circle circle => jsRuntime.InvokeVoidAsync($"{_BaseObjectContainer}.addCircle", mapId, circle, CreateLayerReference(mapId, circle)),
+ Polygon polygon => jsRuntime.InvokeVoidAsync($"{_BaseObjectContainer}.addPolygon", mapId, polygon, CreateLayerReference(mapId, polygon)),
+ Polyline polyline => jsRuntime.InvokeVoidAsync($"{_BaseObjectContainer}.addPolyline", mapId, polyline, CreateLayerReference(mapId, polyline)),
+ ImageLayer image => jsRuntime.InvokeVoidAsync($"{_BaseObjectContainer}.addImageLayer", mapId, image, CreateLayerReference(mapId, image)),
+ GeoJsonDataLayer geo => jsRuntime.InvokeVoidAsync($"{_BaseObjectContainer}.addGeoJsonLayer", mapId, geo, CreateLayerReference(mapId, geo)),
_ => throw new NotImplementedException($"The layer {typeof(Layer).Name} has not been implemented."),
- };
+ };
+ }
- public static ValueTask RemoveLayer(IJSRuntime jsRuntime, string mapId, string layerId) =>
- jsRuntime.InvokeVoidAsync($"{_BaseObjectContainer}.removeLayer", mapId, layerId);
+ public static async ValueTask RemoveLayer(IJSRuntime jsRuntime, string mapId, string layerId)
+ {
+ await jsRuntime.InvokeVoidAsync($"{_BaseObjectContainer}.removeLayer", mapId, layerId);
+ DisposeLayerReference(layerId);
+ }
+
+ public static ValueTask SetMarkerIcon(IJSRuntime jsRuntime, string mapId, string layerId, Icon icon)
+ => jsRuntime.InvokeVoidAsync($"{_BaseObjectContainer}.setMarkerIcon", mapId, layerId, icon);
public static ValueTask UpdatePopupContent(IJSRuntime jsRuntime, string mapId, Layer layer) =>
jsRuntime.InvokeVoidAsync($"{_BaseObjectContainer}.updatePopupContent", mapId, layer.Id, layer.Popup?.Content);
@@ -61,5 +89,23 @@ layer switch
public static ValueTask GetZoom(IJSRuntime jsRuntime, string mapId) =>
jsRuntime.InvokeAsync($"{_BaseObjectContainer}.getZoom", mapId);
+
+ private class _Bounds
+ {
+ public LatLng _southWest { get; set; }
+ public LatLng _northEast { get; set; }
+
+ public Bounds AsBounds() => new Bounds(_southWest, _northEast);
+ }
+
+ public static async Task GetBounds(IJSRuntime jsRuntime, string mapId)
+ {
+ return (await jsRuntime.InvokeAsync<_Bounds>($"{_BaseObjectContainer}.getBounds", mapId)).AsBounds();
+ }
+
+ public static async Task SetZoom(IJSRuntime jsRuntime, string mapId, float zoomLevel)
+ {
+ await jsRuntime.InvokeVoidAsync($"{_BaseObjectContainer}.setZoom", mapId, zoomLevel);
+ }
}
}
diff --git a/BlazorLeaflet/BlazorLeaflet/Map.cs b/BlazorLeaflet/BlazorLeaflet/Map.cs
index 84cd33c..58e793b 100644
--- a/BlazorLeaflet/BlazorLeaflet/Map.cs
+++ b/BlazorLeaflet/BlazorLeaflet/Map.cs
@@ -15,16 +15,44 @@ namespace BlazorLeaflet
{
public class Map
{
+ private LatLng _Center = new LatLng();
///
- /// Initial geographic center of the map
+ /// Geographic center of the map
///
- public LatLng Center { get; set; } = new LatLng();
+ ///
+ public LatLng Center
+ {
+ get => _Center;
+ set
+ {
+ _Center = value;
+ if (_isInitialized)
+ RunTaskInBackground(async () => await LeafletInterops.PanTo(
+ _jsRuntime, Id, value.ToPointF(), false, 0, 0, false));
+ }
+ }
+ private float _Zoom;
///
- /// Initial map zoom level
+ /// Map zoom level
///
- public float Zoom { get; set; }
-
+ public float Zoom
+ {
+ get => _Zoom;
+ set
+ {
+ _Zoom = value;
+ if (_isInitialized)
+ RunTaskInBackground(async () => await LeafletInterops.SetZoom(
+ _jsRuntime, Id, value));
+ }
+ }
+
+ ///
+ /// Map bounds
+ ///
+ public Bounds Bounds { get; private set; }
+
///
/// Minimum zoom level of the map. If not specified and at least one
/// GridLayer or TileLayer is in the map, the lowest of their minZoom
@@ -74,6 +102,19 @@ public void RaiseOnInitialized()
{
_isInitialized = true;
OnInitialized?.Invoke();
+ RunTaskInBackground(UpdateBounds);
+ }
+
+ private async void RunTaskInBackground(Func task)
+ {
+ try
+ {
+ await task();
+ }
+ catch (Exception ex)
+ {
+ NotifyBackgroundExceptionOccurred(ex);
+ }
}
///
@@ -171,6 +212,26 @@ public void PanTo(PointF position, bool animate = false, float duration = 0.25f,
public async Task GetCenter() => await LeafletInterops.GetCenter(_jsRuntime, Id);
public async Task GetZoom() => await LeafletInterops.GetZoom(_jsRuntime, Id);
+ public async Task GetBounds() => await LeafletInterops.GetBounds(_jsRuntime, Id);
+
+ private async Task UpdateZoom()
+ {
+ _Zoom = await GetZoom();
+ await UpdateBounds();
+ }
+
+ private async Task UpdateCenter()
+ {
+
+ _Center = await GetCenter();
+ await UpdateBounds();
+ }
+
+ private async Task UpdateBounds()
+ {
+ Bounds = await GetBounds();
+ OnBoundsChanged?.Invoke(this, new EventArgs());
+ }
#region events
@@ -215,11 +276,33 @@ public void PanTo(PointF position, bool animate = false, float duration = 0.25f,
public event MapEventHandler OnZoomEnd;
[JSInvokable]
- public void NotifyZoomEnd(Event e) => OnZoomEnd?.Invoke(this, e);
+ public async void NotifyZoomEnd(Event e)
+ {
+ try
+ {
+ await UpdateZoom();
+ }
+ finally
+ {
+ OnZoomEnd?.Invoke(this, e);
+ }
+ }
public event MapEventHandler OnMoveEnd;
[JSInvokable]
- public void NotifyMoveEnd(Event e) => OnMoveEnd?.Invoke(this, e);
+ public async void NotifyMoveEnd(Event e)
+ {
+ try
+ {
+ await UpdateCenter();
+ }
+ finally
+ {
+ OnMoveEnd?.Invoke(this, e);
+ }
+ }
+
+ public event EventHandler OnBoundsChanged;
public event MouseEventHandler OnMouseMove;
[JSInvokable]
@@ -241,6 +324,10 @@ public void PanTo(PointF position, bool animate = false, float duration = 0.25f,
[JSInvokable]
public void NotifyPreClick(MouseEvent eventArgs) => OnPreClick?.Invoke(this, eventArgs);
+ public event EventHandler BackgroundExceptionOccurred;
+ private void NotifyBackgroundExceptionOccurred(Exception exception) =>
+ BackgroundExceptionOccurred?.Invoke(this, exception);
+
#endregion events
#region InteractiveLayerEvents
diff --git a/BlazorLeaflet/BlazorLeaflet/Models/Bounds.cs b/BlazorLeaflet/BlazorLeaflet/Models/Bounds.cs
new file mode 100644
index 0000000..d05c0e7
--- /dev/null
+++ b/BlazorLeaflet/BlazorLeaflet/Models/Bounds.cs
@@ -0,0 +1,26 @@
+using System;
+using System.Collections.Generic;
+using System.Runtime.Serialization;
+using System.Text;
+
+namespace BlazorLeaflet.Models
+{
+ public class Bounds
+ {
+ [DataMember(Name = "_northEast")]
+ public LatLng NorthEast { get; set; }
+
+ [DataMember(Name = "_southWest")]
+ public LatLng SouthWest { get; set; }
+
+ public Bounds() { }
+ public Bounds(LatLng southWest, LatLng northEast)
+ {
+ NorthEast = northEast;
+ SouthWest = southWest;
+ }
+
+ public override string ToString() =>
+ $"NE: {NorthEast.Lat} N, {NorthEast.Lng} E; SW: {SouthWest.Lat} N, {SouthWest.Lng} E";
+ }
+}
diff --git a/BlazorLeaflet/BlazorLeaflet/wwwroot/leafletBlazorInterops.js b/BlazorLeaflet/BlazorLeaflet/wwwroot/leafletBlazorInterops.js
index 88c53e1..0c04a65 100644
--- a/BlazorLeaflet/BlazorLeaflet/wwwroot/leafletBlazorInterops.js
+++ b/BlazorLeaflet/BlazorLeaflet/wwwroot/leafletBlazorInterops.js
@@ -1,4 +1,4 @@
-maps = {};
+maps = {};
layers = {};
window.leafletBlazor = {
@@ -206,6 +206,12 @@ window.leafletBlazor = {
},
getZoom: function (mapId) {
return maps[mapId].getZoom();
+ },
+ getBounds: function (mapId) {
+ return maps[mapId].getBounds();
+ },
+ setZoom: function (mapId, zoomLevel) {
+ maps[mapId].setZoom(zoomLevel);
}
};