Skip to content

Commit

Permalink
Merge pull request #277 from dymaptic/bug/275-empty-feature-layer
Browse files Browse the repository at this point in the history
FeatureLayer Client-side Graphics method fixes
  • Loading branch information
TimPurdum authored Dec 19, 2023
2 parents b962b78 + 7ac3a08 commit f441fd3
Show file tree
Hide file tree
Showing 17 changed files with 260 additions and 123 deletions.
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@
<PropertyGroup>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<CoreVersion>2.5.1</CoreVersion>
<CoreVersion>2.5.2</CoreVersion>
</PropertyGroup>
</Project>
41 changes: 26 additions & 15 deletions src/dymaptic.GeoBlazor.Core/Components/Layers/FeatureLayer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -233,9 +233,12 @@ public FeatureLayer(string? url = null, PortalItem? portalItem = null, IReadOnly
/// <summary>
/// A collection of Graphic objects used to create a FeatureLayer.
/// </summary>
[Parameter]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
[RequiredProperty(nameof(Url), nameof(PortalItem))]
#pragma warning disable BL0007
public IReadOnlyCollection<Graphic>? Source
#pragma warning restore BL0007
{
get => _source;
set
Expand Down Expand Up @@ -288,8 +291,15 @@ public IReadOnlyCollection<Field>? Fields
/// <param name="graphic">
/// The graphic to add
/// </param>
/// <exception cref="InvalidOperationException">
/// If the layer is already loaded, you must use <see cref="ApplyEdits"/> to add graphics.
/// </exception>
public Task Add(Graphic graphic)
{
if (JsLayerReference is not null)
{
throw new InvalidOperationException("Cannot add graphics to a feature layer that is already loaded. Use ApplyEdits instead.");
}
return RegisterChildComponent(graphic);
}

Expand All @@ -299,25 +309,21 @@ public Task Add(Graphic graphic)
/// <param name="graphics">
/// The graphics to add
/// </param>
/// <exception cref="InvalidOperationException">
/// If the layer is already loaded, you must use <see cref="ApplyEdits"/> to add graphics.
/// </exception>
public async Task Add(IEnumerable<Graphic> graphics)
{
if (JsLayerReference is not null)
{
throw new InvalidOperationException("Cannot add graphics to a feature layer that is already loaded. Use ApplyEdits instead.");
}
foreach (Graphic graphic in graphics)
{
await RegisterChildComponent(graphic);
}
}

/// <summary>
/// Remove a graphic from the current layer
/// </summary>
/// <param name="graphic">
/// The graphic to remove
/// </param>
public Task Remove(Graphic graphic)
{
return UnregisterChildComponent(graphic);
}

/// <summary>
/// Add a field to the current layer's source
/// </summary>
Expand Down Expand Up @@ -357,6 +363,13 @@ public async Task SetPopupTemplate(PopupTemplate template)
/// </summary>
public async Task<FeatureEditsResult> ApplyEdits(FeatureEdits edits, FeatureEditOptions? options = null)
{
// Verify that the layer is loaded. Layers with no graphics are not rendered and therefore not loaded
// as far as GeoBlazor is concerned
if (JsModule is not null && JsLayerReference is null)
{
await Load();
}

return await JsLayerReference!.InvokeAsync<FeatureEditsResult>("applyEdits", edits, options,
View!.Id);
}
Expand Down Expand Up @@ -489,9 +502,8 @@ public override async Task RegisterChildComponent(MapComponent child)
case OrderedLayerOrderBy orderBy:
OrderBy ??= new HashSet<OrderedLayerOrderBy>();

if (!OrderBy.Contains(orderBy))
if (OrderBy.Add(orderBy))
{
OrderBy.Add(orderBy);
LayerChanged = true;
}

Expand All @@ -514,9 +526,8 @@ public override async Task RegisterChildComponent(MapComponent child)
case Field field:
_fields ??= new HashSet<Field>();

if (!_fields.Contains(field))
if (_fields.Add(field))
{
_fields.Add(field);
LayerChanged = true;
}

Expand Down
13 changes: 12 additions & 1 deletion src/dymaptic.GeoBlazor.Core/Components/Layers/Graphic.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,18 @@ public Graphic()
/// <param name="attributes">
/// Name-value pairs of fields and field values associated with the graphic.
/// </param>
/// <param name="visible">
/// Indicates the visibility of the graphic.
/// </param>
public Graphic(Geometry? geometry = null, Symbol? symbol = null, PopupTemplate? popupTemplate = null,
AttributesDictionary? attributes = null)
AttributesDictionary? attributes = null, bool? visible = null)
{
AllowRender = false;
#pragma warning disable BL0005
Geometry = geometry;
Symbol = symbol;
PopupTemplate = popupTemplate;
Visible = visible;

if (attributes is not null)
{
Expand All @@ -68,6 +72,13 @@ public Graphic(Geometry? geometry = null, Symbol? symbol = null, PopupTemplate?
[Parameter]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public AttributesDictionary Attributes { get; set; } = new();

/// <summary>
/// Indicates the visibility of the graphic. Default value: true.
/// </summary>
[Parameter]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public bool? Visible { get; set; }

/// <summary>
/// The geometry that defines the graphic's location.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -336,7 +336,6 @@ public ImageryLayer(string? url = null, PortalItem? portalItem = null, ImageryRe
/// <summary>
/// A complete list of fields that consists of raster attribute table fields, item pixel value, service pixel value, service pixel value with various server defined function templates, and raster attribute table fields.
/// </summary>
[Parameter]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public IReadOnlyCollection<Field>? RasterFields { get; set; }

Expand All @@ -350,9 +349,8 @@ public ImageryLayer(string? url = null, PortalItem? portalItem = null, ImageryRe
/// <summary>
/// The spatial reference of the image service.
/// </summary>
[Parameter]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public SpatialReference? SpatialReference { get; set; }
public SpatialReference? SpatialReference { get; internal set; }

/// <summary>
/// Determines if the layer will update its temporal data based on the view's timeExtent.
Expand Down
39 changes: 37 additions & 2 deletions src/dymaptic.GeoBlazor.Core/Components/Layers/Label.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public Label()
/// <param name="labelExpressionInfo">
/// Defines the labels for a <see cref="FeatureLayer" />.
/// </param>
public Label(string? labelPlacement = null, string? labelExpression = null,
public Label(LabelPlacement? labelPlacement = null, string? labelExpression = null,
LabelExpressionInfo? labelExpressionInfo = null)
{
#pragma warning disable BL0005 // Component parameter should not be set outside of its component.
Expand All @@ -58,7 +58,7 @@ public Label(string? labelPlacement = null, string? labelExpression = null,
/// </summary>
[Parameter]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? LabelPlacement { get; set; }
public LabelPlacement? LabelPlacement { get; set; }

/// <summary>
/// Defines the labels for a MapImageLayer.
Expand Down Expand Up @@ -205,4 +205,39 @@ public enum LabelPosition
Curved,
Parallel
#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member
}

/// <summary>
/// The position of the <see cref="Label"/>. Possible values are based on the feature type. This property requires a value.
/// </summary>
[JsonConverter(typeof(LabelPlacementStringConverter))]
public enum LabelPlacement
{
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
AboveCenter,
AboveLeft,
AboveRight,
BelowCenter,
BelowLeft,
BelowRight,
CenterCenter,
CenterLeft,
CenterRight,
AboveAfter,
AboveAlong,
AboveBefore,
AboveStart,
AboveEnd,
BelowAfter,
BelowAlong,
BelowBefore,
BelowStart,
BelowEnd,
CenterAfter,
CenterAlong,
CenterBefore,
CenterStart,
CenterEnd,
AlwaysHorizontal
#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member
}
20 changes: 8 additions & 12 deletions src/dymaptic.GeoBlazor.Core/Components/Layers/Layer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -157,23 +157,19 @@ public override async ValueTask DisposeAsync()
/// </remarks>
public async Task Load(CancellationToken cancellationToken = default)
{
if (JsLayerReference is not null)
{
// this layer has already been loaded
return;
}
AbortManager = new AbortManager(JsRuntime);
IJSObjectReference abortSignal = await AbortManager!.CreateAbortSignal(cancellationToken);
IJSObjectReference? arcGisPro = await JsModuleManager.GetArcGisJsPro(JsRuntime, cancellationToken);
IJSObjectReference arcGisJsInterop = await JsModuleManager.GetArcGisJsCore(JsRuntime, arcGisPro, cancellationToken);

if (arcGisPro is not null)
{
JsLayerReference = await arcGisPro.InvokeAsync<IJSObjectReference>("createProLayer",
// ReSharper disable once RedundantCast
cancellationToken, (object)this, true, View?.Id);
}
else
{
JsLayerReference = await arcGisJsInterop.InvokeAsync<IJSObjectReference>("createLayer",
// ReSharper disable once RedundantCast
cancellationToken, (object)this, true, View?.Id);
}
JsLayerReference = await arcGisJsInterop.InvokeAsync<IJSObjectReference>("createLayer",
// ReSharper disable once RedundantCast
cancellationToken, (object)this, true, View?.Id);

await JsLayerReference.InvokeVoidAsync("load", cancellationToken, abortSignal);

Expand Down
12 changes: 9 additions & 3 deletions src/dymaptic.GeoBlazor.Core/JsModuleManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,17 @@

namespace dymaptic.GeoBlazor.Core;

internal static class JsModuleManager
/// <summary>
/// Static class for managing the JavaScript modules used by GeoBlazor.
/// </summary>
public static class JsModuleManager
{
/// <summary>
/// Retrieves the main entry point for the GeoBlazor Core JavaScript module.
/// </summary>
public static async Task<IJSObjectReference> GetArcGisJsCore(IJSRuntime jsRuntime, IJSObjectReference? proModule, CancellationToken cancellationToken)
{
var version = System.Reflection.Assembly.GetAssembly(typeof(JsModuleManager))!.GetName().Version;
Version? version = System.Reflection.Assembly.GetAssembly(typeof(JsModuleManager))!.GetName().Version;

if (proModule is null)
{
Expand All @@ -24,7 +30,7 @@ public static async Task<IJSObjectReference> GetArcGisJsCore(IJSRuntime jsRuntim
/// </summary>
public static async Task<IJSObjectReference?> GetArcGisJsPro(IJSRuntime jsRuntime, CancellationToken cancellationToken)
{
var version = System.Reflection.Assembly.GetAssembly(typeof(JsModuleManager))!.GetName().Version;
Version? version = System.Reflection.Assembly.GetAssembly(typeof(JsModuleManager))!.GetName().Version;

LicenseType licenseType = Licensing.GetLicenseType();

Expand Down
4 changes: 4 additions & 0 deletions src/dymaptic.GeoBlazor.Core/Objects/AttributesDictionary.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ public AttributesDictionary(Dictionary<string, object> dictionary)
JsonValueKind.String => jsonElement.ToString(),
_ => jsonElement
};
if (typedValue is string stringValue && Guid.TryParse(stringValue, out Guid guidValue))
{
typedValue = guidValue;
}
_backingDictionary[kvp.Key] = (typedValue ?? default(object))!;
}

Expand Down
25 changes: 10 additions & 15 deletions src/dymaptic.GeoBlazor.Core/Scripts/arcGisJsInterop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -659,7 +659,7 @@ export async function hitTest(pointObject: any, eventId: string | null, viewId:
isEvent: boolean, options: DotNetHitTestOptions | null): Promise<DotNetHitTestResult | void> {
let view = arcGisObjectRefs[viewId] as MapView;
let result: HitTestResult;
let screenPoint = isEvent ? pointObject : { x: pointObject.x, y: pointObject.y };
let screenPoint = isEvent ? pointObject : view.toScreen(buildJsPoint(pointObject) as Point);

if (options !== null) {
let hitOptions = buildHitTestOptions(options, view);
Expand Down Expand Up @@ -694,7 +694,7 @@ export function toMap(screenPoint: any, viewId: string): DotNetPoint | null {

export function toScreen(mapPoint: any, viewId: string): ScreenPoint {
let view = arcGisObjectRefs[viewId] as MapView;
return view.toScreen(mapPoint);
return view.toScreen(buildJsPoint(mapPoint) as Point);
}

export function disposeView(viewId: string): void {
Expand Down Expand Up @@ -2139,6 +2139,12 @@ export async function addLayer(layerObject: any, viewId: string, isBasemapLayer?
}

export async function createLayer(layerObject: any, wrap?: boolean | null, viewId?: string | null): Promise<Layer | null> {
if (arcGisObjectRefs.hasOwnProperty(layerObject.id)) {
if (wrap) {
return getObjectReference(arcGisObjectRefs[layerObject.id] as Layer);
}
return arcGisObjectRefs[layerObject.id] as Layer;
}
let newLayer: Layer;
switch (layerObject.type) {
case 'graphics':
Expand Down Expand Up @@ -2184,7 +2190,8 @@ export async function createLayer(layerObject: any, wrap?: boolean | null, viewI
let featureLayer = newLayer as FeatureLayer;

copyValuesIfExists(layerObject, featureLayer, 'minScale', 'maxScale', 'orderBy', 'objectIdField',
'definitionExpression', 'outFields', 'legendEnabled', 'popupEnabled', 'apiKey', 'blendMode');
'definitionExpression', 'outFields', 'legendEnabled', 'popupEnabled', 'apiKey', 'blendMode',
'geometryType');

if (hasValue(layerObject.formTemplate)) {
featureLayer.formTemplate = buildJsFormTemplate(layerObject.formTemplate);
Expand Down Expand Up @@ -2495,9 +2502,6 @@ export async function createLayer(layerObject: any, wrap?: boolean | null, viewI
if (hasValue(layerObject.fields && layerObject.fields.length > 0)) {
imageryLayer.fields = buildJsFields(layerObject.fields);
}
if (hasValue(layerObject.fieldsIndex)) {
imageryLayer.fieldsIndex = layerObject.fieldsIndex;
}
if (hasValue(layerObject.mosaicRule)) {
imageryLayer.mosaicRule = layerObject.mosaicRule;
}
Expand All @@ -2513,21 +2517,12 @@ export async function createLayer(layerObject: any, wrap?: boolean | null, viewI
if (hasValue(layerObject.popupTemplate)) {
imageryLayer.popupTemplate = buildJsPopupTemplate(layerObject.popupTemplate, viewId ?? null) as PopupTemplate;
}
if (hasValue(layerObject.rasterFields)) {
imageryLayer.rasterFields = layerObject.rasterFields;
}
if (hasValue(layerObject.rasterFunction)) {
imageryLayer.rasterFunction = layerObject.rasterFunction;
}
if (hasValue(layerObject.rasterFunctionInfos)) {
imageryLayer.rasterFunctionInfos = layerObject.rasterFunctionInfos;
}
if (hasValue(layerObject.sourceJSON)) {
imageryLayer.sourceJSON = layerObject.sourceJSON;
}
if (hasValue(layerObject.spatialReference)) {
imageryLayer.spatialReference = buildJsSpatialReference(layerObject.spatialReference) as SpatialReference;
}
if (hasValue(layerObject.timeExtent)) {
imageryLayer.timeExtent = layerObject.timeExtent;
}
Expand Down
10 changes: 5 additions & 5 deletions src/dymaptic.GeoBlazor.Core/Scripts/definitions.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -606,8 +606,8 @@ export interface DotNetColorRamp {
export interface DotNetAlgorithmicColorRamp {
type: string;
algorithm: string;
fromColor: color;
toColor: color;
fromColor: string;
toColor: string;
}

export interface DotNetMultiPartColorRamp {
Expand All @@ -617,19 +617,19 @@ export interface DotNetMultiPartColorRamp {

export interface DotNetRasterColormapRenderer {
type: string;
colormapInfos: DotNetColormapInfo;
colormapInfos: DotNetColormapInfo[];
}

export interface DotNetColormapInfo {
value: number;
type: string;
color: color;
color: string;
label: string;
}

export interface DotNetFlowRenderer {
authoringInfo: DotNetAuthoringInfo;
color: color;
color: string;
type: string;
flowRepresentation: string;
density: number;
Expand Down
Loading

0 comments on commit f441fd3

Please sign in to comment.