+ Sample demonstrates the use of the Bookmarks widget and Bookmark actions.
+ This follows the ESRI eample, but the Timeline Widget is included with GeoBlazor pro.
+
+
+
+
+
+
+
+
+
+
+
+
+@code {
+ private MapView? MapView { get; set; }
+
+ private FeatureLayerView? _layerView { get; set; }
+
+ private void OnLayerViewCreate(LayerViewCreateEvent evt)
+ {
+ _layerView = evt.LayerView as FeatureLayerView;
+ }
+
+ private async void OnMapClick()
+ {
+ var bookmarks = await ((WebMap)MapView?.Map).GetBookmarks();
+ }
+
+ private void OnBookmarkClick(BookmarkSelectEvent eventArgs)
+ {
+ var layer = MapView!.Map!.Layers.FirstOrDefault();
+ if (_layerView is null) return;
+
+ var hurricaneName = eventArgs.Bookmark.Name;
+
+ var featureEffect = new FeatureEffect
+ {
+ Filter = new FeatureFilter()
+ {
+ Where = $"Name = '{hurricaneName.ToUpper()}'"
+ },
+ ExcludedEffect = new() { new Effect() { Value = "grayscale(100%) opacity(30%)" } }
+ };
+ _ = _layerView.SetFeatureEffect(featureEffect);
+ }
+
+}
\ No newline at end of file
diff --git a/samples/dymaptic.GeoBlazor.Core.Sample.Shared/Pages/Widgets.razor b/samples/dymaptic.GeoBlazor.Core.Sample.Shared/Pages/Widgets.razor
index 06d508c2..1185b173 100644
--- a/samples/dymaptic.GeoBlazor.Core.Sample.Shared/Pages/Widgets.razor
+++ b/samples/dymaptic.GeoBlazor.Core.Sample.Shared/Pages/Widgets.razor
@@ -1,4 +1,5 @@
@page "/widgets"
+@using GeoBlazor.Core.Components.Widgets;
Widgets
Widgets
@@ -37,6 +38,9 @@
+
+
+
@@ -83,6 +87,11 @@
{
}
+ @if (_showBookmarks)
+ {
+
+
+ }
@code {
@@ -119,9 +128,72 @@
case nameof(MeasurementWidget):
_showMeasurement = !_showMeasurement;
break;
+ case nameof(BookmarksWidget):
+ _showBookmarks = !_showBookmarks;
+ break;
}
}
+ protected override void OnAfterRender(bool firstRender)
+ {
+ if (_showBookmarks)
+ {
+ //the MapView.Refresh() is needed because the bookmarks are added after the BookmarksWidget is rendered
+ _bookmarkWidget!.Bookmarks = _bookmarks;
+ MapView!.Refresh();
+ }
+ }
+
+ private List _bookmarks = new List()
+ {
+ new Bookmark()
+ {
+ Name="Angeles National Forest",
+ Thumbnail="/_content/dymaptic.GeoBlazor.Core.Sample.Shared/images/Blazor-API-60px.png",
+ Viewpoint = new Viewpoint()
+ {
+ TargetGeometry = new Extent()
+ {
+ SpatialReference = new SpatialReference(102100),
+ Xmin = -13139131.948889678,
+ Ymin = 4047767.23531948,
+ Xmax = -13092887.54677721,
+ Ymax = 4090610.189673263,
+ }
+ }
+ },
+ new Bookmark()
+ {
+ Name = "Crystal Lake",
+ Viewpoint = new Viewpoint()
+ {
+ TargetGeometry = new Extent()
+ {
+ SpatialReference = new SpatialReference(102100),
+ Xmin = -13125852.551697943,
+ Ymin = 4066904.1101411926,
+ Xmax = -13114291.451169826,
+ Ymax = 4077614.8487296384
+ }
+ }
+ },
+ new Bookmark()
+ {
+ Name = "Mt. Waterman",
+ Viewpoint = new Viewpoint()
+ {
+ TargetGeometry = new Extent()
+ {
+ SpatialReference = new SpatialReference(102100),
+ Xmin= -13185668.186639601,
+ Ymin= 4066176.418652561,
+ Xmax= -13183855.195875114,
+ Ymax= 4067515.260976006
+ }
+ }
+ }
+ };
+
private bool _showSearch;
private bool _showLocate;
private bool _showBasemapToggle;
@@ -131,4 +203,7 @@
private bool _showHome;
private bool _showCompass;
private bool _showMeasurement;
+ private bool _showBookmarks;
+
+ private BookmarksWidget? _bookmarkWidget;
}
\ No newline at end of file
diff --git a/samples/dymaptic.GeoBlazor.Core.Sample.Shared/Shared/NavMenu.razor b/samples/dymaptic.GeoBlazor.Core.Sample.Shared/Shared/NavMenu.razor
index 0c9f250b..bcc740f7 100644
--- a/samples/dymaptic.GeoBlazor.Core.Sample.Shared/Shared/NavMenu.razor
+++ b/samples/dymaptic.GeoBlazor.Core.Sample.Shared/Shared/NavMenu.razor
@@ -77,6 +77,7 @@
new("basemaps", "Basemaps", "oi-map"),
new("feature-layers", "Feature Layers", "oi-layers"),
new("popups", "Popups", "oi-chat"),
+ new("bookmarks", "Bookmarks", "oi-bookmark"),
new("popup-actions", "Popup Actions", "oi-bullhorn"),
new("vector-layer", "Vector Layer", "oi-arrow-right"),
new("layer-lists", "Layer Lists", "oi-list"),
diff --git a/src/dymaptic.GeoBlazor.Core/Components/Layers/FeatureLayerView.cs b/src/dymaptic.GeoBlazor.Core/Components/Layers/FeatureLayerView.cs
index 945c1b2c..4c8da8ef 100644
--- a/src/dymaptic.GeoBlazor.Core/Components/Layers/FeatureLayerView.cs
+++ b/src/dymaptic.GeoBlazor.Core/Components/Layers/FeatureLayerView.cs
@@ -36,6 +36,12 @@ internal FeatureLayerView(LayerView layerView, AbortManager abortManager, bool i
///
public FeatureFilter? Filter { get; private set; }
+
+ ///
+ /// The featureEffect can be used to draw attention features of interest.
+ ///
+ public FeatureEffect? FeatureEffect { get; private set; }
+
///
/// Sets the for this view.
///
@@ -48,6 +54,20 @@ public async Task SetFilter(FeatureFilter? filter)
Filter = filter;
}
+
+ ///
+ /// Sets the for this view.
+ ///
+ ///
+ /// The new effect (or null to clear) to apply to this view.
+ ///
+
+ public async Task SetFeatureEffect(FeatureEffect? featureEffect)
+ {
+ await JsObjectReference!.InvokeVoidAsync("setFeatureEffect", CancellationTokenSource.Token, featureEffect);
+ FeatureEffect = featureEffect;
+ }
+
///
/// Highlights the given feature(s).
///
@@ -377,4 +397,52 @@ public class FeatureFilter
///
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? Where { get; set; }
+}
+
+///
+///
+/// ArcGIS
+/// JS API
+///
+///
+public class FeatureEffect
+{
+ ///
+ /// The effect applied to features that do not meet the filter requirements.
+ ///
+ public List? ExcludedEffect { get; set; }
+
+ ///
+ /// Indicates if labels are visible for features that are excluded from the filter.
+ ///
+ public bool? ExcludedLabelsVisible { get; set; }
+
+ ///
+ /// The filter that drives the effect.
+ ///
+ public FeatureFilter? Filter { get; set; }
+
+ ///
+ /// The effect applied to features that meet the filter requirements.
+ ///
+ public List? IncludedEffect { get; set; }
+
+}
+
+///
+///
+/// ArcGIS
+/// JS API
+///
+///
+public class Effect
+{
+ ///
+ /// The scale of the view for the effect to take place. Use only when setting a scale dependent effect.
+ ///
+ public double? Scale { get; set; }
+ ///
+ /// The effect to be applied to a layer or layerView at the corresponding scale. Use only when setting a scale dependent effect.
+ ///
+ public string? Value { get; set; }
}
\ No newline at end of file
diff --git a/src/dymaptic.GeoBlazor.Core/Components/Views/Viewpoint.cs b/src/dymaptic.GeoBlazor.Core/Components/Views/Viewpoint.cs
new file mode 100644
index 00000000..87662bc6
--- /dev/null
+++ b/src/dymaptic.GeoBlazor.Core/Components/Views/Viewpoint.cs
@@ -0,0 +1,68 @@
+using dymaptic.GeoBlazor.Core.Components.Geometries;
+using Microsoft.AspNetCore.Components;
+using System.Text.Json.Serialization;
+
+namespace dymaptic.GeoBlazor.Core.Components.Views;
+
+///
+/// Describes a point of view for a 2D or 3D view. In a 2D view, the viewpoint is determined using a center point and scale value.
+/// In a 3D view, it is determined using a camera position.
+/// The Viewpoint can be bookmarked for later use, or used for navigation purposes.
+///
+public class Viewpoint : MapComponent
+{
+ ///public Camera Camera { get; set; }
+
+ ///
+ /// The rotation of due north in relation to the top of the view in degrees.
+ ///
+ [Parameter]
+ public double? Rotation { get; set; } = 0;
+
+ ///
+ /// The scale of the viewpoint.
+ ///
+ [Parameter]
+ public double? Scale { get; set; } = 0;
+
+ ///
+ /// The target geometry framed by the viewpoint.
+ ///
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ public Geometry? TargetGeometry { get; set; }
+
+ ///
+ public override async Task RegisterChildComponent(MapComponent child)
+ {
+ switch (child)
+ {
+ case Geometry geometry:
+ TargetGeometry = geometry;
+ break;
+ default:
+ await base.RegisterChildComponent(child);
+ break;
+ }
+ }
+
+ ///
+ public override async Task UnregisterChildComponent(MapComponent child)
+ {
+ switch (child)
+ {
+ case Geometry geometry:
+ TargetGeometry = null;
+ break;
+ default:
+ await base.UnregisterChildComponent(child);
+ break;
+ }
+
+ }
+
+ internal override void ValidateRequiredChildren()
+ {
+ TargetGeometry?.ValidateRequiredChildren();
+ }
+
+}
diff --git a/src/dymaptic.GeoBlazor.Core/Components/WebMap.cs b/src/dymaptic.GeoBlazor.Core/Components/WebMap.cs
index 40504749..fe7a1bf6 100644
--- a/src/dymaptic.GeoBlazor.Core/Components/WebMap.cs
+++ b/src/dymaptic.GeoBlazor.Core/Components/WebMap.cs
@@ -1,4 +1,10 @@
-namespace dymaptic.GeoBlazor.Core.Components;
+using dymaptic.GeoBlazor.Core.Components.Widgets;
+using Microsoft.AspNetCore.Components;
+using Microsoft.JSInterop;
+using System.Text.Json.Serialization;
+
+
+namespace dymaptic.GeoBlazor.Core.Components;
///
/// Loads a WebMap from ArcGIS Online or ArcGIS Enterprise portal into a MapView. It defines the content, style, and
@@ -58,4 +64,20 @@ internal override void ValidateRequiredChildren()
base.ValidateRequiredChildren();
PortalItem?.ValidateRequiredChildren();
}
+
+ ///
+ /// Gets the bookmarks defined in the WebMap from layers
+ ///
+ /// A list of bookmarks or null
+ public async Task> GetBookmarks()
+ {
+ var bookmarks = new List();
+
+ if (JsModule != null)
+ {
+ bookmarks =
+ await JsModule!.InvokeAsync>("getWebMapBookmarks", CancellationTokenSource.Token, this.View?.Id);
+ }
+ return bookmarks;
+ }
}
\ No newline at end of file
diff --git a/src/dymaptic.GeoBlazor.Core/Components/Widgets/Bookmark.cs b/src/dymaptic.GeoBlazor.Core/Components/Widgets/Bookmark.cs
new file mode 100644
index 00000000..ad61351d
--- /dev/null
+++ b/src/dymaptic.GeoBlazor.Core/Components/Widgets/Bookmark.cs
@@ -0,0 +1,69 @@
+using dymaptic.GeoBlazor.Core.Components.Views;
+using dymaptic.GeoBlazor.Core.Objects;
+using Microsoft.AspNetCore.Components;
+using System.Text.Json.Serialization;
+
+namespace dymaptic.GeoBlazor.Core.Components.Widgets;
+
+///
+/// A bookmark is a saved map extent that allows end users to quickly navigate
+/// to a particular area of interest using the Bookmarks widget.
+/// They are usually defined part of the WebMap.
+///
+public class Bookmark : MapComponent
+{
+ ///
+ /// The extent of the specified bookmark.
+ ///
+ [Parameter]
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ public TimeExtent? TimeExtent { get; set; }
+
+ ///
+ /// The name of the bookmark.
+ ///
+ [Parameter]
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ public string? Name { get; set; }
+
+ ///
+ /// The URL for a thumbnail image.
+ ///
+ [Parameter]
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ public string? Thumbnail { get; set; }
+
+ ///
+ /// The viewpoint of the bookmark item. Defines the rotation, scale, and target geometry of the bookmark.
+ ///
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ public Viewpoint? Viewpoint { get; set; }
+
+ ///
+ public override async Task RegisterChildComponent(MapComponent child)
+ {
+ switch (child)
+ {
+ case Viewpoint viewpoint:
+ Viewpoint = viewpoint;
+ break;
+ default:
+ await base.RegisterChildComponent(child);
+ break;
+ }
+ }
+
+ ///
+ public override async Task UnregisterChildComponent(MapComponent child)
+ {
+ switch (child)
+ {
+ case Viewpoint viewpoint:
+ Viewpoint = null;
+ break;
+ default:
+ await base.UnregisterChildComponent(child);
+ break;
+ }
+ }
+}
diff --git a/src/dymaptic.GeoBlazor.Core/Components/Widgets/BookmarksWidget.cs b/src/dymaptic.GeoBlazor.Core/Components/Widgets/BookmarksWidget.cs
new file mode 100644
index 00000000..c68c8a98
--- /dev/null
+++ b/src/dymaptic.GeoBlazor.Core/Components/Widgets/BookmarksWidget.cs
@@ -0,0 +1,100 @@
+using dymaptic.GeoBlazor.Core.Events;
+using Microsoft.AspNetCore.Components;
+using Microsoft.JSInterop;
+using System.Text.Json.Serialization;
+
+
+namespace dymaptic.GeoBlazor.Core.Components.Widgets;
+
+///
+/// The Bookmarks widget allows end users to quickly navigate to a particular area of interest. It displays a list of bookmarks, which are typically defined inside the WebMap.
+///
+/// ArcGIS
+/// JS API
+///
+///
+public class BookmarksWidget : Widget
+{
+ ///
+ [JsonPropertyName("type")]
+ public override string WidgetType => "bookmarks";
+
+ ///
+ /// When true, the widget is visually withdrawn and cannot be interacted with.
+ ///
+ [Parameter]
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ public bool? Disabled { get; set; }
+
+ ///
+ /// Indicates whether the bookmarks are able to be edited.
+ ///
+ [Parameter]
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ public bool? EditingEnabled { get; set; }
+
+ ///
+ /// Indicates the heading level to use for the message "No bookmarks" when no bookmarks are available in this widget.
+ ///
+ [Parameter]
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ public int? HeadingLevel { get; set; }
+
+ ///
+ /// A collection of Bookmarks.
+ ///
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ public List? Bookmarks { get; set; }
+
+ ///
+ /// Handler delegate for click events on the view.
+ ///
+ [Parameter]
+ public EventCallback OnBookmarkSelect { get; set; }
+
+ ///
+ /// JS-Invokable method to return a selected bookmark
+ ///
+ ///
+ /// The return meta object.
+ ///
+ ///
+ /// Fires after a user clicks on a bookmark.
+ ///
+ [JSInvokable]
+ public async Task OnJavascriptBookmarkSelect(BookmarkSelectEvent bookmarkSelectEvent)
+ {
+ await OnBookmarkSelect.InvokeAsync(bookmarkSelectEvent);
+ }
+
+ ///
+ public override async Task RegisterChildComponent(MapComponent child)
+ {
+ switch (child)
+ {
+ case Bookmark bookmark:
+ if (!Bookmarks.Contains(bookmark)) Bookmarks.Add(bookmark);
+ WidgetChanged = true;
+ break;
+ default:
+ await base.RegisterChildComponent(child);
+ break;
+ }
+ }
+
+
+ ///
+ public override async Task UnregisterChildComponent(MapComponent child)
+ {
+ switch (child)
+ {
+ case Bookmark bookmark:
+ if (Bookmarks!.Contains(bookmark)) Bookmarks.Remove(bookmark);
+ WidgetChanged = true;
+ break;
+ default:
+ await base.UnregisterChildComponent(child);
+ break;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/dymaptic.GeoBlazor.Core/Components/Widgets/CompassWidget.cs b/src/dymaptic.GeoBlazor.Core/Components/Widgets/CompassWidget.cs
index 49ff1b77..900a9f24 100644
--- a/src/dymaptic.GeoBlazor.Core/Components/Widgets/CompassWidget.cs
+++ b/src/dymaptic.GeoBlazor.Core/Components/Widgets/CompassWidget.cs
@@ -25,6 +25,7 @@ public class CompassWidget : Widget
///
[Parameter]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [Obsolete("Use Icon instead")]
public string? IconClass { get; set; }
///
diff --git a/src/dymaptic.GeoBlazor.Core/Components/Widgets/ExpandWidget.cs b/src/dymaptic.GeoBlazor.Core/Components/Widgets/ExpandWidget.cs
index 9dbd73aa..d704bcf5 100644
--- a/src/dymaptic.GeoBlazor.Core/Components/Widgets/ExpandWidget.cs
+++ b/src/dymaptic.GeoBlazor.Core/Components/Widgets/ExpandWidget.cs
@@ -1,4 +1,5 @@
-using Microsoft.AspNetCore.Components;
+using dymaptic.GeoBlazor.Core.Serialization;
+using Microsoft.AspNetCore.Components;
using System.Text.Json.Serialization;
@@ -21,6 +22,7 @@ public class ExpandWidget : Widget
///
[Parameter]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [Obsolete("Use ExpandIcon instead")]
public string? ExpandIconClass { get; set; }
///
@@ -28,10 +30,11 @@ public class ExpandWidget : Widget
///
[Parameter]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [Obsolete("Use CollapseIcon instead")]
public string? CollapseIconClass { get; set; }
///
- /// Tooltip to display to indicate Expand widget can be expanded.
+ /// Tooltip to display to indicate Expand widget can be expanded
///
[Parameter]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
@@ -58,7 +61,7 @@ public class ExpandWidget : Widget
[Parameter]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public bool? CloseOnEsc { get; set; }
-
+
///
/// The custom HTML content to display within the expanded Expand widget.
///
@@ -76,6 +79,33 @@ public class ExpandWidget : Widget
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public Widget? WidgetContent { get; set; }
+ ///
+ /// Indicates whether the widget is currently expanded or not.
+ ///
+ [Parameter]
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ public bool? Expanded { get; set; }
+
+ ///
+ /// Calcite icon used when the widget is collapsed. Will automatically use the content's icon if it has one.
+ ///
+ [Parameter]
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ public string? ExpandIcon { get; set; }
+
+ ///
+ /// Calcite icon used to style the Expand button when the content can be collapsed.
+ ///
+ [Parameter]
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ public string? CollapseIcon { get; set; }
+
+ ///
+ /// The mode in which the widget displays.
+ ///
+ [Parameter]
+ public Mode Mode { get; set; } = Mode.Auto;
+
///
public override async Task RegisterChildComponent(MapComponent child)
{
@@ -110,4 +140,18 @@ public override async Task UnregisterChildComponent(MapComponent child)
break;
}
}
+}
+
+
+///
+/// The mode in which the Expander widget displays. These modes are listed below.
+/// Possible Values:"auto"|"floating"|"drawer"
+//Default Value:"auto"
+///
+[JsonConverter(typeof(EnumToKebabCaseStringConverter))]
+public enum Mode
+{
+ Auto,
+ Floating,
+ Drawer
}
\ No newline at end of file
diff --git a/src/dymaptic.GeoBlazor.Core/Components/Widgets/HomeWidget.cs b/src/dymaptic.GeoBlazor.Core/Components/Widgets/HomeWidget.cs
index 0436f0b8..2265c0cc 100644
--- a/src/dymaptic.GeoBlazor.Core/Components/Widgets/HomeWidget.cs
+++ b/src/dymaptic.GeoBlazor.Core/Components/Widgets/HomeWidget.cs
@@ -21,6 +21,7 @@ public class HomeWidget : Widget
///
[Parameter]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [Obsolete("Use Icon instead")]
public string? IconClass { get; set; }
///
diff --git a/src/dymaptic.GeoBlazor.Core/Components/Widgets/Widget.cs b/src/dymaptic.GeoBlazor.Core/Components/Widgets/Widget.cs
index 03a14d7c..e1f94c25 100644
--- a/src/dymaptic.GeoBlazor.Core/Components/Widgets/Widget.cs
+++ b/src/dymaptic.GeoBlazor.Core/Components/Widgets/Widget.cs
@@ -69,6 +69,34 @@ public void OnWidgetCreated(IJSObjectReference jsObjectReference)
JsObjectReference = jsObjectReference;
}
+ ///
+ protected override async Task OnAfterRenderAsync(bool firstRender)
+ {
+ await base.OnAfterRenderAsync(firstRender);
+
+ if (WidgetChanged && MapRendered)
+ {
+ await UpdateWidget();
+ }
+ }
+
+
+ private async Task UpdateWidget()
+ {
+ WidgetChanged = false;
+
+ if (JsModule is null) return;
+
+ // ReSharper disable once RedundantCast
+ await JsModule!.InvokeVoidAsync("updateWidget", CancellationTokenSource.Token,
+ (object)this, View!.Id);
+ }
+
+ ///
+ /// Indicates if the widget has changed since the last render.
+ ///
+ protected bool WidgetChanged;
+
///
/// JS Object Reference to the widget
///
diff --git a/src/dymaptic.GeoBlazor.Core/Events/BookmarkSelectEvent.cs b/src/dymaptic.GeoBlazor.Core/Events/BookmarkSelectEvent.cs
new file mode 100644
index 00000000..4aca63dc
--- /dev/null
+++ b/src/dymaptic.GeoBlazor.Core/Events/BookmarkSelectEvent.cs
@@ -0,0 +1,10 @@
+using dymaptic.GeoBlazor.Core.Components.Widgets;
+
+namespace dymaptic.GeoBlazor.Core.Events;
+
+///
+/// Event object for a a bookmark select event from the BookmarksWidget.
+///
+/// The Bookmark that was selected
+public record BookmarkSelectEvent(Bookmark Bookmark);
+
diff --git a/src/dymaptic.GeoBlazor.Core/Events/ClickEvent.cs b/src/dymaptic.GeoBlazor.Core/Events/ClickEvent.cs
index 46724536..cb2bd521 100644
--- a/src/dymaptic.GeoBlazor.Core/Events/ClickEvent.cs
+++ b/src/dymaptic.GeoBlazor.Core/Events/ClickEvent.cs
@@ -41,4 +41,4 @@ namespace dymaptic.GeoBlazor.Core.Events;
///
public record ClickEvent(string Type, int? EventId, bool? Cancelable, Point MapPoint, double X, double Y, int Button,
int Buttons, double Timestamp, DomPointerEvent Native, PointerType? PointerType)
- : JsEvent(Type, EventId, Cancelable, Timestamp, Native, PointerType);
\ No newline at end of file
+ : JsEvent(Type, EventId, Cancelable, Timestamp, Native, PointerType);
diff --git a/src/dymaptic.GeoBlazor.Core/Scripts/arcGisJsInterop.ts b/src/dymaptic.GeoBlazor.Core/Scripts/arcGisJsInterop.ts
index e35780f7..0aa7dc6f 100644
--- a/src/dymaptic.GeoBlazor.Core/Scripts/arcGisJsInterop.ts
+++ b/src/dymaptic.GeoBlazor.Core/Scripts/arcGisJsInterop.ts
@@ -27,6 +27,7 @@ import Search from "@arcgis/core/widgets/Search";
import Locate from "@arcgis/core/widgets/Locate";
import Widget from "@arcgis/core/widgets/Widget";
import Measurement from "@arcgis/core/widgets/Measurement";
+import Bookmarks from "@arcgis/core/widgets/Bookmarks";
import GraphicsLayer from "@arcgis/core/layers/GraphicsLayer";
import FeatureLayer from "@arcgis/core/layers/FeatureLayer";
import Layer from "@arcgis/core/layers/Layer";
@@ -61,13 +62,15 @@ import {
buildDotNetPoint,
buildDotNetPopupTemplate,
buildDotNetSpatialReference,
- buildViewExtentUpdate
+ buildViewExtentUpdate,
+ buildDotNetBookmark
} from "./dotNetBuilder";
import {
buildJsAttributes,
buildJsExtent,
- buildJsFields, buildJsFormTemplate,
+ buildJsFields,
+ buildJsFormTemplate,
buildJsGeometry,
buildJsGraphic,
buildJsPoint,
@@ -77,7 +80,9 @@ import {
buildJsPortalItem,
buildJsRenderer,
buildJsSpatialReference,
- buildJsSymbol, templateTriggerActionHandler
+ buildJsSymbol,
+ templateTriggerActionHandler,
+ buildJsBookmark
} from "./jsBuilder";
import {
DotNetExtent,
@@ -984,6 +989,28 @@ export async function updateLayer(layerObject: any, viewId: string): Promise {
+ try {
+ setWaitCursor(viewId);
+ let currentWidget = arcGisObjectRefs[widgetObject.id] as Widget;
+ let view = arcGisObjectRefs[viewId] as View;
+
+ if (currentWidget === undefined) {
+ unsetWaitCursor(viewId);
+ return;
+ }
+
+ switch (widgetObject.type) {
+ case 'bookmarks':
+ let bookmarks = currentWidget as Bookmarks;
+ bookmarks.bookmarks = widgetObject.bookmarks.map(buildJsBookmark)
+ break;
+ }
+ unsetWaitCursor(viewId);
+ } catch (error) {
+ logError(error, viewId);
+ }
+}
export function findPlaces(addressQueryParams: any, symbol: any, popupTemplateObject: any, viewId: string): void {
try {
setWaitCursor(viewId);
@@ -1725,7 +1752,9 @@ async function createWidget(widget: any, viewId: string): Promise
view.ui.remove(content);
const expand = new Expand({
view,
- content: content
+ content: content,
+ expanded: widget.expanded,
+ mode: widget.mode,
});
if (hasValue(widget.autoCollapse)) {
@@ -1744,6 +1773,14 @@ async function createWidget(widget: any, viewId: string): Promise
expand.collapseIconClass = widget.collapseIconClass;
}
+ if (hasValue(widget.expandIcon)) {
+ expand.expandIcon = widget.expandIcon;
+ }
+
+ if (hasValue(widget.collapseIcon)) {
+ expand.collapseIcon = widget.collapseIcon;
+ }
+
if (hasValue(widget.expandTooltip)) {
expand.expandTooltip = widget.expandTooltip;
}
@@ -1767,6 +1804,26 @@ async function createWidget(widget: any, viewId: string): Promise
icon: widget.icon ?? undefined,
});
break;
+ case 'bookmarks':
+ const bookmarkWidget = new Bookmarks({
+ view: view,
+ editingEnabled: widget.editingEnabled,
+ disabled: widget.disabled,
+ icon: widget.icon,
+ label: widget.label
+ });
+ if (widget.bookmarks != null) {
+ bookmarkWidget.bookmarks = widget.bookmarks.map(buildJsBookmark);
+ }
+
+ bookmarkWidget.on('bookmark-select', (event) => {
+ widget.dotNetWidgetReference.invokeMethodAsync('OnJavascriptBookmarkSelect', {
+ bookmark: buildDotNetBookmark(event.bookmark)
+ });
+ });
+
+ newWidget = bookmarkWidget;
+ break;
default:
return null;
}
@@ -2388,4 +2445,19 @@ export function getCursor(viewId: string): string {
export function setCursor(cursorType: string, viewId: string) {
let view = arcGisObjectRefs[viewId] as MapView;
view.container.style.cursor = cursorType;
+}
+
+export function getWebMapBookmarks(viewId: string) {
+ let view = arcGisObjectRefs[viewId] as MapView;
+ if (view != null) {
+ let webMap = view.map as WebMap;
+ if (webMap != null) {
+ let arr = webMap.bookmarks.toArray();
+ if (arr instanceof Array) {
+ let abc = arr.map(buildDotNetBookmark);
+ return abc;
+ }
+ }
+ }
+ return null;
}
\ No newline at end of file
diff --git a/src/dymaptic.GeoBlazor.Core/Scripts/definitions.d.ts b/src/dymaptic.GeoBlazor.Core/Scripts/definitions.d.ts
index 4c52a6e2..21143826 100644
--- a/src/dymaptic.GeoBlazor.Core/Scripts/definitions.d.ts
+++ b/src/dymaptic.GeoBlazor.Core/Scripts/definitions.d.ts
@@ -486,4 +486,39 @@ export interface DotNetAttachment {
contentType: string;
uploadId: string;
data: string;
+}
+
+export interface DotNetBookmark {
+ name: string;
+ thumbnail: string;
+ viewpoint: DotNetViewpoint;
+ timeExtent: any;
+}
+
+export interface DotNetViewpoint {
+ rotation: number;
+ scale: number;
+ targetGeometry: DotNetGeometry;
+}
+
+export interface DotNetFeatureEffect {
+ excludedEffect: DotNetEffect[];
+ excludedLabelsVisible: boolean;
+ filter: DotNetFeatureFilter;
+ includedEffect: DotNetEffect[];
+}
+
+export interface DotNetEffect {
+ scale: number;
+ value: string;
+}
+
+export interface DotNetFeatureFilter {
+ distance: number;
+ geometry: DotNetGeometry;
+ objectIds: number[];
+ spatialRelationship: string;
+ timeExtent: any;
+ units: string;
+ where: string;
}
\ No newline at end of file
diff --git a/src/dymaptic.GeoBlazor.Core/Scripts/dotNetBuilder.ts b/src/dymaptic.GeoBlazor.Core/Scripts/dotNetBuilder.ts
index d203746e..c9954dce 100644
--- a/src/dymaptic.GeoBlazor.Core/Scripts/dotNetBuilder.ts
+++ b/src/dymaptic.GeoBlazor.Core/Scripts/dotNetBuilder.ts
@@ -39,7 +39,9 @@ import {
DotNetTextPopupContent,
DotNetTextSymbol,
DotNetViewHit,
- MapCollection
+ MapCollection,
+ DotNetBookmark,
+ DotNetViewpoint
} from "./definitions";
import Point from "@arcgis/core/geometry/Point";
import Polyline from "@arcgis/core/geometry/Polyline";
@@ -68,7 +70,7 @@ import ExpressionContent from "@arcgis/core/popup/content/ExpressionContent";
import ElementExpressionInfo from "@arcgis/core/popup/ElementExpressionInfo";
import FeatureLayer from "@arcgis/core/layers/FeatureLayer";
import GraphicsLayer from "@arcgis/core/layers/GraphicsLayer";
-import {arcGisObjectRefs} from "./arcGisJsInterop";
+import { arcGisObjectRefs } from "./arcGisJsInterop";
import SimpleMarkerSymbol from "@arcgis/core/symbols/SimpleMarkerSymbol";
import Symbol from "@arcgis/core/symbols/Symbol";
import Graphic from "@arcgis/core/Graphic";
@@ -628,4 +630,30 @@ export function buildViewExtentUpdate(view: View): any {
tilt: view.camera?.tilt
}
}
+}
+
+export function buildDotNetBookmark(bookmark: any): DotNetBookmark {
+ return {
+ name: bookmark.name,
+ thumbnail: bookmark.thumbnail != null ? bookmark.thumbnail.url : null,
+ timeExtent: buildDotNetTimeExtent(bookmark.timeExtent),
+ viewpoint: buildDotNetViewpoint(bookmark.viewpoint)
+ } as DotNetBookmark;
+}
+
+export function buildDotNetViewpoint(viewpoint: any): DotNetViewpoint | null {
+ if (viewpoint === null) return null;
+ return {
+ rotation: viewpoint.rotation,
+ scale: viewpoint.scale,
+ targetGeometry: buildDotNetGeometry(viewpoint.targetGeometry)
+ } as DotNetViewpoint;
+}
+
+export function buildDotNetTimeExtent(timeExtent: any): any | null {
+ if (timeExtent === null) return null;
+ return {
+ start: timeExtent.start.toISOString(),
+ end: timeExtent.end.toISOString()
+ } as any;
}
\ No newline at end of file
diff --git a/src/dymaptic.GeoBlazor.Core/Scripts/featureLayerView.ts b/src/dymaptic.GeoBlazor.Core/Scripts/featureLayerView.ts
index d5df4a34..45fd625a 100644
--- a/src/dymaptic.GeoBlazor.Core/Scripts/featureLayerView.ts
+++ b/src/dymaptic.GeoBlazor.Core/Scripts/featureLayerView.ts
@@ -1,8 +1,9 @@
import FeatureLayerView from "@arcgis/core/views/layers/FeatureLayerView";
import FeatureEffect from "@arcgis/core/layers/support/FeatureEffect";
+import FeatureFilter from "@arcgis/core/layers/support/FeatureFilter";
import Query from "@arcgis/core/rest/support/Query";
-import {DotNetFeatureSet, DotNetGraphic, DotNetQuery} from "./definitions";
-import {buildJsGraphic, buildJsQuery} from "./jsBuilder";
+import {DotNetFeatureSet, DotNetGraphic, DotNetQuery, DotNetFeatureEffect, DotNetFeatureFilter} from "./definitions";
+import {buildJsGraphic, buildJsQuery, buildJsFeatureEffect, buildJsFeatureFilter} from "./jsBuilder";
import {blazorServer, dotNetRefs, graphicsRefs} from "./arcGisJsInterop";
import Handle = __esri.Handle;
import {buildDotNetGeometry, buildDotNetGraphic, buildDotNetSpatialReference} from "./dotNetBuilder";
@@ -21,12 +22,14 @@ export default class FeatureLayerViewWrapper {
}
}
- setFeatureEffect(featureEffect: FeatureEffect): void {
+ setFeatureEffect(dnfeatureEffect: DotNetFeatureEffect): void {
+ let featureEffect = buildJsFeatureEffect(dnfeatureEffect);
this.featureLayerView.featureEffect = featureEffect;
}
- setFilter(filter: any): void {
- this.featureLayerView.filter = filter;
+ setFilter(dnDeatureFilter: DotNetFeatureFilter): void {
+ let featureFilter = buildJsFeatureFilter(dnDeatureFilter);
+ this.featureLayerView.filter = featureFilter;
}
setMaximumNumberOfFeatures(maximumNumberOfFeatures: number): void {
diff --git a/src/dymaptic.GeoBlazor.Core/Scripts/jsBuilder.ts b/src/dymaptic.GeoBlazor.Core/Scripts/jsBuilder.ts
index dcc8b306..1a793a0c 100644
--- a/src/dymaptic.GeoBlazor.Core/Scripts/jsBuilder.ts
+++ b/src/dymaptic.GeoBlazor.Core/Scripts/jsBuilder.ts
@@ -2,7 +2,7 @@
import Extent from "@arcgis/core/geometry/Extent";
import Graphic from "@arcgis/core/Graphic";
import PopupTemplate from "@arcgis/core/PopupTemplate";
-import {arcGisObjectRefs, triggerActionHandler} from "./arcGisJsInterop";
+import { arcGisObjectRefs, triggerActionHandler } from "./arcGisJsInterop";
import Geometry from "@arcgis/core/geometry/Geometry";
import Point from "@arcgis/core/geometry/Point";
import Polyline from "@arcgis/core/geometry/Polyline";
@@ -12,6 +12,10 @@ import SimpleRenderer from "@arcgis/core/renderers/SimpleRenderer";
import Renderer from "@arcgis/core/renderers/Renderer";
import Field from "@arcgis/core/layers/support/Field";
import Font from "@arcgis/core/symbols/Font";
+import Bookmark from "@arcgis/core/webmap/Bookmark"
+import Viewpoint from "@arcgis/core/Viewpoint";
+import FeatureEffect from "@arcgis/core/layers/support/FeatureEffect";
+import FeatureFilter from "@arcgis/core/layers/support/FeatureFilter";
import {
DotNetApplyEdits,
DotNetAttachmentsEdit,
@@ -48,7 +52,11 @@ import {
DotNetSymbol,
DotNetTextPopupContent,
DotNetTextSymbol,
- DotNetTopFeaturesQuery
+ DotNetTopFeaturesQuery,
+ DotNetBookmark,
+ DotNetViewpoint,
+ DotNetFeatureEffect,
+ DotNetFeatureFilter
} from "./definitions";
import PictureMarkerSymbol from "@arcgis/core/symbols/PictureMarkerSymbol";
import Popup from "@arcgis/core/widgets/Popup";
@@ -78,7 +86,7 @@ import SimpleLineSymbol from "@arcgis/core/symbols/SimpleLineSymbol";
import ElementExpressionInfo from "@arcgis/core/popup/ElementExpressionInfo";
import ChartMediaInfoValueSeries from "@arcgis/core/popup/content/support/ChartMediaInfoValueSeries";
import View from "@arcgis/core/views/View";
-import {buildDotNetGraphic} from "./dotNetBuilder";
+import { buildDotNetGraphic } from "./dotNetBuilder";
import ViewClickEvent = __esri.ViewClickEvent;
import PopupOpenOptions = __esri.PopupOpenOptions;
import PopupDockOptions = __esri.PopupDockOptions;
@@ -103,9 +111,10 @@ import RadioButtonsInput from "@arcgis/core/form/elements/inputs/RadioButtonsInp
import SwitchInput from "@arcgis/core/form/elements/inputs/SwitchInput";
import Domain from "@arcgis/core/layers/support/Domain";
+
export function buildJsSpatialReference(dotNetSpatialReference: DotNetSpatialReference): SpatialReference {
if (dotNetSpatialReference === undefined || dotNetSpatialReference === null) {
- return new SpatialReference({wkid: 4326});
+ return new SpatialReference({ wkid: 4326 });
}
let jsSpatialRef = new SpatialReference();
if (dotNetSpatialReference.wkid !== null) {
@@ -212,7 +221,7 @@ export function buildJsPopupTemplate(popupTemplateObject: DotNetPopupTemplate, v
} else {
content = async (featureSelection) => {
try {
- let results : DotNetPopupContent[] | null = await popupTemplateObject.dotNetPopupTemplateReference
+ let results: DotNetPopupContent[] | null = await popupTemplateObject.dotNetPopupTemplateReference
.invokeMethodAsync("OnContentFunction", buildDotNetGraphic(featureSelection.graphic));
return results?.map(r => buildJsPopupContent(r));
} catch (error) {
@@ -439,6 +448,34 @@ export function buildJsGeometry(geometry: DotNetGeometry): Geometry | null {
return geometry as any;
}
+export function buildJsBookmark(dnBookmark: DotNetBookmark): Bookmark | null {
+ if (dnBookmark === undefined || dnBookmark === null) return null;
+ let bookmark = new Bookmark();
+ bookmark.name = dnBookmark.name ?? undefined;
+ bookmark.timeExtent = dnBookmark.timeExtent ?? undefined;
+
+ if (!(dnBookmark.thumbnail == null)) {
+ //ESRI has this as an "object" with url property
+ let thumbnail = { url: dnBookmark.thumbnail };
+ bookmark.thumbnail = thumbnail;
+ } else {
+ bookmark.thumbnail = undefined;
+ }
+
+ bookmark.viewpoint = buildJsViewpoint(dnBookmark.viewpoint);
+
+ return bookmark as Bookmark;
+}
+
+export function buildJsViewpoint(dnViewpoint: DotNetViewpoint): Viewpoint | null {
+ if (dnViewpoint === undefined || dnViewpoint === null) return null;
+ let viewpoint = new Viewpoint();
+ viewpoint.rotation = dnViewpoint.rotation ?? undefined;
+ viewpoint.scale = dnViewpoint.scale ?? undefined;
+ viewpoint.targetGeometry = buildJsGeometry(dnViewpoint.targetGeometry);
+ return viewpoint as Viewpoint;
+}
+
export function buildJsPoint(dnPoint: DotNetPoint): Point | null {
if (dnPoint === undefined || dnPoint === null) return null;
let point = new Point({
@@ -451,7 +488,7 @@ export function buildJsPoint(dnPoint: DotNetPoint): Point | null {
if (hasValue(dnPoint.spatialReference)) {
point.spatialReference = buildJsSpatialReference(dnPoint.spatialReference);
} else {
- point.spatialReference = new SpatialReference({wkid: 4326});
+ point.spatialReference = new SpatialReference({ wkid: 4326 });
}
return point;
@@ -465,7 +502,7 @@ export function buildJsPolyline(dnPolyline: DotNetPolyline): Polyline | null {
if (hasValue(dnPolyline.spatialReference)) {
polyline.spatialReference = buildJsSpatialReference(dnPolyline.spatialReference);
} else {
- polyline.spatialReference = new SpatialReference({wkid: 4326});
+ polyline.spatialReference = new SpatialReference({ wkid: 4326 });
}
return polyline;
}
@@ -478,7 +515,7 @@ export function buildJsPolygon(dnPolygon: DotNetPolygon): Polygon | null {
if (hasValue(dnPolygon.spatialReference)) {
polygon.spatialReference = buildJsSpatialReference(dnPolygon.spatialReference);
} else {
- polygon.spatialReference = new SpatialReference({wkid: 4326});
+ polygon.spatialReference = new SpatialReference({ wkid: 4326 });
}
return polygon;
}
@@ -571,7 +608,7 @@ export function buildJsViewClickEvent(dotNetClickEvent: any): ViewClickEvent {
button: dotNetClickEvent.button ?? undefined,
buttons: dotNetClickEvent.buttons ?? undefined,
timestamp: dotNetClickEvent.timestamp ?? undefined
- } as ViewClickEvent
+ } as ViewClickEvent;
}
export async function buildJsPopup(dotNetPopup: any, viewId: string): Promise {
@@ -931,7 +968,7 @@ export function buildJsFormTemplate(dotNetFormTemplate: any): FormTemplate {
}
function buildJsFormTemplateElement(dotNetFormTemplateElement: any): Element {
- switch (dotNetFormTemplateElement.type){
+ switch (dotNetFormTemplateElement.type) {
case 'group':
return new GroupElement({
label: dotNetFormTemplateElement.label ?? undefined,
@@ -976,7 +1013,7 @@ function buildJsFormTemplateElement(dotNetFormTemplateElement: any): Element {
}
function buildJsDomain(dotNetDomain: any): any {
- switch (dotNetDomain?.type){
+ switch (dotNetDomain?.type) {
case 'coded-value':
return new CodedValueDomain({
name: dotNetDomain.name ?? undefined,
@@ -989,7 +1026,7 @@ function buildJsDomain(dotNetDomain: any): any {
minValue: dotNetDomain.minValue ?? undefined
});
}
-
+
return undefined;
}
@@ -1039,7 +1076,7 @@ function buildJsFormInput(dotNetFormInput: any): any {
onValue: dotNetFormInput.onValue ?? undefined
});
}
-
+
return undefined;
}
@@ -1079,4 +1116,62 @@ function buildJsPathsOrRings(pathsOrRings: any) {
function hasValue(prop: any): boolean {
return prop !== undefined && prop !== null;
+}
+
+export function buildJsFeatureEffect(dnFeatureEffect: DotNetFeatureEffect): FeatureEffect | null {
+ if (dnFeatureEffect === undefined || dnFeatureEffect === null) return null;
+ let featureEffect = new FeatureEffect();
+
+ //if there is a single effect, its a string, if there are effects based on scale its an array and has scale and value.
+ if (dnFeatureEffect.excludedEffect != null) {
+ if (dnFeatureEffect.excludedEffect.length === 1) {
+ featureEffect.excludedEffect = buildJsEffect(dnFeatureEffect.excludedEffect[0]);
+ } else {
+ featureEffect.excludedEffect = dnFeatureEffect.excludedEffect.map(buildJsEffect);
+ }
+
+ } else {
+ featureEffect.excludedEffect = undefined;
+ }
+ featureEffect.excludedLabelsVisible = dnFeatureEffect.excludedLabelsVisible ?? undefined;
+ featureEffect.filter = buildJsFeatureFilter(dnFeatureEffect.filter) ?? undefined;
+
+ if (dnFeatureEffect.includedEffect != null) {
+ if (dnFeatureEffect.includedEffect.length === 1) {
+ featureEffect.includedEffect = buildJsEffect(dnFeatureEffect.includedEffect[0]);
+ } else {
+ featureEffect.includedEffect = dnFeatureEffect.includedEffect.map(buildJsEffect);
+ }
+
+ } else {
+ featureEffect.includedEffect = undefined;
+ }
+
+ return featureEffect;
+}
+
+export function buildJsFeatureFilter(dnFeatureFilter: DotNetFeatureFilter): FeatureFilter | null {
+ if (dnFeatureFilter === undefined || dnFeatureFilter === null) return null;
+
+ let featureFilter = new FeatureFilter();
+ featureFilter.distance = dnFeatureFilter.distance ?? undefined;
+ featureFilter.geometry = buildJsGeometry(dnFeatureFilter.geometry);
+ featureFilter.objectIds = dnFeatureFilter.objectIds ?? undefined;
+ featureFilter.spatialRelationship = dnFeatureFilter.spatialRelationship ?? undefined;
+ featureFilter.timeExtent = dnFeatureFilter.timeExtent ?? undefined;
+ featureFilter.units = dnFeatureFilter.units ?? undefined;
+ featureFilter.where = dnFeatureFilter.where ?? undefined;
+ return featureFilter;
+}
+
+export function buildJsEffect(dnEffect: any): any {
+
+ if (dnEffect.scale != null) {
+ return {
+ value: dnEffect.value,
+ scale: dnEffect.scale
+ };
+ } else {
+ return dnEffect.value;
+ }
}
\ No newline at end of file