From 14bff50cab181f20a5d9549b04accad75c6b7388 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=85dne=20Sigurd=20Leirdal?= Date: Tue, 5 Nov 2024 18:08:34 +0100 Subject: [PATCH 01/11] Added marker clustering --- .../wwwroot/appsettings.json | 2 +- .../wwwroot/index.html | 2 + .../Components/Maps/GoogleMap.razor.cs | 15 +++++++- blazorbootstrap/wwwroot/blazor.bootstrap.js | 38 +++++++++++++++++-- 4 files changed, 51 insertions(+), 6 deletions(-) diff --git a/BlazorBootstrap.Demo.WebAssembly/wwwroot/appsettings.json b/BlazorBootstrap.Demo.WebAssembly/wwwroot/appsettings.json index a6f4631bf..4acbd9b52 100644 --- a/BlazorBootstrap.Demo.WebAssembly/wwwroot/appsettings.json +++ b/BlazorBootstrap.Demo.WebAssembly/wwwroot/appsettings.json @@ -15,6 +15,6 @@ "stackoverflow": "//stackoverflow.com/questions/tagged/blazor-bootstrap" }, "GoogleMap": { - "ApiKey": "AIzaSyDc110Rvu20IMJhlZcWTOPoLbVQdnjLyXs" + "ApiKey": "AIzaSyDug1LSoAWJ_UStV8cZmhhfPRfFsdzrS6Y" } } \ No newline at end of file diff --git a/BlazorBootstrap.Demo.WebAssembly/wwwroot/index.html b/BlazorBootstrap.Demo.WebAssembly/wwwroot/index.html index 0fe3f5706..7646df527 100644 --- a/BlazorBootstrap.Demo.WebAssembly/wwwroot/index.html +++ b/BlazorBootstrap.Demo.WebAssembly/wwwroot/index.html @@ -59,6 +59,8 @@ + + diff --git a/blazorbootstrap/Components/Maps/GoogleMap.razor.cs b/blazorbootstrap/Components/Maps/GoogleMap.razor.cs index 90613c4b5..a2efa01e7 100644 --- a/blazorbootstrap/Components/Maps/GoogleMap.razor.cs +++ b/blazorbootstrap/Components/Maps/GoogleMap.razor.cs @@ -42,7 +42,7 @@ public async Task OnMarkerClickJS(GoogleMapMarker marker) /// A completed task. public ValueTask RefreshAsync() { - JSRuntime.InvokeVoidAsync("window.blazorBootstrap.googlemaps.initialize", Id, Zoom, Center, Markers, Clickable, objRef); + JSRuntime.InvokeVoidAsync("window.blazorBootstrap.googlemaps.initialize", Id, Zoom, Center, Markers, Clickable, EnableClustering, objRef); return ValueTask.CompletedTask; } @@ -60,7 +60,7 @@ public ValueTask UpdateMarkersAsync(IEnumerable markers) private void OnScriptLoad() { - Task.Run(async () => await JSRuntime.InvokeVoidAsync("window.blazorBootstrap.googlemaps.initialize", Id, Zoom, Center, Markers, Clickable, objRef)); + Task.Run(async () => await JSRuntime.InvokeVoidAsync("window.blazorBootstrap.googlemaps.initialize", Id, Zoom, Center, Markers, Clickable, EnableClustering, objRef)); } #endregion @@ -151,6 +151,17 @@ private void OnScriptLoad() /// [Parameter] public int Zoom { get; set; } = 14; + + /// + /// Determines whether markers on the map should be clustered when they are close together. + /// When enabled, multiple markers in proximity will be combined into a single cluster marker showing the count of points. + /// This helps reduce visual clutter when there are many markers in a small area. + /// + /// + /// Default value is false. + /// + [Parameter] + public bool EnableClustering { get; set; } = true; #endregion } diff --git a/blazorbootstrap/wwwroot/blazor.bootstrap.js b/blazorbootstrap/wwwroot/blazor.bootstrap.js index 055742f0d..81ec9e85b 100644 --- a/blazorbootstrap/wwwroot/blazor.bootstrap.js +++ b/blazorbootstrap/wwwroot/blazor.bootstrap.js @@ -498,21 +498,36 @@ window.blazorBootstrap = { dotNetHelper.invokeMethodAsync('OnMarkerClickJS', marker); }); } + console.info(mapInstance.markerCluster); + if (mapInstance.markerCluster) { + // Check if the marker is already in the cluster + console.info("We are here"); + const markers = mapInstance.markerCluster.markers; + const markerExists = markers.includes(markerEl); + + if (!markerExists) { + mapInstance.markerCluster.markers.add(markerEl); + } else { + mapInstance.markerCluster.render(); + } + } + } }, - create: (elementId, map, zoom, center, markers, clickable) => { + create: (elementId, map, zoom, center, markers, clickable, enableClustering) => { window.blazorBootstrap.googlemaps.instances[elementId] = { map: map, zoom: zoom, center: center, markers: markers, - clickable: clickable + clickable: clickable, + enableClustering : enableClustering }; }, get: (elementId) => { return window.blazorBootstrap.googlemaps.instances[elementId]; }, - initialize: (elementId, zoom, center, markers, clickable, dotNetHelper) => { + initialize: (elementId, zoom, center, markers, clickable, enableClustering, dotNetHelper) => { window.blazorBootstrap.googlemaps.markerEls[elementId] = window.blazorBootstrap.googlemaps.markerEls[elementId] ?? []; let mapOptions = { center: center, zoom: zoom, mapId: elementId }; @@ -525,6 +540,19 @@ window.blazorBootstrap = { window.blazorBootstrap.googlemaps.addMarker(elementId, marker, dotNetHelper); } } + // Initialize marker clustering after all markers are added + if(enableClustering) { + const mapInstance = window.blazorBootstrap.googlemaps.get(elementId); + mapInstance.markerCluster = new markerClusterer.MarkerClusterer({ + map: mapInstance.map, + markers: window.blazorBootstrap.googlemaps.markerEls[elementId], + // You can change the clustering algorithm like so: + // algorithm: new markerClusterer.SuperClusterAlgorithm({ + // radius: 100, + // maxZoom: 15 + // }) + }); + } }, instances: {}, markerEls: {}, @@ -543,6 +571,10 @@ window.blazorBootstrap = { window.blazorBootstrap.googlemaps.addMarker(elementId, marker, dotNetHelper); } } + const mapInstance = window.blazorBootstrap.googlemaps.get(elementId); + if(mapInstance.markerCluster) { + mapInstance.markerCluster.markers = markers; + } } }, grid: { From b4870bc4a8c79d16a9498ea38053ddbb99570bfa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=85dne-Sigurd=20Leirdal?= Date: Wed, 6 Nov 2024 14:53:27 +0100 Subject: [PATCH 02/11] Fixed clustering for both when disabled and enabled --- blazorbootstrap/wwwroot/blazor.bootstrap.js | 62 +++++++++++++-------- 1 file changed, 39 insertions(+), 23 deletions(-) diff --git a/blazorbootstrap/wwwroot/blazor.bootstrap.js b/blazorbootstrap/wwwroot/blazor.bootstrap.js index 81ec9e85b..8ba4ef48c 100644 --- a/blazorbootstrap/wwwroot/blazor.bootstrap.js +++ b/blazorbootstrap/wwwroot/blazor.bootstrap.js @@ -498,15 +498,12 @@ window.blazorBootstrap = { dotNetHelper.invokeMethodAsync('OnMarkerClickJS', marker); }); } - console.info(mapInstance.markerCluster); if (mapInstance.markerCluster) { - // Check if the marker is already in the cluster - console.info("We are here"); const markers = mapInstance.markerCluster.markers; const markerExists = markers.includes(markerEl); if (!markerExists) { - mapInstance.markerCluster.markers.add(markerEl); + mapInstance.markerCluster.addMarker(markerEl); } else { mapInstance.markerCluster.render(); } @@ -528,29 +525,38 @@ window.blazorBootstrap = { return window.blazorBootstrap.googlemaps.instances[elementId]; }, initialize: (elementId, zoom, center, markers, clickable, enableClustering, dotNetHelper) => { - window.blazorBootstrap.googlemaps.markerEls[elementId] = window.blazorBootstrap.googlemaps.markerEls[elementId] ?? []; - + window.blazorBootstrap.googlemaps.markerEls[elementId] ??= []; + + // clean up just the clustering if it exists + const existingInstance = window.blazorBootstrap.googlemaps.get(elementId); + if (existingInstance?.markerCluster) { + existingInstance.markerCluster.setMap(null); + existingInstance.markerCluster = null; + } + let mapOptions = { center: center, zoom: zoom, mapId: elementId }; let map = new google.maps.Map(document.getElementById(elementId), mapOptions); window.blazorBootstrap.googlemaps.create(elementId, map, zoom, center, markers, clickable); - if (markers) { + + if(!enableClustering) + window.blazorBootstrap.googlemaps.markerEls[elementId].forEach(marker => { + marker.map = map; + }); + + // don't recreate markers if they already exist + if (markers && window.blazorBootstrap.googlemaps.markerEls[elementId].length === 0) { for (const marker of markers) { window.blazorBootstrap.googlemaps.addMarker(elementId, marker, dotNetHelper); } } - // Initialize marker clustering after all markers are added + if(enableClustering) { const mapInstance = window.blazorBootstrap.googlemaps.get(elementId); mapInstance.markerCluster = new markerClusterer.MarkerClusterer({ - map: mapInstance.map, - markers: window.blazorBootstrap.googlemaps.markerEls[elementId], - // You can change the clustering algorithm like so: - // algorithm: new markerClusterer.SuperClusterAlgorithm({ - // radius: 100, - // maxZoom: 15 - // }) + map: map, + markers: window.blazorBootstrap.googlemaps.markerEls[elementId] }); } }, @@ -558,22 +564,32 @@ window.blazorBootstrap = { markerEls: {}, updateMarkers: (elementId, markers, dotNetHelper) => { let markerEls = window.blazorBootstrap.googlemaps.markerEls[elementId] ?? []; + const mapInstance = window.blazorBootstrap.googlemaps.get(elementId); + const clusteringEnabled = !!mapInstance.markerCluster; + // Clean up cluster first if it exists + if (clusteringEnabled) { + mapInstance.markerCluster.clearMarkers(); + mapInstance.markerCluster.setMap(null); + } - // delete the markers - if (markerEls.length > 0) { - for (const markerEl of markerEls) { - markerEl.setMap(null); - } + // Clear all existing markers from the map + for (const markerEl of markerEls) { + markerEl.map = null; } + // Reset marker array and add new markers + window.blazorBootstrap.googlemaps.markerEls[elementId] = []; if (markers) { for (const marker of markers) { window.blazorBootstrap.googlemaps.addMarker(elementId, marker, dotNetHelper); } } - const mapInstance = window.blazorBootstrap.googlemaps.get(elementId); - if(mapInstance.markerCluster) { - mapInstance.markerCluster.markers = markers; + + if (clusteringEnabled) { + mapInstance.markerCluster = new markerClusterer.MarkerClusterer({ + map: mapInstance.map, + markers: window.blazorBootstrap.googlemaps.markerEls[elementId] + }); } } }, From 2e85213c7b2e971063dec6e938d235657831141c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=85dne-Sigurd=20Leirdal?= Date: Wed, 6 Nov 2024 14:59:39 +0100 Subject: [PATCH 03/11] Optimized updateMarkers --- blazorbootstrap/wwwroot/blazor.bootstrap.js | 24 ++++++++++----------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/blazorbootstrap/wwwroot/blazor.bootstrap.js b/blazorbootstrap/wwwroot/blazor.bootstrap.js index 8ba4ef48c..bb4c3d925 100644 --- a/blazorbootstrap/wwwroot/blazor.bootstrap.js +++ b/blazorbootstrap/wwwroot/blazor.bootstrap.js @@ -526,25 +526,19 @@ window.blazorBootstrap = { }, initialize: (elementId, zoom, center, markers, clickable, enableClustering, dotNetHelper) => { window.blazorBootstrap.googlemaps.markerEls[elementId] ??= []; - + + let mapOptions = { center: center, zoom: zoom, mapId: elementId }; + let map = new google.maps.Map(document.getElementById(elementId), mapOptions); + + window.blazorBootstrap.googlemaps.create(elementId, map, zoom, center, markers, clickable); + // clean up just the clustering if it exists const existingInstance = window.blazorBootstrap.googlemaps.get(elementId); if (existingInstance?.markerCluster) { existingInstance.markerCluster.setMap(null); existingInstance.markerCluster = null; - } - - let mapOptions = { center: center, zoom: zoom, mapId: elementId }; - let map = new google.maps.Map(document.getElementById(elementId), mapOptions); - - window.blazorBootstrap.googlemaps.create(elementId, map, zoom, center, markers, clickable); + } - - if(!enableClustering) - window.blazorBootstrap.googlemaps.markerEls[elementId].forEach(marker => { - marker.map = map; - }); - // don't recreate markers if they already exist if (markers && window.blazorBootstrap.googlemaps.markerEls[elementId].length === 0) { for (const marker of markers) { @@ -558,6 +552,10 @@ window.blazorBootstrap = { map: map, markers: window.blazorBootstrap.googlemaps.markerEls[elementId] }); + } else { + window.blazorBootstrap.googlemaps.markerEls[elementId].forEach(marker => { + marker.map = map; + }); } }, instances: {}, From b5d06dbe9c6c4a5739408507047963cf5b3fd2f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=85dne-Sigurd=20Leirdal?= Date: Fri, 8 Nov 2024 16:39:34 +0100 Subject: [PATCH 04/11] Custom clusters implemented, added two demos for Maps --- .../Pages/Maps/GoogleMapDocumentation.razor | 8 + ...o_07_Dynamic_markers_with_clustering.razor | 169 ++++++++++++++++ ...namic_markers_with_custom_clustering.razor | 186 ++++++++++++++++++ .../Components/Maps/GoogleMap.razor.cs | 29 ++- .../Models/Maps/GoogleMapClusterOptions.cs | 43 ++++ blazorbootstrap/wwwroot/blazor.bootstrap.css | 9 + blazorbootstrap/wwwroot/blazor.bootstrap.js | 71 ++++++- 7 files changed, 498 insertions(+), 17 deletions(-) create mode 100644 BlazorBootstrap.Demo.RCL/Components/Pages/Maps/GoogleMap_Demo_07_Dynamic_markers_with_clustering.razor create mode 100644 BlazorBootstrap.Demo.RCL/Components/Pages/Maps/GoogleMap_Demo_08_Dynamic_markers_with_custom_clustering.razor create mode 100644 blazorbootstrap/Models/Maps/GoogleMapClusterOptions.cs diff --git a/BlazorBootstrap.Demo.RCL/Components/Pages/Maps/GoogleMapDocumentation.razor b/BlazorBootstrap.Demo.RCL/Components/Pages/Maps/GoogleMapDocumentation.razor index ecc5f8e76..9c0ee9538 100644 --- a/BlazorBootstrap.Demo.RCL/Components/Pages/Maps/GoogleMapDocumentation.razor +++ b/BlazorBootstrap.Demo.RCL/Components/Pages/Maps/GoogleMapDocumentation.razor @@ -71,6 +71,14 @@ +
+ +
+ +
+ +
+ @code { private const string pageUrl = RouteConstants.Demos_GoogleMap_Documentation; private const string pageTitle = "Blazor Google Map"; diff --git a/BlazorBootstrap.Demo.RCL/Components/Pages/Maps/GoogleMap_Demo_07_Dynamic_markers_with_clustering.razor b/BlazorBootstrap.Demo.RCL/Components/Pages/Maps/GoogleMap_Demo_07_Dynamic_markers_with_clustering.razor new file mode 100644 index 000000000..b63ab3dfb --- /dev/null +++ b/BlazorBootstrap.Demo.RCL/Components/Pages/Maps/GoogleMap_Demo_07_Dynamic_markers_with_clustering.razor @@ -0,0 +1,169 @@ +@inherits GoogleMapDemoComponentBase + +
+ + + +
+ + + +@code { + Random random = new Random(2000000000); + GoogleMap googleMapRef = default!; + GoogleMapClusterOptions clusterOptions = new GoogleMapClusterOptions() + { + ClusteringEnabled = true, + }; + + [Inject] public ToastService ToastService { get; set; } = default!; + + private async ValueTask AddWeatherMarkerAsync() => await googleMapRef.AddMarkerAsync(GetRandomMarker()); + + private async Task UpdateWeatherMarkersAsync() + { + var markerList = new List + { + GetRandomMarker(), + GetRandomMarker(), + GetRandomMarker(), + GetRandomMarker(), + GetRandomMarker(), + GetRandomMarker(), + }; + await googleMapRef.UpdateMarkersAsync(markerList); + } + + private async Task RefreshMapAsync() + { + markers.Add(GetRandomMarker()); + markers.Add(GetRandomMarker()); + + await googleMapRef.RefreshAsync(); + } + + private void OnGoogleMapMarkerClick(GoogleMapMarker marker) + { + ToastService.Notify(new ToastMessage(ToastType.Success, $"{marker.Title}", $"This is a toast message for a weather forecast. DateTime: {DateTime.Now}")); + } + + List markers = new() + { + new GoogleMapMarker() + { + PinElement = new PinElement + { + Glyph = "bi bi-cloud-drizzle-fill fs-6 text-white", + UseIconFonts = true, + Background=ColorUtility.CategoricalSixColors[0].ToColor().ToRgbaString().ToLowerInvariant(), + BorderColor=ColorUtility.CategoricalSixColors[0].ToColor().ToRgbString().ToLowerInvariant() + }, + Position = new GoogleMapMarkerPosition(37.50024109655184, -122.28528451834352), + Title = "Drizzle", + }, + new GoogleMapMarker() + { + PinElement = new PinElement + { + Glyph = "bi bi-cloud-drizzle-fill fs-6 text-white", + UseIconFonts = true, + Background=ColorUtility.CategoricalSixColors[0].ToColor().ToRgbaString().ToLowerInvariant(), + BorderColor=ColorUtility.CategoricalSixColors[0].ToColor().ToRgbString().ToLowerInvariant() + }, + Position = new GoogleMapMarkerPosition(37.44440882321596, -122.2160620727), + Title = "Drizzle", + }, + new GoogleMapMarker() + { + PinElement = new PinElement + { + Glyph = "bi bi-cloud-lightning-rain-fill fs-6 text-white", + UseIconFonts = true, + Background=ColorUtility.CategoricalSixColors[2].ToColor().ToRgbaString().ToLowerInvariant(), + BorderColor=ColorUtility.CategoricalSixColors[2].ToColor().ToRgbString().ToLowerInvariant() + }, + Position = new GoogleMapMarkerPosition(37.39561833718522, -122.21855116258479), + Title = "Lightning rain", + }, + new GoogleMapMarker() + { + PinElement = new PinElement + { + Glyph = "bi bi-cloud-lightning-rain-fill fs-6 text-white", + UseIconFonts = true, + Background=ColorUtility.CategoricalSixColors[2].ToColor().ToRgbaString().ToLowerInvariant(), + BorderColor=ColorUtility.CategoricalSixColors[2].ToColor().ToRgbString().ToLowerInvariant() + }, + Position = new GoogleMapMarkerPosition(37.423928529779644, -122.1087629822001), + Title = "Lightning rain", + }, + new GoogleMapMarker() + { + PinElement = new PinElement + { + Glyph = "bi bi-cloud-rain-fill fs-6 text-white", + UseIconFonts = true, + Background=ColorUtility.CategoricalSixColors[1].ToColor().ToRgbaString().ToLowerInvariant(), + BorderColor=ColorUtility.CategoricalSixColors[1].ToColor().ToRgbString().ToLowerInvariant() + }, + Position = new GoogleMapMarkerPosition(37.40578635332598, -122.15043378466069), + Title = "Rain", + }, + new GoogleMapMarker() + { + PinElement = new PinElement + { + Glyph = "bi bi-cloud-rain-fill fs-6 text-white", + UseIconFonts = true, + Background=ColorUtility.CategoricalSixColors[1].ToColor().ToRgbaString().ToLowerInvariant(), + BorderColor=ColorUtility.CategoricalSixColors[1].ToColor().ToRgbString().ToLowerInvariant() + }, + Position = new GoogleMapMarkerPosition(37.36399747905774, -122.10465384268522), + Title = "Rain", + }, + new GoogleMapMarker() + { + PinElement = new PinElement + { + Glyph = "bi bi-cloud-rain-heavy-fill fs-6 text-white", + UseIconFonts = true, + Background=ColorUtility.CategoricalSixColors[3].ToColor().ToRgbaString().ToLowerInvariant(), + BorderColor=ColorUtility.CategoricalSixColors[3].ToColor().ToRgbString().ToLowerInvariant() + }, + Position = new GoogleMapMarkerPosition(37.38343706184458, -122.02340436985183), + Title = "Heavy rain", + } + }; + + private GoogleMapMarker GetRandomMarker() + { + var lat = Double.Parse($"37.{random.Next()}"); + var lng = Double.Parse($"-122.{random.Next()}"); + return new GoogleMapMarker() + { + PinElement = new PinElement + { + Glyph = "bi bi-cloud-rain-heavy-fill fs-6 text-white", + UseIconFonts = true, + Background = ColorUtility.CategoricalTwelveColors[9].ToColor().ToRgbaString().ToLowerInvariant(), + BorderColor = ColorUtility.CategoricalTwelveColors[9].ToColor().ToRgbString().ToLowerInvariant() + }, + Position = new GoogleMapMarkerPosition(lat, lng), + Title = "Heavy rain", + }; + } +} \ No newline at end of file diff --git a/BlazorBootstrap.Demo.RCL/Components/Pages/Maps/GoogleMap_Demo_08_Dynamic_markers_with_custom_clustering.razor b/BlazorBootstrap.Demo.RCL/Components/Pages/Maps/GoogleMap_Demo_08_Dynamic_markers_with_custom_clustering.razor new file mode 100644 index 000000000..eadb710f8 --- /dev/null +++ b/BlazorBootstrap.Demo.RCL/Components/Pages/Maps/GoogleMap_Demo_08_Dynamic_markers_with_custom_clustering.razor @@ -0,0 +1,186 @@ +@inherits GoogleMapDemoComponentBase + +
+ + + +
+ + + +@code { + Random random = new Random(2000000000); + GoogleMap googleMapRef = default!; + GoogleMapClusterOptions clusterOptions = new GoogleMapClusterOptions() + { + ClusteringEnabled = true, + EnableClusterClick = true, + // Algorithm = new GoogleMapClusterAlgorithm + // { + // Type = "SuperClusterAlgorithm", + // Options = new Dictionary + // { + // { "radius", 100 }, + // { "maxZoom", 15 } + // } + // }, + Renderer = new GoogleMapClusterRenderer + { + TextColor = "#ff0000", + TextFontSize = "30px", + SvgIcon = @"", + } + + }; + + [Inject] public ToastService ToastService { get; set; } = default!; + + private async ValueTask AddWeatherMarkerAsync() => await googleMapRef.AddMarkerAsync(GetRandomMarker()); + + private async Task UpdateWeatherMarkersAsync() + { + var markerList = new List + { + GetRandomMarker(), + GetRandomMarker(), + GetRandomMarker(), + GetRandomMarker(), + GetRandomMarker(), + GetRandomMarker(), + }; + await googleMapRef.UpdateMarkersAsync(markerList); + } + + private async Task RefreshMapAsync() + { + markers.Add(GetRandomMarker()); + markers.Add(GetRandomMarker()); + + await googleMapRef.RefreshAsync(); + } + + private void OnGoogleMapMarkerClick(GoogleMapMarker marker) + { + ToastService.Notify(new ToastMessage(ToastType.Success, $"{marker.Title}", $"This is a toast message for a weather forecast. DateTime: {DateTime.Now}")); + } + + List markers = new() + { + new GoogleMapMarker() + { + PinElement = new PinElement + { + Glyph = "bi bi-cloud-drizzle-fill fs-6 text-white", + UseIconFonts = true, + Background=ColorUtility.CategoricalSixColors[0].ToColor().ToRgbaString().ToLowerInvariant(), + BorderColor=ColorUtility.CategoricalSixColors[0].ToColor().ToRgbString().ToLowerInvariant() + }, + Position = new GoogleMapMarkerPosition(37.50024109655184, -122.28528451834352), + Title = "Drizzle", + }, + new GoogleMapMarker() + { + PinElement = new PinElement + { + Glyph = "bi bi-cloud-drizzle-fill fs-6 text-white", + UseIconFonts = true, + Background=ColorUtility.CategoricalSixColors[0].ToColor().ToRgbaString().ToLowerInvariant(), + BorderColor=ColorUtility.CategoricalSixColors[0].ToColor().ToRgbString().ToLowerInvariant() + }, + Position = new GoogleMapMarkerPosition(37.44440882321596, -122.2160620727), + Title = "Drizzle", + }, + new GoogleMapMarker() + { + PinElement = new PinElement + { + Glyph = "bi bi-cloud-lightning-rain-fill fs-6 text-white", + UseIconFonts = true, + Background=ColorUtility.CategoricalSixColors[2].ToColor().ToRgbaString().ToLowerInvariant(), + BorderColor=ColorUtility.CategoricalSixColors[2].ToColor().ToRgbString().ToLowerInvariant() + }, + Position = new GoogleMapMarkerPosition(37.39561833718522, -122.21855116258479), + Title = "Lightning rain", + }, + new GoogleMapMarker() + { + PinElement = new PinElement + { + Glyph = "bi bi-cloud-lightning-rain-fill fs-6 text-white", + UseIconFonts = true, + Background=ColorUtility.CategoricalSixColors[2].ToColor().ToRgbaString().ToLowerInvariant(), + BorderColor=ColorUtility.CategoricalSixColors[2].ToColor().ToRgbString().ToLowerInvariant() + }, + Position = new GoogleMapMarkerPosition(37.423928529779644, -122.1087629822001), + Title = "Lightning rain", + }, + new GoogleMapMarker() + { + PinElement = new PinElement + { + Glyph = "bi bi-cloud-rain-fill fs-6 text-white", + UseIconFonts = true, + Background=ColorUtility.CategoricalSixColors[1].ToColor().ToRgbaString().ToLowerInvariant(), + BorderColor=ColorUtility.CategoricalSixColors[1].ToColor().ToRgbString().ToLowerInvariant() + }, + Position = new GoogleMapMarkerPosition(37.40578635332598, -122.15043378466069), + Title = "Rain", + }, + new GoogleMapMarker() + { + PinElement = new PinElement + { + Glyph = "bi bi-cloud-rain-fill fs-6 text-white", + UseIconFonts = true, + Background=ColorUtility.CategoricalSixColors[1].ToColor().ToRgbaString().ToLowerInvariant(), + BorderColor=ColorUtility.CategoricalSixColors[1].ToColor().ToRgbString().ToLowerInvariant() + }, + Position = new GoogleMapMarkerPosition(37.36399747905774, -122.10465384268522), + Title = "Rain", + }, + new GoogleMapMarker() + { + PinElement = new PinElement + { + Glyph = "bi bi-cloud-rain-heavy-fill fs-6 text-white", + UseIconFonts = true, + Background=ColorUtility.CategoricalSixColors[3].ToColor().ToRgbaString().ToLowerInvariant(), + BorderColor=ColorUtility.CategoricalSixColors[3].ToColor().ToRgbString().ToLowerInvariant() + }, + Position = new GoogleMapMarkerPosition(37.38343706184458, -122.02340436985183), + Title = "Heavy rain", + } + }; + + private GoogleMapMarker GetRandomMarker() + { + var lat = Double.Parse($"37.{random.Next()}"); + var lng = Double.Parse($"-122.{random.Next()}"); + return new GoogleMapMarker() + { + PinElement = new PinElement + { + Glyph = "bi bi-cloud-rain-heavy-fill fs-6 text-white", + UseIconFonts = true, + Background = ColorUtility.CategoricalTwelveColors[9].ToColor().ToRgbaString().ToLowerInvariant(), + BorderColor = ColorUtility.CategoricalTwelveColors[9].ToColor().ToRgbString().ToLowerInvariant() + }, + Position = new GoogleMapMarkerPosition(lat, lng), + Title = "Heavy rain", + }; + } +} \ No newline at end of file diff --git a/blazorbootstrap/Components/Maps/GoogleMap.razor.cs b/blazorbootstrap/Components/Maps/GoogleMap.razor.cs index a2efa01e7..f418abe08 100644 --- a/blazorbootstrap/Components/Maps/GoogleMap.razor.cs +++ b/blazorbootstrap/Components/Maps/GoogleMap.razor.cs @@ -35,6 +35,13 @@ public async Task OnMarkerClickJS(GoogleMapMarker marker) if (OnMarkerClick.HasDelegate) await OnMarkerClick.InvokeAsync(marker); } + + [JSInvokable] + public async Task OnClusterClickJS(GoogleMapClusterClickEvent clusterEvent) + { + if (OnClusterClick.HasDelegate) + await OnClusterClick.InvokeAsync(clusterEvent); + } /// /// Refreshes the Google Map component. @@ -42,7 +49,7 @@ public async Task OnMarkerClickJS(GoogleMapMarker marker) /// A completed task. public ValueTask RefreshAsync() { - JSRuntime.InvokeVoidAsync("window.blazorBootstrap.googlemaps.initialize", Id, Zoom, Center, Markers, Clickable, EnableClustering, objRef); + JSRuntime.InvokeVoidAsync("window.blazorBootstrap.googlemaps.initialize", Id, Zoom, Center, Markers, Clickable, ClusterOptions, objRef); return ValueTask.CompletedTask; } @@ -60,7 +67,7 @@ public ValueTask UpdateMarkersAsync(IEnumerable markers) private void OnScriptLoad() { - Task.Run(async () => await JSRuntime.InvokeVoidAsync("window.blazorBootstrap.googlemaps.initialize", Id, Zoom, Center, Markers, Clickable, EnableClustering, objRef)); + Task.Run(async () => await JSRuntime.InvokeVoidAsync("window.blazorBootstrap.googlemaps.initialize", Id, Zoom, Center, Markers, Clickable, ClusterOptions, objRef)); } #endregion @@ -151,17 +158,19 @@ private void OnScriptLoad() /// [Parameter] public int Zoom { get; set; } = 14; - + /// - /// Determines whether markers on the map should be clustered when they are close together. - /// When enabled, multiple markers in proximity will be combined into a single cluster marker showing the count of points. - /// This helps reduce visual clutter when there are many markers in a small area. + /// Gets or sets the clustering options for the map. /// - /// - /// Default value is false. - /// [Parameter] - public bool EnableClustering { get; set; } = true; + public GoogleMapClusterOptions? ClusterOptions { get; set; } = new(); + /// + /// Event fired when a user clicks on a cluster. + /// This event fires only when EnableClustering is true and ClusterOptions.EnableClusterClick is true. + /// + [Parameter] + public EventCallback OnClusterClick { get; set; } + #endregion } diff --git a/blazorbootstrap/Models/Maps/GoogleMapClusterOptions.cs b/blazorbootstrap/Models/Maps/GoogleMapClusterOptions.cs new file mode 100644 index 000000000..79059b1d3 --- /dev/null +++ b/blazorbootstrap/Models/Maps/GoogleMapClusterOptions.cs @@ -0,0 +1,43 @@ +namespace BlazorBootstrap +{ + /// + /// Represents the options for clustering markers on a Google Map. + /// To enable clustering, set to true. + /// + /// + /// Default value for is false. + /// + public class GoogleMapClusterOptions + { + public GoogleMapClusterRenderer? Renderer { get; set; } + public GoogleMapClusterAlgorithm? Algorithm { get; set; } + public bool ClusteringEnabled { get; set; } = false; + public bool EnableClusterClick { get; set; } + } + + public class GoogleMapClusterAlgorithm + { + public string Type { get; set; } = "SuperClusterAlgorithm"; + public Dictionary Options { get; set; } = new(); + } + + /// + /// Cluster renderer for Google Maps. Be aware that the properties only applies if you have a custom SVG icon. + /// + public class GoogleMapClusterRenderer + { + // Properties for the cluster count + public string? TextColor { get; set; } + public string? TextFontSize { get; set; } + public bool ShowMarkerCount { get; set; } = true; + // Properties for the cluster icon + public string? SvgIcon { get; set; } + + } + + public class GoogleMapClusterClickEvent + { + public GoogleMapMarkerPosition Position { get; set; } = default!; + public IEnumerable Markers { get; set; } = default!; + } +} \ No newline at end of file diff --git a/blazorbootstrap/wwwroot/blazor.bootstrap.css b/blazorbootstrap/wwwroot/blazor.bootstrap.css index 047277b75..366549205 100644 --- a/blazorbootstrap/wwwroot/blazor.bootstrap.css +++ b/blazorbootstrap/wwwroot/blazor.bootstrap.css @@ -189,6 +189,13 @@ table button.dropdown-toggle.bb-grid-filter::after { padding: .375rem; } +/* google maps */ +.bb-googlemaps-marker-fix { + position: absolute; + transform: translate(-50%, -50%); +} + + /* grid - fixed header */ .bb-table { /* NOTE: intentionally overriding the behavior */ @@ -638,3 +645,5 @@ main { color: var(--bs-link-hover-color); text-decoration: underline; } + + diff --git a/blazorbootstrap/wwwroot/blazor.bootstrap.js b/blazorbootstrap/wwwroot/blazor.bootstrap.js index bb4c3d925..f49f4e83d 100644 --- a/blazorbootstrap/wwwroot/blazor.bootstrap.js +++ b/blazorbootstrap/wwwroot/blazor.bootstrap.js @@ -474,6 +474,13 @@ window.blazorBootstrap = { else if (marker.content) { _content = document.createElement("div"); _content.classList.add("bb-google-marker-content"); + + // fixes SVG misalignment on zoom out - Adlei + const hasSVG = marker.content.includes(' { + create: (elementId, map, zoom, center, markers, clickable, clusterOptions) => { window.blazorBootstrap.googlemaps.instances[elementId] = { map: map, zoom: zoom, center: center, markers: markers, clickable: clickable, - enableClustering : enableClustering + clusterOptions : clusterOptions }; }, get: (elementId) => { return window.blazorBootstrap.googlemaps.instances[elementId]; }, - initialize: (elementId, zoom, center, markers, clickable, enableClustering, dotNetHelper) => { + initialize: (elementId, zoom, center, markers, clickable, clusterOptions, dotNetHelper) => { window.blazorBootstrap.googlemaps.markerEls[elementId] ??= []; let mapOptions = { center: center, zoom: zoom, mapId: elementId }; @@ -545,13 +552,63 @@ window.blazorBootstrap = { window.blazorBootstrap.googlemaps.addMarker(elementId, marker, dotNetHelper); } } - - if(enableClustering) { + + // add clustering if enabled, if not keep the markers as it is + if(clusterOptions?.clusteringEnabled) { const mapInstance = window.blazorBootstrap.googlemaps.get(elementId); - mapInstance.markerCluster = new markerClusterer.MarkerClusterer({ + const clusterConfig = { map: map, markers: window.blazorBootstrap.googlemaps.markerEls[elementId] - }); + }; + + + if (clusterOptions?.renderer) { + clusterConfig.renderer = { + render: ({ count, position }) => { + // Create custom marker element + const div = document.createElement("div"); + if (clusterOptions.renderer.svgIcon) { + const countStyle = `position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)` + + (clusterOptions.renderer.textColor ? `;color:${clusterOptions.renderer.textColor}` : '') + + (clusterOptions.renderer.textFontSize ? `;font-size:${clusterOptions.renderer.textFontSize}` : ''); + + div.innerHTML = ` +
+
${clusterOptions.renderer.svgIcon}
+ ${clusterOptions.renderer.showMarkerCount ? `
${count}
` : ''} +
`; + } + // Return as an advanced marker element + return new google.maps.marker.AdvancedMarkerElement({ + position, + content: div + }); + } + }; + } + if (clusterOptions?.algorithm) { + console.info('Algorithm is changed'); + clusterConfig.algorithm = new markerClusterer[clusterOptions.algorithm.type]({ + ...clusterOptions.algorithm.options + }); + } + // set marker cluster to the instance with the configuration + mapInstance.markerCluster = new markerClusterer.MarkerClusterer(clusterConfig); + + if (clusterOptions?.enableClusterClick) { + console.info('Cluster click enabled'); + mapInstance.markerCluster.addListener("click", (cluster) => { + dotNetHelper.invokeMethodAsync('OnClusterClickJS', { + position: cluster.position, + markers: cluster.markers.map(m => ({ + position: m.position, + title: m.title + })) + }); + }); + } + + } else { window.blazorBootstrap.googlemaps.markerEls[elementId].forEach(marker => { marker.map = map; From e0b9f43a31971f23a2afffe0806263958d814a84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=85dne=20Sigurd=20Leirdal?= Date: Sat, 9 Nov 2024 15:39:47 +0100 Subject: [PATCH 05/11] Added Algorithms, ClusterClick event and Custom Clustering --- .../Pages/Maps/GoogleMapDocumentation.razor | 10 ++- ...namic_markers_with_custom_clustering.razor | 37 ++++++--- .../Enums/GoogleMapAlgorithmTypes.cs | 7 ++ .../Models/Maps/GoogleMapClusterOptions.cs | 23 +++++- blazorbootstrap/wwwroot/blazor.bootstrap.js | 80 ++++++++++++++----- 5 files changed, 124 insertions(+), 33 deletions(-) create mode 100644 blazorbootstrap/Enums/GoogleMapAlgorithmTypes.cs diff --git a/BlazorBootstrap.Demo.RCL/Components/Pages/Maps/GoogleMapDocumentation.razor b/BlazorBootstrap.Demo.RCL/Components/Pages/Maps/GoogleMapDocumentation.razor index 9c0ee9538..529727f89 100644 --- a/BlazorBootstrap.Demo.RCL/Components/Pages/Maps/GoogleMapDocumentation.razor +++ b/BlazorBootstrap.Demo.RCL/Components/Pages/Maps/GoogleMapDocumentation.razor @@ -72,11 +72,19 @@
+
+ This example shows how to enable clustering, so that nearby markers group together. By using GoogleMapClusterOptions + and by setting ClusteringEnabled to true, and by setting the options to the GoogleMaps component, clustering will be enabled. +
- +
+ This example expands on clustering, showing how to customize the cluster markers to your own design, by using the GoogleMapClusterOptions. The cluster marker can be customized by changing the GoogleMapClusterRenderer + . You can also change the Algorithm's as well as the MaxZoom level. See https://googlemaps.github.io/js-markerclusterer/public/algorithms/ for the showcase of the different algorithms available. You can define a ClusterClick event the same way you would do markers too. +
+
@code { diff --git a/BlazorBootstrap.Demo.RCL/Components/Pages/Maps/GoogleMap_Demo_08_Dynamic_markers_with_custom_clustering.razor b/BlazorBootstrap.Demo.RCL/Components/Pages/Maps/GoogleMap_Demo_08_Dynamic_markers_with_custom_clustering.razor index eadb710f8..b329e1382 100644 --- a/BlazorBootstrap.Demo.RCL/Components/Pages/Maps/GoogleMap_Demo_08_Dynamic_markers_with_custom_clustering.razor +++ b/BlazorBootstrap.Demo.RCL/Components/Pages/Maps/GoogleMap_Demo_08_Dynamic_markers_with_custom_clustering.razor @@ -20,7 +20,8 @@ Zoom="10" Markers="markers" ClusterOptions="clusterOptions" - OnMarkerClick="OnGoogleMapMarkerClick" /> + OnMarkerClick="OnGoogleMapMarkerClick" + OnClusterClick="OnGoogleMapClusterClick"/> @code { Random random = new Random(2000000000); @@ -29,15 +30,14 @@ { ClusteringEnabled = true, EnableClusterClick = true, - // Algorithm = new GoogleMapClusterAlgorithm - // { - // Type = "SuperClusterAlgorithm", - // Options = new Dictionary - // { - // { "radius", 100 }, - // { "maxZoom", 15 } - // } - // }, + Algorithm = new GoogleMapClusterAlgorithm + { + Type = GoogleMapAlgorithmTypes.SuperClusterAlgorithm.ToString(), + Options = new GoogleMapClusterAlgorithmOptions() + { + MaxZoom = 16, + } + }, Renderer = new GoogleMapClusterRenderer { TextColor = "#ff0000", @@ -48,7 +48,24 @@ }; [Inject] public ToastService ToastService { get; set; } = default!; + + private void OnGoogleMapClusterClick(GoogleMapClusterClickEvent clusterEvent) + { + var weatherTypes = clusterEvent.Markers + .Select(m => m.Title) + .GroupBy(title => title) + .Select(g => $"{g.Key}: {g.Count()}") + .ToList(); + var summary = string.Join(", ", weatherTypes); + + ToastService.Notify(new ToastMessage( + ToastType.Info, + $"Cluster clicked: {clusterEvent.Markers.Count()} markers", + $"Weather summary: {summary}\nPosition: ({clusterEvent.Position.Latitude:F6}, {clusterEvent.Position.Longitude:F6})" + )); + } + private async ValueTask AddWeatherMarkerAsync() => await googleMapRef.AddMarkerAsync(GetRandomMarker()); private async Task UpdateWeatherMarkersAsync() diff --git a/blazorbootstrap/Enums/GoogleMapAlgorithmTypes.cs b/blazorbootstrap/Enums/GoogleMapAlgorithmTypes.cs new file mode 100644 index 000000000..80a3a6fad --- /dev/null +++ b/blazorbootstrap/Enums/GoogleMapAlgorithmTypes.cs @@ -0,0 +1,7 @@ +namespace BlazorBootstrap; + public enum GoogleMapAlgorithmTypes + { + NoopAlgorithm, + GridAlgorithm, + SuperClusterAlgorithm + } \ No newline at end of file diff --git a/blazorbootstrap/Models/Maps/GoogleMapClusterOptions.cs b/blazorbootstrap/Models/Maps/GoogleMapClusterOptions.cs index 79059b1d3..efb9bc642 100644 --- a/blazorbootstrap/Models/Maps/GoogleMapClusterOptions.cs +++ b/blazorbootstrap/Models/Maps/GoogleMapClusterOptions.cs @@ -14,16 +14,30 @@ public class GoogleMapClusterOptions public bool ClusteringEnabled { get; set; } = false; public bool EnableClusterClick { get; set; } } - + + /// + /// Makes it possible to change the Cluster Algorithms, by changing type or the zoom level under options. + /// public class GoogleMapClusterAlgorithm { - public string Type { get; set; } = "SuperClusterAlgorithm"; - public Dictionary Options { get; set; } = new(); + public string Type { get; set; } = GoogleMapAlgorithmTypes.SuperClusterAlgorithm.ToString(); + public GoogleMapClusterAlgorithmOptions Options { get; set; } = new(); + } + + /// + /// Currently only has MaxZoom, but can be expanded with more options in the future. + /// + public class GoogleMapClusterAlgorithmOptions + { + public int? MaxZoom { get; set; } = 16; } /// /// Cluster renderer for Google Maps. Be aware that the properties only applies if you have a custom SVG icon. /// + /// + /// ShowMarkerCount is set to true by default. + /// public class GoogleMapClusterRenderer { // Properties for the cluster count @@ -35,6 +49,9 @@ public class GoogleMapClusterRenderer } + /// + /// Defines the event arguments when the user clicks on a cluster. + /// public class GoogleMapClusterClickEvent { public GoogleMapMarkerPosition Position { get; set; } = default!; diff --git a/blazorbootstrap/wwwroot/blazor.bootstrap.js b/blazorbootstrap/wwwroot/blazor.bootstrap.js index f49f4e83d..46897dcba 100644 --- a/blazorbootstrap/wwwroot/blazor.bootstrap.js +++ b/blazorbootstrap/wwwroot/blazor.bootstrap.js @@ -537,7 +537,7 @@ window.blazorBootstrap = { let mapOptions = { center: center, zoom: zoom, mapId: elementId }; let map = new google.maps.Map(document.getElementById(elementId), mapOptions); - window.blazorBootstrap.googlemaps.create(elementId, map, zoom, center, markers, clickable); + window.blazorBootstrap.googlemaps.create(elementId, map, zoom, center, markers, clickable, clusterOptions); // clean up just the clustering if it exists const existingInstance = window.blazorBootstrap.googlemaps.get(elementId); @@ -618,33 +618,75 @@ window.blazorBootstrap = { instances: {}, markerEls: {}, updateMarkers: (elementId, markers, dotNetHelper) => { - let markerEls = window.blazorBootstrap.googlemaps.markerEls[elementId] ?? []; const mapInstance = window.blazorBootstrap.googlemaps.get(elementId); - const clusteringEnabled = !!mapInstance.markerCluster; - // Clean up cluster first if it exists - if (clusteringEnabled) { + let markerEls = window.blazorBootstrap.googlemaps.markerEls[elementId] ?? []; + const clusterOptions = mapInstance.clusterOptions; + + // clean up existing cluster + if (mapInstance.markerCluster) { mapInstance.markerCluster.clearMarkers(); mapInstance.markerCluster.setMap(null); } + + // clean up old and keep already existing markers + markerEls = markerEls.filter(existing => { + const shouldKeep = markers?.some(m => m.title === existing.title && m.pinElement === existing.pinElement); + if (!shouldKeep) existing.map = null; + return shouldKeep; + }); + window.blazorBootstrap.googlemaps.markerEls[elementId] = markerEls; - // Clear all existing markers from the map - for (const markerEl of markerEls) { - markerEl.map = null; - } - - // Reset marker array and add new markers - window.blazorBootstrap.googlemaps.markerEls[elementId] = []; - if (markers) { - for (const marker of markers) { + // add only new markers + markers?.forEach(marker => { + if (!markerEls.some(m => m.title === marker.title && m.pinElement === marker.pinElement)) window.blazorBootstrap.googlemaps.addMarker(elementId, marker, dotNetHelper); - } - } + }); - if (clusteringEnabled) { - mapInstance.markerCluster = new markerClusterer.MarkerClusterer({ + if (clusterOptions?.clusteringEnabled) { + const clusterConfig = { map: mapInstance.map, markers: window.blazorBootstrap.googlemaps.markerEls[elementId] - }); + }; + + // add renderer if configured + if (clusterOptions.renderer) { + clusterConfig.renderer = { + render: ({ count, position }) => { + const div = document.createElement("div"); + if (clusterOptions.renderer.svgIcon) { + const countStyle = `position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)` + + (clusterOptions.renderer.textColor ? `;color:${clusterOptions.renderer.textColor}` : '') + + (clusterOptions.renderer.textFontSize ? `;font-size:${clusterOptions.renderer.textFontSize}` : ''); + + div.innerHTML = ` +
+
${clusterOptions.renderer.svgIcon}
+ ${clusterOptions.renderer.showMarkerCount ? `
${count}
` : ''} +
`; + } + return new google.maps.marker.AdvancedMarkerElement({ position, content: div }); + } + }; + } + + // add algorithm if configured + if (clusterOptions.algorithm) { + clusterConfig.algorithm = new markerClusterer[clusterOptions.algorithm.type]({ + ...clusterOptions.algorithm.options + }); + } + + // create cluster and add click handler + mapInstance.markerCluster = new markerClusterer.MarkerClusterer(clusterConfig); + + if (clusterOptions.enableClusterClick) { + mapInstance.markerCluster.addListener("click", (cluster) => { + dotNetHelper.invokeMethodAsync('OnClusterClickJS', { + position: cluster.position, + markers: cluster.markers.map(m => ({ position: m.position, title: m.title })) + }); + }); + } } } }, From dde0eabf94ede51d54e7cc43d996de0df43dfaba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=85dne=20Sigurd=20Leirdal?= Date: Sat, 9 Nov 2024 16:11:14 +0100 Subject: [PATCH 06/11] Removed debugging and restored API key --- BlazorBootstrap.Demo.WebAssembly/wwwroot/appsettings.json | 2 +- blazorbootstrap/wwwroot/blazor.bootstrap.js | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/BlazorBootstrap.Demo.WebAssembly/wwwroot/appsettings.json b/BlazorBootstrap.Demo.WebAssembly/wwwroot/appsettings.json index 4acbd9b52..a6f4631bf 100644 --- a/BlazorBootstrap.Demo.WebAssembly/wwwroot/appsettings.json +++ b/BlazorBootstrap.Demo.WebAssembly/wwwroot/appsettings.json @@ -15,6 +15,6 @@ "stackoverflow": "//stackoverflow.com/questions/tagged/blazor-bootstrap" }, "GoogleMap": { - "ApiKey": "AIzaSyDug1LSoAWJ_UStV8cZmhhfPRfFsdzrS6Y" + "ApiKey": "AIzaSyDc110Rvu20IMJhlZcWTOPoLbVQdnjLyXs" } } \ No newline at end of file diff --git a/blazorbootstrap/wwwroot/blazor.bootstrap.js b/blazorbootstrap/wwwroot/blazor.bootstrap.js index 46897dcba..c467ad40b 100644 --- a/blazorbootstrap/wwwroot/blazor.bootstrap.js +++ b/blazorbootstrap/wwwroot/blazor.bootstrap.js @@ -587,7 +587,6 @@ window.blazorBootstrap = { }; } if (clusterOptions?.algorithm) { - console.info('Algorithm is changed'); clusterConfig.algorithm = new markerClusterer[clusterOptions.algorithm.type]({ ...clusterOptions.algorithm.options }); @@ -596,7 +595,6 @@ window.blazorBootstrap = { mapInstance.markerCluster = new markerClusterer.MarkerClusterer(clusterConfig); if (clusterOptions?.enableClusterClick) { - console.info('Cluster click enabled'); mapInstance.markerCluster.addListener("click", (cluster) => { dotNetHelper.invokeMethodAsync('OnClusterClickJS', { position: cluster.position, From e682baf4e385b07b94ac47a324dc2ba8b1ee030a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=85dne=20Sigurd=20Leirdal?= Date: Fri, 10 Jan 2025 12:53:18 +0100 Subject: [PATCH 07/11] Added MapId --- blazorbootstrap/BlazorBootstrap.csproj | 21 +++++++++++++++++-- .../Components/Maps/GoogleMap.razor.cs | 6 ++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/blazorbootstrap/BlazorBootstrap.csproj b/blazorbootstrap/BlazorBootstrap.csproj index 19cb62caa..d6720a124 100644 --- a/blazorbootstrap/BlazorBootstrap.csproj +++ b/blazorbootstrap/BlazorBootstrap.csproj @@ -3,8 +3,8 @@ Blazor.Bootstrap - 3.1.1 - 3.1.1 + 3.2.3 + 3.2.3 Apache-2.0 @@ -54,4 +54,21 @@ + + + true + wwwroot + + + + true + staticwebassets + PreserveNewest + + + + + + \ No newline at end of file diff --git a/blazorbootstrap/Components/Maps/GoogleMap.razor.cs b/blazorbootstrap/Components/Maps/GoogleMap.razor.cs index f418abe08..c2e52a9cb 100644 --- a/blazorbootstrap/Components/Maps/GoogleMap.razor.cs +++ b/blazorbootstrap/Components/Maps/GoogleMap.razor.cs @@ -81,6 +81,12 @@ private void OnScriptLoad() ($"height:{Height!.Value.ToString(CultureInfo.InvariantCulture)}{HeightUnit.ToCssString()}", Height is not null && Height.Value > 0) ); + /// + /// Gets or sets the Google Maps Map ID. + /// + [Parameter] + public string? MapId { get; set; } + /// /// Gets or sets the Google Map API key. /// From 9a03e148826449e7a60f868fab342a36f2f3b417 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=85dne=20Sigurd=20Leirdal?= Date: Fri, 10 Jan 2025 13:28:03 +0100 Subject: [PATCH 08/11] Added Map ID for Google Maps --- blazorbootstrap/BlazorBootstrap.csproj | 4 ++-- blazorbootstrap/Components/Maps/GoogleMap.razor.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/blazorbootstrap/BlazorBootstrap.csproj b/blazorbootstrap/BlazorBootstrap.csproj index a033d39e4..866744893 100644 --- a/blazorbootstrap/BlazorBootstrap.csproj +++ b/blazorbootstrap/BlazorBootstrap.csproj @@ -3,8 +3,8 @@ Blazor.Bootstrap - 3.2.3 - 3.2.3 + 3.4.0 + 3.4.0 Apache-2.0 diff --git a/blazorbootstrap/Components/Maps/GoogleMap.razor.cs b/blazorbootstrap/Components/Maps/GoogleMap.razor.cs index c2e52a9cb..320611bf2 100644 --- a/blazorbootstrap/Components/Maps/GoogleMap.razor.cs +++ b/blazorbootstrap/Components/Maps/GoogleMap.razor.cs @@ -82,7 +82,7 @@ private void OnScriptLoad() ); /// - /// Gets or sets the Google Maps Map ID. + /// Gets or sets the Google Map ID. It essentially allows for custom styled maps from Google Maps Platforms, see: https://developers.google.com/maps/documentation/javascript/map-ids/mapid-over /// [Parameter] public string? MapId { get; set; } @@ -105,7 +105,7 @@ private void OnScriptLoad() [Parameter] public bool Clickable { get; set; } - private string? GoogleMapsJsFileUrl => $"https://maps.googleapis.com/maps/api/js?key={ApiKey}&libraries=maps,marker"; + private string? GoogleMapsJsFileUrl => $"https://maps.googleapis.com/maps/api/js?key={ApiKey}&libraries=maps,marker{(MapId != null ? $"&map_ids={MapId}" : "")}"; /// /// Gets or sets the height of the . From fb4e8a415e9f3182fbd047a027510cb05a5d0204 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=85dne=20Sigurd=20Leirdal?= Date: Fri, 10 Jan 2025 14:53:00 +0100 Subject: [PATCH 09/11] Added Map Id, for custom looking maps --- blazorbootstrap/BlazorBootstrap.csproj | 4 ++-- blazorbootstrap/Components/Maps/GoogleMap.razor.cs | 4 ++-- blazorbootstrap/wwwroot/blazor.bootstrap.js | 11 ++++++++--- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/blazorbootstrap/BlazorBootstrap.csproj b/blazorbootstrap/BlazorBootstrap.csproj index 866744893..0cc474c99 100644 --- a/blazorbootstrap/BlazorBootstrap.csproj +++ b/blazorbootstrap/BlazorBootstrap.csproj @@ -3,8 +3,8 @@ Blazor.Bootstrap - 3.4.0 - 3.4.0 + 3.4.4 + 3.4.4 Apache-2.0 diff --git a/blazorbootstrap/Components/Maps/GoogleMap.razor.cs b/blazorbootstrap/Components/Maps/GoogleMap.razor.cs index 320611bf2..343c5c5b9 100644 --- a/blazorbootstrap/Components/Maps/GoogleMap.razor.cs +++ b/blazorbootstrap/Components/Maps/GoogleMap.razor.cs @@ -49,7 +49,7 @@ public async Task OnClusterClickJS(GoogleMapClusterClickEvent clusterEvent) /// A completed task. public ValueTask RefreshAsync() { - JSRuntime.InvokeVoidAsync("window.blazorBootstrap.googlemaps.initialize", Id, Zoom, Center, Markers, Clickable, ClusterOptions, objRef); + JSRuntime.InvokeVoidAsync("window.blazorBootstrap.googlemaps.initialize", Id, Zoom, Center, Markers, Clickable, ClusterOptions, MapId, objRef); return ValueTask.CompletedTask; } @@ -67,7 +67,7 @@ public ValueTask UpdateMarkersAsync(IEnumerable markers) private void OnScriptLoad() { - Task.Run(async () => await JSRuntime.InvokeVoidAsync("window.blazorBootstrap.googlemaps.initialize", Id, Zoom, Center, Markers, Clickable, ClusterOptions, objRef)); + Task.Run(async () => await JSRuntime.InvokeVoidAsync("window.blazorBootstrap.googlemaps.initialize", Id, Zoom, Center, Markers, Clickable, ClusterOptions, MapId, objRef)); } #endregion diff --git a/blazorbootstrap/wwwroot/blazor.bootstrap.js b/blazorbootstrap/wwwroot/blazor.bootstrap.js index 781b84ca7..bfcc1efa7 100644 --- a/blazorbootstrap/wwwroot/blazor.bootstrap.js +++ b/blazorbootstrap/wwwroot/blazor.bootstrap.js @@ -531,10 +531,15 @@ window.blazorBootstrap = { get: (elementId) => { return window.blazorBootstrap.googlemaps.instances[elementId]; }, - initialize: (elementId, zoom, center, markers, clickable, clusterOptions, dotNetHelper) => { + initialize: (elementId, zoom, center, markers, clickable, clusterOptions, mapId, dotNetHelper) => { window.blazorBootstrap.googlemaps.markerEls[elementId] ??= []; - - let mapOptions = { center: center, zoom: zoom, mapId: elementId }; + let id = elementId; + + // in case a person wants to use a custom map + if(mapId) + id = mapId; + + let mapOptions = { center: center, zoom: zoom, mapId: id }; let map = new google.maps.Map(document.getElementById(elementId), mapOptions); window.blazorBootstrap.googlemaps.create(elementId, map, zoom, center, markers, clickable, clusterOptions); From 1d570f16efce4f799a1a1cd9b55d09c77bd59588 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=85dne=20Sigurd=20Leirdal?= Date: Fri, 17 Jan 2025 10:46:03 +0100 Subject: [PATCH 10/11] Added disabling of info windows --- blazorbootstrap/BlazorBootstrap.csproj | 4 ++-- blazorbootstrap/Models/Maps/GoogleMapMarker.cs | 5 +++++ blazorbootstrap/wwwroot/blazor.bootstrap.js | 12 ++++++++---- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/blazorbootstrap/BlazorBootstrap.csproj b/blazorbootstrap/BlazorBootstrap.csproj index 0cc474c99..5165885ae 100644 --- a/blazorbootstrap/BlazorBootstrap.csproj +++ b/blazorbootstrap/BlazorBootstrap.csproj @@ -3,8 +3,8 @@ Blazor.Bootstrap - 3.4.4 - 3.4.4 + 3.4.6 + 3.4.6 Apache-2.0 diff --git a/blazorbootstrap/Models/Maps/GoogleMapMarker.cs b/blazorbootstrap/Models/Maps/GoogleMapMarker.cs index abd68296a..5947e3810 100644 --- a/blazorbootstrap/Models/Maps/GoogleMapMarker.cs +++ b/blazorbootstrap/Models/Maps/GoogleMapMarker.cs @@ -14,5 +14,10 @@ public class GoogleMapMarker [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public string? Title { get; set; } + /// + /// Variable for disabling info windows on the maps + /// + public bool DisableInfoWindow { get; set; } = false; + #endregion } diff --git a/blazorbootstrap/wwwroot/blazor.bootstrap.js b/blazorbootstrap/wwwroot/blazor.bootstrap.js index bfcc1efa7..449ce6060 100644 --- a/blazorbootstrap/wwwroot/blazor.bootstrap.js +++ b/blazorbootstrap/wwwroot/blazor.bootstrap.js @@ -498,10 +498,14 @@ window.blazorBootstrap = { if (clickable) { markerEl.addListener("click", ({ domEvent, latLng }) => { const { target } = domEvent; - const infoWindow = new google.maps.InfoWindow(); - infoWindow.close(); - infoWindow.setContent(markerEl.title); - infoWindow.open(markerEl.map, markerEl); + + // Disables info window, but enables clicking + if(!marker.disableInfoWindow) { + const infoWindow = new google.maps.InfoWindow(); + infoWindow.close(); + infoWindow.setContent(markerEl.title); + infoWindow.open(markerEl.map, markerEl); + } dotNetHelper.invokeMethodAsync('OnMarkerClickJS', marker); }); } From 5a8e2a4d0a352db495efd2299d54ce5dfb3f0d3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=85dne=20Sigurd=20Leirdal?= Date: Thu, 30 Jan 2025 13:50:13 +0100 Subject: [PATCH 11/11] Added options for Map Control, such as street view and zoom --- blazorbootstrap/BlazorBootstrap.csproj | 4 +- .../Components/Maps/GoogleMap.razor.cs | 14 ++++-- blazorbootstrap/Enums/GoogleMapControls.cs | 21 +++++++++ blazorbootstrap/wwwroot/blazor.bootstrap.js | 43 +++++++++++++++++-- 4 files changed, 74 insertions(+), 8 deletions(-) create mode 100644 blazorbootstrap/Enums/GoogleMapControls.cs diff --git a/blazorbootstrap/BlazorBootstrap.csproj b/blazorbootstrap/BlazorBootstrap.csproj index 5165885ae..bbe952f19 100644 --- a/blazorbootstrap/BlazorBootstrap.csproj +++ b/blazorbootstrap/BlazorBootstrap.csproj @@ -3,8 +3,8 @@ Blazor.Bootstrap - 3.4.6 - 3.4.6 + 3.4.6.3 + 3.4.6.3 Apache-2.0 diff --git a/blazorbootstrap/Components/Maps/GoogleMap.razor.cs b/blazorbootstrap/Components/Maps/GoogleMap.razor.cs index 343c5c5b9..3e6eca80e 100644 --- a/blazorbootstrap/Components/Maps/GoogleMap.razor.cs +++ b/blazorbootstrap/Components/Maps/GoogleMap.razor.cs @@ -49,7 +49,7 @@ public async Task OnClusterClickJS(GoogleMapClusterClickEvent clusterEvent) /// A completed task. public ValueTask RefreshAsync() { - JSRuntime.InvokeVoidAsync("window.blazorBootstrap.googlemaps.initialize", Id, Zoom, Center, Markers, Clickable, ClusterOptions, MapId, objRef); + JSRuntime.InvokeVoidAsync("window.blazorBootstrap.googlemaps.initialize", Id, Zoom, Center, Markers, Clickable, ClusterOptions, MapControls, MapId, objRef); return ValueTask.CompletedTask; } @@ -67,7 +67,7 @@ public ValueTask UpdateMarkersAsync(IEnumerable markers) private void OnScriptLoad() { - Task.Run(async () => await JSRuntime.InvokeVoidAsync("window.blazorBootstrap.googlemaps.initialize", Id, Zoom, Center, Markers, Clickable, ClusterOptions, MapId, objRef)); + Task.Run(async () => await JSRuntime.InvokeVoidAsync("window.blazorBootstrap.googlemaps.initialize", Id, Zoom, Center, Markers, Clickable, ClusterOptions, MapControls, MapId, objRef)); } #endregion @@ -177,6 +177,14 @@ private void OnScriptLoad() /// [Parameter] public EventCallback OnClusterClick { get; set; } - + + /// + /// Decides which controls to show on the map. + /// + /// + /// Full is the default value, which enables both street view and zoom controls. + /// + [Parameter] + public GoogleMapControls MapControls { get; set; } = GoogleMapControls.Full; #endregion } diff --git a/blazorbootstrap/Enums/GoogleMapControls.cs b/blazorbootstrap/Enums/GoogleMapControls.cs new file mode 100644 index 000000000..a341ace67 --- /dev/null +++ b/blazorbootstrap/Enums/GoogleMapControls.cs @@ -0,0 +1,21 @@ +namespace BlazorBootstrap; + +public enum GoogleMapControls +{ + /// + /// Enables both zoom and street view controls. + /// + Full, + /// + /// Removes the zoom controls, but keeps the street view control. + /// + NoZoom, + /// + /// Removes street view controls, but keeps the zoom control. + /// + NoStreetView, + /// + /// Removes all controls entirely, including zoom and street view. + /// + NoZoomAndStreetView, +} \ No newline at end of file diff --git a/blazorbootstrap/wwwroot/blazor.bootstrap.js b/blazorbootstrap/wwwroot/blazor.bootstrap.js index 449ce6060..91ddf1eb4 100644 --- a/blazorbootstrap/wwwroot/blazor.bootstrap.js +++ b/blazorbootstrap/wwwroot/blazor.bootstrap.js @@ -529,21 +529,24 @@ window.blazorBootstrap = { center: center, markers: markers, clickable: clickable, - clusterOptions : clusterOptions + clusterOptions: clusterOptions, }; }, get: (elementId) => { return window.blazorBootstrap.googlemaps.instances[elementId]; }, - initialize: (elementId, zoom, center, markers, clickable, clusterOptions, mapId, dotNetHelper) => { + initialize: (elementId, zoom, center, markers, clickable, clusterOptions, mapControls, mapId, dotNetHelper) => { window.blazorBootstrap.googlemaps.markerEls[elementId] ??= []; let id = elementId; // in case a person wants to use a custom map if(mapId) id = mapId; + + // get the controls configuration based on the enum + const controlsConfig = window.blazorBootstrap.googlemaps.getMapControlsConfig(mapControls); - let mapOptions = { center: center, zoom: zoom, mapId: id }; + let mapOptions = { center: center, zoom: zoom, mapId: id, ...controlsConfig }; let map = new google.maps.Map(document.getElementById(elementId), mapOptions); window.blazorBootstrap.googlemaps.create(elementId, map, zoom, center, markers, clickable, clusterOptions); @@ -695,6 +698,40 @@ window.blazorBootstrap = { }); } } + }, + getMapControlsConfig: (mapControls) => { + // Default configuration - everything enabled + let controlsConfig = { + streetViewControl: true, + zoomControl: true, + disableDefaultUI: false + }; + + switch (mapControls) { + case 0: // Full + // Default config is already set up for Full + break; + + case 1: // NoZoom + controlsConfig.zoomControl = false; + controlsConfig.disableDefaultUI = true; + controlsConfig.streetViewControl = true; + break; + + case 2: // NoStreetView + controlsConfig.streetViewControl = false; + controlsConfig.disableDefaultUI = true; + controlsConfig.zoomControl = true; + break; + + case 3: // NoZoomAndStreetView + controlsConfig.streetViewControl = false; + controlsConfig.zoomControl = false; + controlsConfig.disableDefaultUI = true; + break; + } + + return controlsConfig; } }, grid: {