From 7e9a9c80a3d189cad0d5ab345b4eae47ebdb94a7 Mon Sep 17 00:00:00 2001 From: Tim Purdum Date: Tue, 16 Dec 2025 09:42:16 -0600 Subject: [PATCH 01/89] implement logic components on JSSyncService --- docs/DeveloperGuide.md | 2 +- .../dymaptic.GeoBlazor.Core.Analyzers.csproj | 3 +- .../Components/GeometryEngine.cs | 307 ++++++++++++------ .../Components/LocationService.cs | 127 ++++---- .../Extensions/StringExtensions.cs | 15 +- .../JsModuleManager.cs | 17 + .../Model/LogicComponent.cs | 90 ++--- .../Model/ProjectionEngine.cs | 36 +- .../Scripts/geoBlazorCore.ts | 43 +-- .../Scripts/geometry.ts | 8 +- .../Scripts/geometryEngine.ts | 212 +++++------- .../Scripts/locationService.ts | 17 +- .../{projection.ts => projectionEngine.ts} | 23 +- .../Components/GeometryEngineTests.cs | 161 +++++++++ 14 files changed, 645 insertions(+), 416 deletions(-) rename src/dymaptic.GeoBlazor.Core/Scripts/{projection.ts => projectionEngine.ts} (88%) diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index 4f1ddd007..171b0ef79 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -4,7 +4,7 @@ development and refactoring of existing code, but may not be adhered to by all e ## TypeScript/ESBuild The TypeScript files are compiled as part of the build process using a source generator and ESBuild. -The source generator, `ESBuildLauncher.cs`, watches the `Scripts` folder for changes, and then runs the ESBuild +The source generator, `ESBuildGenerator.cs`, watches the `Scripts` folder for changes, and then runs the ESBuild compiler on the TypeScript files. The output is placed in the `wwwroot/js` folder. If you are making changes to the TypeScript files, you can run the ESBuild compiler manually by running diff --git a/src/dymaptic.GeoBlazor.Core.Analyzers/dymaptic.GeoBlazor.Core.Analyzers.csproj b/src/dymaptic.GeoBlazor.Core.Analyzers/dymaptic.GeoBlazor.Core.Analyzers.csproj index 9d7950a70..937d53341 100644 --- a/src/dymaptic.GeoBlazor.Core.Analyzers/dymaptic.GeoBlazor.Core.Analyzers.csproj +++ b/src/dymaptic.GeoBlazor.Core.Analyzers/dymaptic.GeoBlazor.Core.Analyzers.csproj @@ -3,9 +3,7 @@ netstandard2.0 false - enable latest - true true dymaptic.GeoBlazor.Core.Analyzers @@ -21,6 +19,7 @@ + diff --git a/src/dymaptic.GeoBlazor.Core/Components/GeometryEngine.cs b/src/dymaptic.GeoBlazor.Core/Components/GeometryEngine.cs index 417db6d05..abcd58598 100644 --- a/src/dymaptic.GeoBlazor.Core/Components/GeometryEngine.cs +++ b/src/dymaptic.GeoBlazor.Core/Components/GeometryEngine.cs @@ -7,18 +7,19 @@ namespace dymaptic.GeoBlazor.Core.Components; [CodeGenerationIgnore] public class GeometryEngine : LogicComponent { + // TODO: Add CancellationToken support to all methods + /// /// Default Constructor /// - /// - /// Injected Identity Manager reference - /// - public GeometryEngine(AuthenticationManager authenticationManager) : base(authenticationManager) + public GeometryEngine(IAppValidator appValidator, IJSRuntime jsRuntime, JsModuleManager jsModuleManager, + AuthenticationManager authenticationManager) + : base(appValidator, jsRuntime, jsModuleManager, authenticationManager) { } /// - protected override string ComponentName => nameof(GeometryEngine); + protected override string ComponentName => nameof(GeometryEngine).ToLowerFirstChar(); /// /// Creates planar (or Euclidean) buffer polygons at a specified distance around the input geometries. @@ -38,9 +39,10 @@ public GeometryEngine(AuthenticationManager authenticationManager) : base(authen /// /// The resulting buffers. /// + [SerializedMethod] public async Task Buffer(IEnumerable geometries, IEnumerable distances) { - return await InvokeAsync("buffer", geometries, distances, null, null); + return await InvokeAsync(nameof(GeometryEngine), parameters: [geometries, distances, null, null]); } /// @@ -64,10 +66,11 @@ public async Task Buffer(IEnumerable geometries, IEnumerabl /// /// The resulting buffers. /// + [SerializedMethod] public async Task Buffer(IEnumerable geometries, IEnumerable distances, GeometryEngineLinearUnit? unit) { - return await InvokeAsync("buffer", geometries, distances, unit, null); + return await InvokeAsync(nameof(GeometryEngine), parameters: [geometries, distances, unit, null]); } /// @@ -94,10 +97,11 @@ public async Task Buffer(IEnumerable geometries, IEnumerabl /// /// The resulting buffers. /// + [SerializedMethod] public async Task Buffer(IEnumerable geometries, IEnumerable distances, GeometryEngineLinearUnit? unit, bool? unionResults) { - return await InvokeAsync("buffer", geometries, distances, unit, unionResults); + return await InvokeAsync(nameof(GeometryEngine), parameters: [geometries, distances, unit, unionResults]); } /// @@ -118,9 +122,10 @@ public async Task Buffer(IEnumerable geometries, IEnumerabl /// /// The resulting buffer. /// + [SerializedMethod] public async Task Buffer(Geometry geometry, double distance) { - return await InvokeAsync("buffer", geometry, distance, null); + return await InvokeAsync(nameof(GeometryEngine), parameters: [geometry, distance, null]); } /// @@ -144,9 +149,10 @@ public async Task Buffer(Geometry geometry, double distance) /// /// The resulting buffer. /// + [SerializedMethod] public async Task Buffer(Geometry geometry, double distance, GeometryEngineLinearUnit? unit) { - return await InvokeAsync("buffer", geometry, distance, unit); + return await InvokeAsync(nameof(GeometryEngine), parameters: [geometry, distance, unit]); } /// @@ -161,9 +167,10 @@ public async Task Buffer(Geometry geometry, double distance, GeometryEn /// /// Clipped geometry. /// + [SerializedMethod] public async Task Clip(Geometry geometry, Extent extent) { - return await InvokeAsync("clip", geometry, extent); + return await InvokeAsync(nameof(GeometryEngine), parameters: [geometry, extent]); } /// @@ -178,9 +185,10 @@ public async Task Buffer(Geometry geometry, double distance, GeometryEn /// /// Returns true if the containerGeometry contains the insideGeometry. /// + [SerializedMethod] public async Task Contains(Geometry containerGeometry, Geometry insideGeometry) { - return await InvokeAsync("contains", containerGeometry, insideGeometry); + return await InvokeAsync(nameof(GeometryEngine), parameters: [containerGeometry, insideGeometry]); } /// @@ -196,9 +204,10 @@ public async Task Contains(Geometry containerGeometry, Geometry insideGeom /// Returns the convex hull of the input geometries. This is usually a polygon, but can also be a polyline (if the /// input is a set of points or polylines forming a straight line), or a point (in degenerate cases). /// + [SerializedMethod] public async Task ConvexHull(IEnumerable geometries, bool? merge = null) { - return await InvokeAsync("convexHull", geometries, merge); + return await InvokeAsync(nameof(GeometryEngine), parameters: [geometries, merge]); } /// @@ -211,9 +220,10 @@ public async Task ConvexHull(IEnumerable geometries, bool? /// Returns the convex hull of the input geometries. This is usually a polygon, but can also be a polyline (if the /// input is a set of points or polylines forming a straight line), or a point (in degenerate cases). /// + [SerializedMethod] public async Task ConvexHull(Geometry geometry) { - return await InvokeAsync("convexHull", geometry); + return await InvokeAsync(nameof(GeometryEngine), parameters: [geometry]); } /// @@ -228,9 +238,10 @@ public async Task ConvexHull(Geometry geometry) /// /// Returns true if geometry1 crosses geometry2. /// + [SerializedMethod] public async Task Crosses(Geometry geometry1, Geometry geometry2) { - return await InvokeAsync("crosses", geometry1, geometry2); + return await InvokeAsync(nameof(GeometryEngine), parameters: [geometry1, geometry2]); } /// @@ -245,9 +256,10 @@ public async Task Crosses(Geometry geometry1, Geometry geometry2) /// /// Returns an array of geometries created by cutting the input geometry with the cutter. /// + [SerializedMethod] public async Task Cut(Geometry geometry, Polyline cutter) { - return await InvokeAsync("cut", geometry, cutter); + return await InvokeAsync(nameof(GeometryEngine), parameters: [geometry, cutter]); } /// @@ -265,9 +277,10 @@ public async Task Cut(Geometry geometry, Polyline cutter) /// /// The densified geometry. /// + [SerializedMethod] public async Task Densify(Geometry geometry, double maxSegmentLength, GeometryEngineLinearUnit? maxSegmentLengthUnit = null) { - return await InvokeAsync("densify", geometry, maxSegmentLength, maxSegmentLengthUnit); + return await InvokeAsync(nameof(GeometryEngine), parameters: [geometry, maxSegmentLength, maxSegmentLengthUnit]); } /// @@ -282,9 +295,10 @@ public async Task Densify(Geometry geometry, double maxSegmentLength, /// /// Returns the geometry of inputGeometry minus the subtractor geometry. /// + [SerializedMethod] public async Task Difference(IEnumerable geometries, Geometry subtractor) { - return await InvokeAsync("difference", geometries, subtractor); + return await InvokeAsync(nameof(GeometryEngine), parameters: [geometries, subtractor]); } /// @@ -299,9 +313,10 @@ public async Task Difference(IEnumerable geometries, Geome /// /// Returns the geometry of inputGeometry minus the subtractor geometry. /// + [SerializedMethod] public async Task Difference(Geometry geometry, Geometry subtractor) { - return await InvokeAsync("difference", geometry, subtractor); + return await InvokeAsync(nameof(GeometryEngine), parameters: [geometry, subtractor]); } /// @@ -316,9 +331,10 @@ public async Task Difference(Geometry geometry, Geometry subtractor) /// /// Returns true if geometry1 and geometry2 are disjoint (don't intersect in any way). /// + [SerializedMethod] public async Task Disjoint(Geometry geometry1, Geometry geometry2) { - return await InvokeAsync("disjoint", geometry1, geometry2); + return await InvokeAsync(nameof(GeometryEngine), parameters: [geometry1, geometry2]); } /// @@ -336,9 +352,10 @@ public async Task Disjoint(Geometry geometry1, Geometry geometry2) /// /// Distance between the two input geometries. /// + [SerializedMethod] public async Task Distance(Geometry geometry1, Geometry geometry2, GeometryEngineLinearUnit? distanceUnit = null) { - return await InvokeAsync("distance", geometry1, geometry2, distanceUnit); + return await InvokeAsync(nameof(GeometryEngine), parameters: [geometry1, geometry2, distanceUnit]); } /// @@ -356,9 +373,10 @@ public async Task Distance(Geometry geometry1, Geometry geometry2, Geome /// /// Returns true if the two input geometries are equal. /// + [SerializedMethod] public async Task AreEqual(Geometry geometry1, Geometry geometry2) { - return await InvokeAsync("equals", geometry1, geometry2); + return await InvokeAsync(nameof(GeometryEngine), parameters: [geometry1, geometry2]); } /// @@ -370,9 +388,10 @@ public async Task AreEqual(Geometry geometry1, Geometry geometry2) /// /// Resolves to a object. /// + [SerializedMethod] public async Task ExtendedSpatialReferenceInfo(SpatialReference spatialReference) { - return await InvokeAsync("extendedSpatialReferenceInfo", spatialReference); + return await InvokeAsync(nameof(GeometryEngine), parameters: [spatialReference]); } /// @@ -387,9 +406,10 @@ public async Task ExtendedSpatialReferenceInfo(SpatialRefe /// /// The flipped geometry. /// + [SerializedMethod] public async Task FlipHorizontal(Geometry geometry, Point? flipOrigin = null) { - return await InvokeAsync("flipHorizontal", geometry, flipOrigin); + return await InvokeAsync(nameof(GeometryEngine), parameters: [geometry, flipOrigin]); } /// @@ -404,9 +424,10 @@ public async Task FlipHorizontal(Geometry geometry, Point? flipOrigin /// /// The flipped geometry. /// + [SerializedMethod] public async Task FlipVertical(Geometry geometry, Point? flipOrigin = null) { - return await InvokeAsync("flipVertical", geometry, flipOrigin); + return await InvokeAsync(nameof(GeometryEngine), parameters: [geometry, flipOrigin]); } /// @@ -427,9 +448,10 @@ public async Task FlipVertical(Geometry geometry, Point? flipOrigin = /// /// The generalized geometry. /// + [SerializedMethod] public async Task Generalize(Geometry geometry, double maxDeviation, bool? removeDegenerateParts = null, GeometryEngineLinearUnit? maxDeviationUnit = null) { - return await InvokeAsync("generalize", geometry, maxDeviation, removeDegenerateParts, maxDeviationUnit); + return await InvokeAsync(nameof(GeometryEngine), parameters: [geometry, maxDeviation, removeDegenerateParts, maxDeviationUnit]); } /// @@ -447,9 +469,10 @@ public async Task Generalize(Geometry geometry, double maxDeviation, b /// /// Area of the input geometry. /// + [SerializedMethod] public async Task GeodesicArea(Polygon geometry, GeometryEngineAreaUnit? unit = null) { - return await InvokeAsync("geodesicArea", geometry, unit); + return await InvokeAsync(nameof(GeometryEngine), parameters: [geometry, unit]); } /// @@ -470,9 +493,10 @@ public async Task GeodesicArea(Polygon geometry, GeometryEngineAreaUnit? /// /// The resulting buffers /// + [SerializedMethod] public async Task GeodesicBuffer(IEnumerable geometries, IEnumerable distances) { - return await InvokeAsync("geodesicBuffer", geometries, distances, null, null); + return await InvokeAsync(nameof(GeometryEngine), parameters: [geometries, distances, null, null]); } /// @@ -496,10 +520,11 @@ public async Task GeodesicBuffer(IEnumerable geometries, IE /// /// The resulting buffers /// + [SerializedMethod] public async Task GeodesicBuffer(IEnumerable geometries, IEnumerable distances, GeometryEngineLinearUnit? unit) { - return await InvokeAsync("geodesicBuffer", geometries, distances, unit, null); + return await InvokeAsync(nameof(GeometryEngine), parameters: [geometries, distances, unit, null]); } /// @@ -526,10 +551,11 @@ public async Task GeodesicBuffer(IEnumerable geometries, IE /// /// The resulting buffers /// + [SerializedMethod] public async Task GeodesicBuffer(IEnumerable geometries, IEnumerable distances, GeometryEngineLinearUnit? unit, bool? unionResults) { - return await InvokeAsync("geodesicBuffer", geometries, distances, unit, unionResults); + return await InvokeAsync(nameof(GeometryEngine), parameters: [geometries, distances, unit, unionResults]); } /// @@ -550,9 +576,10 @@ public async Task GeodesicBuffer(IEnumerable geometries, IE /// /// The resulting buffers /// + [SerializedMethod] public async Task GeodesicBuffer(Geometry geometry, double distance) { - return await InvokeAsync("geodesicBuffer", geometry, distance, null); + return await InvokeAsync(nameof(GeometryEngine), parameters: [geometry, distance, null]); } /// @@ -576,9 +603,10 @@ public async Task GeodesicBuffer(Geometry geometry, double distance) /// /// The resulting buffers /// + [SerializedMethod] public async Task GeodesicBuffer(Geometry geometry, double distance, GeometryEngineLinearUnit? unit) { - return await InvokeAsync("geodesicBuffer", geometry, distance, unit); + return await InvokeAsync(nameof(GeometryEngine), parameters: [geometry, distance, unit]); } /// @@ -593,9 +621,10 @@ public async Task GeodesicBuffer(Geometry geometry, double distance, Ge /// /// Returns the densified geometry. /// + [SerializedMethod] public async Task GeodesicDensify(Geometry geometry, double maxSegmentLength) { - return await InvokeAsync("geodesicDensify", geometry, maxSegmentLength, null); + return await InvokeAsync(nameof(GeometryEngine), parameters: [geometry, maxSegmentLength, null]); } /// @@ -613,10 +642,11 @@ public async Task GeodesicDensify(Geometry geometry, double maxSegment /// /// Returns the densified geometry. /// + [SerializedMethod] public async Task GeodesicDensify(Geometry geometry, double maxSegmentLength, GeometryEngineLinearUnit? maxSegmentLengthUnit) { - return await InvokeAsync("geodesicDensify", geometry, maxSegmentLength, maxSegmentLengthUnit); + return await InvokeAsync(nameof(GeometryEngine), parameters: [geometry, maxSegmentLength, maxSegmentLengthUnit]); } /// @@ -634,9 +664,10 @@ public async Task GeodesicDensify(Geometry geometry, double maxSegment /// /// Length of the input geometry. /// + [SerializedMethod] public async Task GeodesicLength(Geometry geometry, GeometryEngineLinearUnit? unit = null) { - return await InvokeAsync("geodesicLength", geometry, unit); + return await InvokeAsync(nameof(GeometryEngine), parameters: [geometry, unit]); } /// @@ -651,9 +682,10 @@ public async Task GeodesicLength(Geometry geometry, GeometryEngineLinear /// /// The intersections of the geometries. /// + [SerializedMethod] public async Task Intersect(IEnumerable geometries1, Geometry geometry2) { - return await InvokeAsync("intersect", geometries1, geometry2); + return await InvokeAsync(nameof(GeometryEngine), parameters: [geometries1, geometry2]); } /// @@ -668,9 +700,10 @@ public async Task Intersect(IEnumerable geometries1, Geome /// /// The intersections of the geometries. /// + [SerializedMethod] public async Task Intersect(Geometry geometry1, Geometry geometry2) { - return await InvokeAsync("intersect", geometry1, geometry2); + return await InvokeAsync(nameof(GeometryEngine), parameters: [geometry1, geometry2]); } /// @@ -685,9 +718,10 @@ public async Task Intersect(Geometry geometry1, Geometry geometry2) /// /// Returns true if the input geometries intersect each other. /// + [SerializedMethod] public async Task Intersects(Geometry geometry1, Geometry geometry2) { - return await InvokeAsync("intersects", geometry1, geometry2); + return await InvokeAsync(nameof(GeometryEngine), parameters: [geometry1, geometry2]); } /// @@ -699,9 +733,10 @@ public async Task Intersects(Geometry geometry1, Geometry geometry2) /// /// Returns true if the geometry is topologically simple. /// + [SerializedMethod] public async Task IsSimple(Geometry geometry) { - return await InvokeAsync("isSimple", geometry); + return await InvokeAsync(nameof(GeometryEngine), parameters: [geometry]); } /// @@ -716,9 +751,10 @@ public async Task IsSimple(Geometry geometry) /// /// Returns an object containing the nearest coordinate. /// + [SerializedMethod] public async Task NearestCoordinate(Geometry geometry, Point inputPoint) { - return await InvokeAsync("nearestCoordinate", geometry, inputPoint); + return await InvokeAsync(nameof(GeometryEngine), parameters: [geometry, inputPoint]); } /// @@ -733,9 +769,10 @@ public async Task NearestCoordinate(Geometry geometry, Point /// /// Returns an object containing the nearest vertex. /// + [SerializedMethod] public async Task NearestVertex(Geometry geometry, Point inputPoint) { - return await InvokeAsync("nearestVertex", geometry, inputPoint); + return await InvokeAsync(nameof(GeometryEngine), parameters: [geometry, inputPoint]); } /// @@ -756,9 +793,10 @@ public async Task NearestVertex(Geometry geometry, Point inp /// /// An array of objects containing the nearest vertices within the given searchRadius. /// + [SerializedMethod] public async Task NearestVertices(Geometry geometry, Point inputPoint, double searchRadius, int maxVertexCountToReturn) { - return await InvokeAsync("nearestVertices", geometry, inputPoint, searchRadius, maxVertexCountToReturn); + return await InvokeAsync(nameof(GeometryEngine), parameters: [geometry, inputPoint, searchRadius, maxVertexCountToReturn]); } /// @@ -773,9 +811,10 @@ public async Task NearestVertices(Geometry geometry, Point /// /// The offset geometries. /// + [SerializedMethod] public async Task Offset(IEnumerable geometries, double offsetDistance) { - return await InvokeAsync("offset", geometries, offsetDistance, null, null, null, null); + return await InvokeAsync(nameof(GeometryEngine), parameters: [geometries, offsetDistance, null, null, null, null]); } /// @@ -793,11 +832,11 @@ public async Task Offset(IEnumerable geometries, double of /// /// The offset geometries. /// + [SerializedMethod] public async Task Offset(IEnumerable geometries, double offsetDistance, GeometryEngineLinearUnit? offsetUnit) { - return await InvokeAsync("offset", geometries, offsetDistance, - offsetUnit, null, null, null); + return await InvokeAsync(nameof(GeometryEngine), parameters: [geometries, offsetDistance, offsetUnit, null, null, null]); } /// @@ -818,11 +857,11 @@ public async Task Offset(IEnumerable geometries, double of /// /// The offset geometries. /// + [SerializedMethod] public async Task Offset(IEnumerable geometries, double offsetDistance, GeometryEngineLinearUnit? offsetUnit, JoinType? joinType) { - return await InvokeAsync("offset", geometries, offsetDistance, - offsetUnit, joinType, null, null); + return await InvokeAsync(nameof(GeometryEngine), parameters: [geometries, offsetDistance, offsetUnit, joinType, null, null]); } /// @@ -846,11 +885,11 @@ public async Task Offset(IEnumerable geometries, double of /// /// The offset geometries. /// + [SerializedMethod] public async Task Offset(IEnumerable geometries, double offsetDistance, GeometryEngineLinearUnit? offsetUnit, JoinType? joinType, double? bevelRatio) { - return await InvokeAsync("offset", geometries, offsetDistance, - offsetUnit, joinType, bevelRatio, null); + return await InvokeAsync(nameof(GeometryEngine), parameters: [geometries, offsetDistance, offsetUnit, joinType, bevelRatio, null]); } /// @@ -877,11 +916,12 @@ public async Task Offset(IEnumerable geometries, double of /// /// The offset geometries. /// + [SerializedMethod] public async Task Offset(IEnumerable geometries, double offsetDistance, GeometryEngineLinearUnit? offsetUnit, JoinType? joinType, double? bevelRatio, double? flattenError) { - return await InvokeAsync("offset", geometries, offsetDistance, offsetUnit, joinType, bevelRatio, flattenError); + return await InvokeAsync(nameof(GeometryEngine), parameters: [geometries, offsetDistance, offsetUnit, joinType, bevelRatio, flattenError]); } /// @@ -896,9 +936,10 @@ public async Task Offset(IEnumerable geometries, double of /// /// The offset geometry. /// + [SerializedMethod] public async Task Offset(Geometry geometry, double offsetDistance) { - return await InvokeAsync("offset", geometry, offsetDistance, null, null, null, null); + return await InvokeAsync(nameof(GeometryEngine), parameters: [geometry, offsetDistance, null, null, null, null]); } /// @@ -916,11 +957,11 @@ public async Task Offset(Geometry geometry, double offsetDistance) /// /// The offset geometry. /// + [SerializedMethod] public async Task Offset(Geometry geometry, double offsetDistance, GeometryEngineLinearUnit? offsetUnit) { - return await InvokeAsync("offset", geometry, offsetDistance, - offsetUnit, null, null, null); + return await InvokeAsync(nameof(GeometryEngine), parameters: [geometry, offsetDistance, offsetUnit, null, null, null]); } /// @@ -941,11 +982,11 @@ public async Task Offset(Geometry geometry, double offsetDistance, /// /// The offset geometry. /// + [SerializedMethod] public async Task Offset(Geometry geometry, double offsetDistance, GeometryEngineLinearUnit? offsetUnit, JoinType? joinType) { - return await InvokeAsync("offset", geometry, offsetDistance, - offsetUnit, joinType, null, null); + return await InvokeAsync(nameof(GeometryEngine), parameters: [geometry, offsetDistance, offsetUnit, joinType, null, null]); } /// @@ -969,11 +1010,11 @@ public async Task Offset(Geometry geometry, double offsetDistance, /// /// The offset geometry. /// + [SerializedMethod] public async Task Offset(Geometry geometry, double offsetDistance, GeometryEngineLinearUnit? offsetUnit, JoinType? joinType, double? bevelRatio) { - return await InvokeAsync("offset", geometry, offsetDistance, - offsetUnit, joinType, bevelRatio, null); + return await InvokeAsync(nameof(GeometryEngine), parameters: [geometry, offsetDistance, offsetUnit, joinType, bevelRatio, null]); } /// @@ -1000,11 +1041,12 @@ public async Task Offset(Geometry geometry, double offsetDistance, /// /// The offset geometry. /// + [SerializedMethod] public async Task Offset(Geometry geometry, double offsetDistance, GeometryEngineLinearUnit? offsetUnit, JoinType? joinType, double? bevelRatio, double? flattenError) { - return await InvokeAsync("offset", geometry, offsetDistance, offsetUnit, joinType, bevelRatio, flattenError); + return await InvokeAsync(nameof(GeometryEngine), parameters: [geometry, offsetDistance, offsetUnit, joinType, bevelRatio, flattenError]); } /// @@ -1019,9 +1061,10 @@ public async Task Offset(Geometry geometry, double offsetDistance, /// /// Returns true if the two geometries overlap. /// + [SerializedMethod] public async Task Overlaps(Geometry geometry1, Geometry geometry2) { - return await InvokeAsync("overlaps", geometry1, geometry2); + return await InvokeAsync(nameof(GeometryEngine), parameters: [geometry1, geometry2]); } /// @@ -1030,9 +1073,10 @@ public async Task Overlaps(Geometry geometry1, Geometry geometry2) /// /// The input polygon. /// + [SerializedMethod] public async Task PlanarArea(Polygon geometry) { - return await InvokeAsync("planarArea", geometry, null); + return await InvokeAsync(nameof(GeometryEngine), parameters: [geometry, null]); } /// @@ -1047,9 +1091,10 @@ public async Task PlanarArea(Polygon geometry) /// /// The area of the input geometry. /// + [SerializedMethod] public async Task PlanarArea(Polygon geometry, GeometryEngineAreaUnit? unit) { - return await InvokeAsync("planarArea", geometry, unit); + return await InvokeAsync(nameof(GeometryEngine), parameters: [geometry, unit]); } /// @@ -1061,9 +1106,10 @@ public async Task PlanarArea(Polygon geometry, GeometryEngineAreaUnit? u /// /// The length of the input geometry. /// + [SerializedMethod] public async Task PlanarLength(Geometry geometry) { - return await InvokeAsync("planarLength", geometry); + return await InvokeAsync(nameof(GeometryEngine), parameters: [geometry]); } /// @@ -1078,9 +1124,10 @@ public async Task PlanarLength(Geometry geometry) /// /// The length of the input geometry. /// + [SerializedMethod] public async Task PlanarLength(Geometry geometry, GeometryEngineLinearUnit? unit) { - return await InvokeAsync("planarLength", geometry, unit); + return await InvokeAsync(nameof(GeometryEngine), parameters: [geometry, unit]); } /// @@ -1104,9 +1151,10 @@ public async Task PlanarLength(Geometry geometry, GeometryEngineLinearUn /// /// Returns true if the relation of the input geometries is accurate. /// + [SerializedMethod] public async Task Relate(Geometry geometry1, Geometry geometry2, string relation) { - return await InvokeAsync("relate", geometry1, geometry2, relation); + return await InvokeAsync(nameof(GeometryEngine), parameters: [geometry1, geometry2, relation]); } /// @@ -1124,9 +1172,10 @@ public async Task Relate(Geometry geometry1, Geometry geometry2, string re /// /// The rotated geometry. /// + [SerializedMethod] public async Task Rotate(Geometry geometry, double angle, Point rotationOrigin) { - return await InvokeAsync("rotate", geometry, angle, rotationOrigin); + return await InvokeAsync(nameof(GeometryEngine), parameters: [geometry, angle, rotationOrigin]); } /// @@ -1138,9 +1187,10 @@ public async Task Rotate(Geometry geometry, double angle, Point rotati /// /// The simplified geometry. /// + [SerializedMethod] public async Task Simplify(Geometry geometry) { - return await InvokeAsync("simplify", geometry); + return await InvokeAsync(nameof(GeometryEngine), parameters: [geometry]); } /// @@ -1155,9 +1205,10 @@ public async Task Simplify(Geometry geometry) /// /// The symmetric differences of the two geometries. /// + [SerializedMethod] public async Task SymmetricDifference(IEnumerable leftGeometries, Geometry rightGeometry) { - return await InvokeAsync("symmetricDifference", leftGeometries, rightGeometry); + return await InvokeAsync(nameof(GeometryEngine), parameters: [leftGeometries, rightGeometry]); } /// @@ -1172,9 +1223,10 @@ public async Task SymmetricDifference(IEnumerable leftGeom /// /// The symmetric differences of the two geometries. /// + [SerializedMethod] public async Task SymmetricDifference(Geometry leftGeometry, Geometry rightGeometry) { - return await InvokeAsync("symmetricDifference", leftGeometry, rightGeometry); + return await InvokeAsync(nameof(GeometryEngine), parameters: [leftGeometry, rightGeometry]); } /// @@ -1189,9 +1241,10 @@ public async Task SymmetricDifference(Geometry leftGeometry, Geometry /// /// When true, geometry1 touches geometry2. /// + [SerializedMethod] public async Task Touches(Geometry geometry1, Geometry geometry2) { - return await InvokeAsync("touches", geometry1, geometry2); + return await InvokeAsync(nameof(GeometryEngine), parameters: [geometry1, geometry2]); } /// @@ -1203,9 +1256,10 @@ public async Task Touches(Geometry geometry1, Geometry geometry2) /// /// The union of the geometries /// + [SerializedMethod] public async Task Union(params Geometry[] geometries) { - return await InvokeAsync("union", geometries.Cast()); + return await InvokeAsync(nameof(GeometryEngine), parameters: [geometries.Cast()]); } /// @@ -1217,9 +1271,10 @@ public async Task Union(params Geometry[] geometries) /// /// The union of the geometries /// + [SerializedMethod] public async Task Union(IEnumerable geometries) { - return await InvokeAsync("union", geometries.Cast()); + return await InvokeAsync(nameof(GeometryEngine), parameters: [geometries.Cast()]); } /// @@ -1234,9 +1289,10 @@ public async Task Union(IEnumerable geometries) /// /// Returns true if innerGeometry is within outerGeometry. /// + [SerializedMethod] public async Task Within(Geometry innerGeometry, Geometry outerGeometry) { - return await InvokeAsync("within", innerGeometry, outerGeometry); + return await InvokeAsync(nameof(GeometryEngine), parameters: [innerGeometry, outerGeometry]); } /// @@ -1248,10 +1304,11 @@ public async Task Within(Geometry innerGeometry, Geometry outerGeometry) /// /// Returns a new geometry instance. /// + [SerializedMethod] public async Task FromArcGisJson(string json) where T : Geometry { - return await InvokeAsync("fromJSON", json, typeof(T).Name); + return await InvokeAsync(nameof(GeometryEngine), parameters: [json, typeof(T).Name]); } /// @@ -1263,10 +1320,11 @@ public async Task FromArcGisJson(string json) /// /// The ArcGIS portal JSON representation of an instance of this class. /// + [SerializedMethod] public async Task ToArcGisJson(T geometry) where T : Geometry { - return await InvokeAsync("toJSON", geometry); + return await InvokeAsync(nameof(GeometryEngine), parameters: [geometry]); } /// @@ -1275,10 +1333,11 @@ public async Task ToArcGisJson(T geometry) /// /// Unlike the Clone methods in the Geometry classes, this method does a loop through the ArcGIS JS SDK. Therefore, if you are having issues with unpopulated fields in the geometry, try using this method instead. /// + [SerializedMethod] public async Task Clone(T geometry) where T : Geometry { - return await InvokeAsync("clone", geometry); + return await InvokeAsync(nameof(GeometryEngine), parameters: [geometry]); } /// @@ -1293,9 +1352,10 @@ public async Task Clone(T geometry) /// /// The centered extent. /// + [SerializedMethod] public async Task CenterExtentAt(Extent extent, Point point) { - return await InvokeAsync("centerExtentAt", extent, point); + return await InvokeAsync(nameof(GeometryEngine), parameters: [extent, point]); } /// @@ -1310,9 +1370,10 @@ public async Task CenterExtentAt(Extent extent, Point point) /// /// The expanded extent. /// + [SerializedMethod] public async Task Expand(Extent extent, double factor) { - return await InvokeAsync("expand", extent, factor); + return await InvokeAsync(nameof(GeometryEngine), parameters: [extent, factor]); } /// @@ -1324,9 +1385,10 @@ public async Task Expand(Extent extent, double factor) /// /// An array with either one Extent that's been shifted to within +/- 180 or two Extents if the original extent intersects the International Dateline. /// + [SerializedMethod] public async Task NormalizeExtent(Extent extent) { - return await InvokeAsync("normalizeExtent", extent); + return await InvokeAsync(nameof(GeometryEngine), parameters: [extent]); } /// @@ -1345,9 +1407,10 @@ public async Task NormalizeExtent(Extent extent) /// The offset distance in map units for the Z-coordinate. /// /// + [SerializedMethod] public async Task OffsetExtent(Extent extent, double dx, double dy, double dz = 0) { - return await InvokeAsync("offsetExtent", extent, dx, dy, dz); + return await InvokeAsync(nameof(GeometryEngine), parameters: [extent, dx, dy, dz]); } /// @@ -1359,9 +1422,10 @@ public async Task OffsetExtent(Extent extent, double dx, double dy, doub /// /// Returns a point with a normalized x-value. /// + [SerializedMethod] public async Task NormalizePoint(Point point) { - return await InvokeAsync("normalizePoint", point); + return await InvokeAsync(nameof(GeometryEngine), parameters: [point]); } /// @@ -1376,9 +1440,10 @@ public async Task NormalizePoint(Point point) /// /// Returns a new polyline with the added path. /// + [SerializedMethod] public async Task AddPath(Polyline polyline, MapPath points) { - return await InvokeAsync("addPath", polyline, points); + return await InvokeAsync(nameof(GeometryEngine), parameters: [polyline, points]); } /// @@ -1393,12 +1458,13 @@ public async Task AddPath(Polyline polyline, MapPath points) /// /// Returns a new polyline with the added path. /// + [SerializedMethod] public async Task AddPath(Polyline polyline, Point[] points) { var mapPoints = new List(); foreach (Point p in points) { - mapPoints.Add(new MapPoint(p.X!.Value, p.Y!.Value)); + mapPoints.Add(new MapPoint(p.X ?? p.Longitude!.Value, p.Y ?? p.Latitude!.Value)); } return await AddPath(polyline, new MapPath(mapPoints)); @@ -1419,9 +1485,10 @@ public async Task AddPath(Polyline polyline, Point[] points) /// /// Returns the point along the Polyline located in the given path and point indices. /// + [SerializedMethod] public async Task GetPoint(Polyline polyline, int pathIndex, int pointIndex) { - return await InvokeAsync("getPointOnPolyline", polyline, pathIndex, pointIndex); + return await InvokeAsync(nameof(GeometryEngine), parameters: [polyline, pathIndex, pointIndex]); } /// @@ -1442,9 +1509,10 @@ public async Task GetPoint(Polyline polyline, int pathIndex, int pointInd /// /// Returns a new polyline with the inserted point. /// + [SerializedMethod] public async Task InsertPoint(Polyline polyline, int pathIndex, int pointIndex, Point point) { - return await InvokeAsync("insertPointOnPolyline", polyline, pathIndex, pointIndex, point); + return await InvokeAsync(nameof(GeometryEngine), parameters: [polyline, pathIndex, pointIndex, point]); } /// @@ -1461,7 +1529,12 @@ public async Task InsertPoint(Polyline polyline, int pathIndex, int po /// public async Task<(Polyline PolyLine, Point[] Path)> RemovePath(Polyline polyline, int index) { - return await InvokeAsync<(Polyline PolyLine, Point[] Path)>("removePath", polyline, index); + // DON'T ADD [SerializedMethod], doesn't work here + // TODO: Refactor API for V5 + GeometryRemovePathResult result = + await InvokeAsync(nameof(GeometryEngine), parameters: [polyline, index]); + + return ((Polyline)result.Geometry, result.Path); } /// @@ -1479,9 +1552,15 @@ public async Task InsertPoint(Polyline polyline, int pathIndex, int po /// /// Returns an object with the modified polyline and the removed point. /// - public async Task<(Polyline PolyLine, Point Point)> RemovePoint(Polyline polyline, int pathIndex, int pointIndex) + public async Task<(Polyline Polyline, Point Point)> RemovePoint(Polyline polyline, int pathIndex, int pointIndex) { - return await InvokeAsync<(Polyline PolyLine, Point Point)>("removePointOnPolyline", polyline, pathIndex, pointIndex); + // DON'T ADD [SerializedMethod], doesn't work here + // TODO: Refactor API for V5 + GeometryRemovePointResult result = + await InvokeAsync(nameof(GeometryEngine), + parameters: [polyline, pathIndex, pointIndex]); + + return ((Polyline)result.Geometry, result.Point); } /// @@ -1502,9 +1581,10 @@ public async Task InsertPoint(Polyline polyline, int pathIndex, int po /// /// Returns a new polyline with the updated point. /// + [SerializedMethod] public async Task SetPoint(Polyline polyline, int pathIndex, int pointIndex, Point point) { - return await InvokeAsync("setPointOnPolyline", polyline, pathIndex, pointIndex, point); + return await InvokeAsync(nameof(GeometryEngine), parameters: [polyline, pathIndex, pointIndex, point]); } /// @@ -1519,9 +1599,10 @@ public async Task SetPoint(Polyline polyline, int pathIndex, int point /// /// Returns a new polygon with the added ring. /// + [SerializedMethod] public async Task AddRing(Polygon polygon, MapPath points) { - return await InvokeAsync("addRing", polygon, points); + return await InvokeAsync(nameof(GeometryEngine), parameters: [polygon, points]); } /// @@ -1536,12 +1617,13 @@ public async Task AddRing(Polygon polygon, MapPath points) /// /// Returns a new polygon with the added ring. /// + [SerializedMethod] public async Task AddRing(Polygon polygon, Point[] points) { var mapPoints = new List(); foreach (Point p in points) { - mapPoints.Add(new MapPoint(p.X!.Value, p.Y!.Value)); + mapPoints.Add(new MapPoint(p.X ?? p.Longitude!.Value, p.Y ?? p.Latitude!.Value)); } return await AddRing(polygon, new MapPath(mapPoints)); @@ -1556,9 +1638,10 @@ public async Task AddRing(Polygon polygon, Point[] points) /// /// A polygon instance representing the given extent. /// + [SerializedMethod] public async Task PolygonFromExtent(Extent extent) { - return await InvokeAsync("fromExtent", extent); + return await InvokeAsync(nameof(GeometryEngine), parameters: [extent]); } /// @@ -1576,9 +1659,10 @@ public async Task PolygonFromExtent(Extent extent) /// /// Returns the point at the specified ring index and point index. /// + [SerializedMethod] public async Task GetPoint(Polygon polygon, int ringIndex, int pointIndex) { - return await InvokeAsync("getPointOnPolygon", polygon, ringIndex, pointIndex); + return await InvokeAsync(nameof(GeometryEngine), parameters: [polygon, ringIndex, pointIndex]); } /// @@ -1599,9 +1683,10 @@ public async Task GetPoint(Polygon polygon, int ringIndex, int pointIndex /// /// Returns a new polygon with the inserted point. /// + [SerializedMethod] public async Task InsertPoint(Polygon polygon, int ringIndex, int pointIndex, Point point) { - return await InvokeAsync("insertPointOnPolygon", polygon, ringIndex, pointIndex, point); + return await InvokeAsync(nameof(GeometryEngine), parameters: [polygon, ringIndex, pointIndex, point]); } /// @@ -1616,9 +1701,10 @@ public async Task InsertPoint(Polygon polygon, int ringIndex, int point /// /// Returns true if the ring is clockwise and false for counterclockwise. /// + [SerializedMethod] public async Task IsClockwise(Polygon polygon, MapPath ring) { - return await InvokeAsync("isClockwise", polygon, ring); + return await InvokeAsync(nameof(GeometryEngine), parameters: [polygon, ring]); } /// @@ -1633,6 +1719,7 @@ public async Task IsClockwise(Polygon polygon, MapPath ring) /// /// Returns true if the ring is clockwise and false for counterclockwise. /// + [SerializedMethod] public async Task IsClockwise(Polygon polygon, Point[] ring) { var mapPoints = new List(); @@ -1661,7 +1748,13 @@ public async Task IsClockwise(Polygon polygon, Point[] ring) /// public async Task<(Polygon Polygon, Point Point)> RemovePoint(Polygon polygon, int ringIndex, int pointIndex) { - return await InvokeAsync<(Polygon Polygon, Point Point)>("removePointOnPolygon", polygon, ringIndex, pointIndex); + // DON'T ADD [SerializedMethod], doesn't work here + // TODO: Refactor API for V5 + GeometryRemovePointResult result = + await InvokeAsync(nameof(GeometryEngine), + parameters: [polygon, ringIndex, pointIndex]); + + return ((Polygon)result.Geometry, result.Point); } /// @@ -1678,7 +1771,12 @@ public async Task IsClockwise(Polygon polygon, Point[] ring) /// public async Task<(Polygon Polygon, Point[] Ring)> RemoveRing(Polygon polygon, int index) { - return await InvokeAsync<(Polygon Polygon, Point[] Ring)>("removeRing", polygon, index); + // DON'T ADD [SerializedMethod], doesn't work here + // TODO: Refactor API for V5 + GeometryRemovePathResult result = + await InvokeAsync(nameof(GeometryEngine), parameters: [polygon, index]); + + return ((Polygon)result.Geometry, result.Path); } /// @@ -1699,32 +1797,39 @@ public async Task IsClockwise(Polygon polygon, Point[] ring) /// /// Returns a new polygon with the updated point. /// + [SerializedMethod] public async Task SetPoint(Polygon polygon, int ringIndex, int pointIndex, Point point) { - return await InvokeAsync("setPointOnPolygon", polygon, ringIndex, pointIndex, point); + return await InvokeAsync(nameof(GeometryEngine), parameters: [polygon, ringIndex, pointIndex, point]); } /// /// Retrieves the center point of the extent in map units. /// + [SerializedMethod] public async Task GetExtentCenter(Extent extent) { - return await InvokeAsync("getExtentCenter", extent); + return await InvokeAsync(nameof(GeometryEngine), parameters: [extent]); } /// /// Retrieves the height of the extent in map units (the distance between ymin and ymax). /// + [SerializedMethod] public async Task GetExtentHeight(Extent extent) { - return await InvokeAsync("getExtentHeight", extent); + return await InvokeAsync(nameof(GeometryEngine), parameters: [extent]); } /// /// Retrieves the width of the extent in map units (the distance between xmin and xmax). /// + [SerializedMethod] public async Task GetExtentWidth(Extent extent) { - return await InvokeAsync("getExtentWidth", extent); + return await InvokeAsync(nameof(GeometryEngine), parameters: [extent]); } -} \ No newline at end of file +} + +internal record GeometryRemovePointResult(Geometry Geometry, Point Point); +internal record GeometryRemovePathResult(Geometry Geometry, Point[] Path); \ No newline at end of file diff --git a/src/dymaptic.GeoBlazor.Core/Components/LocationService.cs b/src/dymaptic.GeoBlazor.Core/Components/LocationService.cs index 5d2b681f9..266b55772 100644 --- a/src/dymaptic.GeoBlazor.Core/Components/LocationService.cs +++ b/src/dymaptic.GeoBlazor.Core/Components/LocationService.cs @@ -6,10 +6,9 @@ public partial class LocationService : LogicComponent /// /// Default Constructor /// - /// - /// Injected Identity Manager reference - /// - public LocationService(AuthenticationManager authenticationManager) : base(authenticationManager) + public LocationService(IAppValidator appValidator, IJSRuntime jsRuntime, JsModuleManager jsModuleManager, + AuthenticationManager authenticationManager) + : base(appValidator, jsRuntime, jsModuleManager, authenticationManager) { } @@ -26,6 +25,7 @@ public LocationService(AuthenticationManager authenticationManager) : base(authe /// /// The input addresses in the format supported by the geocode service. [CodeGenerationIgnore] + [SerializedMethod] public async Task> AddressesToLocations(List
addresses) { return await AddressesToLocations(ESRIGeoLocationUrl, addresses); @@ -40,6 +40,7 @@ public async Task> AddressesToLocations(List
add /// /// Limits the results to only search in the country provided. For example US for United States or SE for Sweden. Only applies to the World Geocode Service. See the World Geocoding Service documentation for more information. /// + [SerializedMethod] public async Task> AddressesToLocations(List
addresses, string? countryCode) { return await AddressesToLocations(ESRIGeoLocationUrl, addresses, countryCode); @@ -58,6 +59,7 @@ public async Task> AddressesToLocations(List
add /// Limit result to one or more categories. For example, "Populated Place" or "Scandinavian Food". /// Only applies to the World Geocode Service. See Category filtering (World Geocoding Service) for more information. /// + [SerializedMethod] public async Task> AddressesToLocations(List
addresses, string? countryCode, List? categories) { @@ -80,6 +82,7 @@ public async Task> AddressesToLocations(List
add /// /// Define the type of location, either "street" or "rooftop", of the point returned from the World Geocoding Service. /// + [SerializedMethod] public async Task> AddressesToLocations(List
addresses, string? countryCode, List? categories, LocationType? locationType) { @@ -105,6 +108,7 @@ public async Task> AddressesToLocations(List
add /// /// The spatial reference of the output geometries. If not specified, the output geometries are in the spatial reference of the input geometries when performing a reverse geocode and in the default spatial reference returned by the service if finding locations by address. /// + [SerializedMethod] public async Task> AddressesToLocations(List
addresses, string? countryCode, List? categories, LocationType? locationType, SpatialReference? outSpatialReference) @@ -135,6 +139,7 @@ public async Task> AddressesToLocations(List
add /// /// Additional options to be used for the data request /// + [SerializedMethod] public async Task> AddressesToLocations(List
addresses, string? countryCode, List? categories, LocationType? locationType, SpatialReference? outSpatialReference, RequestOptions? requestOptions) @@ -258,12 +263,13 @@ public Task> AddressesToLocations(string url, List /// Additional options to be used for the data request /// + [SerializedMethod] public async Task> AddressesToLocations(string url, List
addresses, string? countryCode = null, List? categories = null, LocationType? locationType = null, SpatialReference? outSpatialReference = null, RequestOptions? requestOptions = null) { - return await AddressesToLocations(url, addresses, countryCode, categories, locationType, - outSpatialReference, requestOptions, null); + return await InvokeAsync>(nameof(LocationService), parameters: [url, addresses, countryCode, categories, + locationType, outSpatialReference, requestOptions, null]); } #endregion @@ -278,6 +284,7 @@ public async Task> AddressesToLocations(string url, List< ///
/// The input addresses in the format supported by the geocode service. [CodeGenerationIgnore] + [SerializedMethod] public async Task> AddressesToLocations(List addresses) { return await AddressesToLocations(ESRIGeoLocationUrl, addresses); @@ -292,6 +299,7 @@ public async Task> AddressesToLocations(List addr /// /// Limits the results to only search in the country provided. For example US for United States or SE for Sweden. Only applies to the World Geocode Service. See the World Geocoding Service documentation for more information. /// + [SerializedMethod] public async Task> AddressesToLocations(List addresses, string? countryCode) { return await AddressesToLocations(ESRIGeoLocationUrl, addresses, countryCode); @@ -310,6 +318,7 @@ public async Task> AddressesToLocations(List addr /// Limit result to one or more categories. For example, "Populated Place" or "Scandinavian Food". /// Only applies to the World Geocode Service. See Category filtering (World Geocoding Service) for more information. /// + [SerializedMethod] public async Task> AddressesToLocations(List addresses, string? countryCode, List? categories) { @@ -332,6 +341,7 @@ public async Task> AddressesToLocations(List addr /// /// Define the type of location, either "street" or "rooftop", of the point returned from the World Geocoding Service. /// + [SerializedMethod] public async Task> AddressesToLocations(List addresses, string? countryCode, List? categories, LocationType? locationType) { @@ -357,6 +367,7 @@ public async Task> AddressesToLocations(List addr /// /// The spatial reference of the output geometries. If not specified, the output geometries are in the spatial reference of the input geometries when performing a reverse geocode and in the default spatial reference returned by the service if finding locations by address. /// + [SerializedMethod] public async Task> AddressesToLocations(List addresses, string? countryCode, List? categories, LocationType? locationType, SpatialReference? outSpatialReference) @@ -387,6 +398,7 @@ public async Task> AddressesToLocations(List addr /// /// Additional options to be used for the data request /// + [SerializedMethod] public async Task> AddressesToLocations(List addresses, string? countryCode, List? categories, LocationType? locationType, SpatialReference? outSpatialReference, RequestOptions? requestOptions) @@ -420,6 +432,7 @@ public async Task> AddressesToLocations(List addr /// /// The name of the single line address field for the ArcGIS Locator Service (for ArcGIS 10+), defaults to 'address'. /// + [SerializedMethod] public async Task> AddressesToLocations(List addresses, string? countryCode, List? categories, LocationType? locationType, SpatialReference? outSpatialReference, RequestOptions? requestOptions, string? addressSearchStringParameterName) @@ -577,13 +590,14 @@ public Task> AddressesToLocations(string url, List /// The name of the single line address field for the ArcGIS Locator Service (for ArcGIS 10+), defaults to 'address'. /// + [SerializedMethod] public async Task> AddressesToLocations(string url, List addresses, string? countryCode = null, List? categories = null, LocationType? locationType = null, SpatialReference? outSpatialReference = null, RequestOptions? requestOptions = null, string? addressSearchStringParameterName = null) { - return await AddressesToLocations(url, addresses as object, countryCode, categories, locationType, - outSpatialReference, requestOptions, addressSearchStringParameterName); + return await InvokeAsync>(nameof(LocationService), parameters: [url, addresses, countryCode, categories, + locationType, outSpatialReference, requestOptions, addressSearchStringParameterName]); } #endregion @@ -611,6 +625,7 @@ public Task> AddressToLocations(Address address) /// Limit result to one or more categories. For example, "Populated Place" or "Scandinavian Food". /// Only applies to the World Geocode Service. See Category filtering (World Geocoding Service) for more information. /// + [SerializedMethod] public async Task> AddressToLocations(Address address, List? categories) { return await AddressToLocations(ESRIGeoLocationUrl, address, categories); @@ -629,6 +644,7 @@ public async Task> AddressToLocations(Address address, Li /// Limit result to a specific country. For example, "US" for United States or "SE" for Sweden. /// Only applies to the World Geocode Service. See Geocode coverage (World Geocoding Service) for more information. /// + [SerializedMethod] public async Task> AddressToLocations(Address address, List? categories, string? countryCode) { @@ -649,6 +665,7 @@ public async Task> AddressToLocations(Address address, Li /// Only applies to the World Geocode Service. See Geocode coverage (World Geocoding Service) for more information. /// /// Allows the results of single geocode transactions to be persisted. + [SerializedMethod] public async Task> AddressToLocations(Address address, List? categories, string? countryCode, bool? forStorage) { @@ -670,6 +687,7 @@ public async Task> AddressToLocations(Address address, Li /// /// Allows the results of single geocode transactions to be persisted. /// Used to weight returned results for a specified area. + [SerializedMethod] public async Task> AddressToLocations(Address address, List? categories, string? countryCode, bool? forStorage, Point? location) { @@ -694,6 +712,7 @@ public async Task> AddressToLocations(Address address, Li /// /// Define the type of location, either "street" or "rooftop", of the point returned from the World Geocoding Service. /// + [SerializedMethod] public async Task> AddressToLocations(Address address, List? categories, string? countryCode, bool? forStorage, Point? location, LocationType? locationType) { @@ -720,6 +739,7 @@ public async Task> AddressToLocations(Address address, Li /// Define the type of location, either "street" or "rooftop", of the point returned from the World Geocoding Service. /// /// A suggestLocations result ID (magicKey). Used to query for a specific results information. + [SerializedMethod] public async Task> AddressToLocations(Address address, List? categories, string? countryCode, bool? forStorage, Point? location, LocationType? locationType, string? magicKey) @@ -748,6 +768,7 @@ public async Task> AddressToLocations(Address address, Li /// /// A suggestLocations result ID (magicKey). Used to query for a specific results information. /// Maximum results to return from the query. + [SerializedMethod] public async Task> AddressToLocations(Address address, List? categories, string? countryCode, bool? forStorage, Point? location, LocationType? locationType, string? magicKey, int? maxLocations) @@ -779,6 +800,7 @@ public async Task> AddressToLocations(Address address, Li /// /// The list of fields included in the returned result set. This list is a comma delimited list of field names. If you specify the shape field in the list of return fields, it is ignored. For non-intersection addresses you can specify the candidate fields as defined in the geocode service. For intersection addresses you can specify the intersection candidate fields. /// + [SerializedMethod] public async Task> AddressToLocations(Address address, List? categories, string? countryCode, bool? forStorage, Point? location, LocationType? locationType, string? magicKey, int? maxLocations, List? outFields) @@ -813,6 +835,7 @@ public async Task> AddressToLocations(Address address, Li /// /// The spatial reference of the output geometries. If not specified, the output geometries are in the spatial reference of the input geometries when performing a reverse geocode and in the default spatial reference returned by the service if finding locations by address. /// + [SerializedMethod] public async Task> AddressToLocations(Address address, List? categories, string? countryCode, bool? forStorage, Point? location, LocationType? locationType, string? magicKey, int? maxLocations, List? outFields, @@ -851,6 +874,7 @@ public async Task> AddressToLocations(Address address, Li /// /// Defines the extent within which the geocode server will search. Requires ArcGIS Server version 10.1 or greater. /// + [SerializedMethod] public async Task> AddressToLocations(Address address, List? categories, string? countryCode, bool? forStorage, Point? location, LocationType? locationType, string? magicKey, int? maxLocations, List? outFields, @@ -892,6 +916,7 @@ public async Task> AddressToLocations(Address address, Li /// /// Additional options to be used for the data request /// + [SerializedMethod] public async Task> AddressToLocations(Address address, List? categories = null, string? countryCode = null, bool? forStorage = null, Point? location = null, LocationType? locationType = null, string? magicKey = null, int? maxLocations = null, List? outFields = null, @@ -1213,6 +1238,7 @@ public Task> AddressToLocations(string url, Address addre /// /// #pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters + [SerializedMethod] public async Task> AddressToLocations(string url, Address address, #pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters List? categories = null, string? countryCode = null, bool? forStorage = null, Point? location = null, @@ -1250,6 +1276,7 @@ public Task> AddressToLocations(string address) /// Limit result to one or more categories. For example, "Populated Place" or "Scandinavian Food". /// Only applies to the World Geocode Service. See Category filtering (World Geocoding Service) for more information. /// + [SerializedMethod] public async Task> AddressToLocations(string address, List? categories) { return await AddressToLocations(ESRIGeoLocationUrl, address, categories); @@ -1268,6 +1295,7 @@ public async Task> AddressToLocations(string address, Lis /// Limit result to a specific country. For example, "US" for United States or "SE" for Sweden. /// Only applies to the World Geocode Service. See Geocode coverage (World Geocoding Service) for more information. /// + [SerializedMethod] public async Task> AddressToLocations(string address, List? categories, string? countryCode) { @@ -1288,6 +1316,7 @@ public async Task> AddressToLocations(string address, Lis /// Only applies to the World Geocode Service. See Geocode coverage (World Geocoding Service) for more information. /// /// Allows the results of single geocode transactions to be persisted. + [SerializedMethod] public async Task> AddressToLocations(string address, List? categories, string? countryCode, bool? forStorage) { @@ -1309,6 +1338,7 @@ public async Task> AddressToLocations(string address, Lis /// /// Allows the results of single geocode transactions to be persisted. /// Used to weight returned results for a specified area. + [SerializedMethod] public async Task> AddressToLocations(string address, List? categories, string? countryCode, bool? forStorage, Point? location) { @@ -1333,6 +1363,7 @@ public async Task> AddressToLocations(string address, Lis /// /// Define the type of location, either "street" or "rooftop", of the point returned from the World Geocoding Service. /// + [SerializedMethod] public async Task> AddressToLocations(string address, List? categories, string? countryCode, bool? forStorage, Point? location, LocationType? locationType) { @@ -1359,6 +1390,7 @@ public async Task> AddressToLocations(string address, Lis /// Define the type of location, either "street" or "rooftop", of the point returned from the World Geocoding Service. /// /// A suggestLocations result ID (magicKey). Used to query for a specific results information. + [SerializedMethod] public async Task> AddressToLocations(string address, List? categories, string? countryCode, bool? forStorage, Point? location, LocationType? locationType, string? magicKey) @@ -1387,6 +1419,7 @@ public async Task> AddressToLocations(string address, Lis /// /// A suggestLocations result ID (magicKey). Used to query for a specific results information. /// Maximum results to return from the query. + [SerializedMethod] public async Task> AddressToLocations(string address, List? categories, string? countryCode, bool? forStorage, Point? location, LocationType? locationType, string? magicKey, int? maxLocations) @@ -1418,6 +1451,7 @@ public async Task> AddressToLocations(string address, Lis /// /// The list of fields included in the returned result set. This list is a comma delimited list of field names. If you specify the shape field in the list of return fields, it is ignored. For non-intersection addresses you can specify the candidate fields as defined in the geocode service. For intersection addresses you can specify the intersection candidate fields. /// + [SerializedMethod] public async Task> AddressToLocations(string address, List? categories, string? countryCode, bool? forStorage, Point? location, LocationType? locationType, string? magicKey, int? maxLocations, List? outFields) @@ -1452,6 +1486,7 @@ public async Task> AddressToLocations(string address, Lis /// /// The spatial reference of the output geometries. If not specified, the output geometries are in the spatial reference of the input geometries when performing a reverse geocode and in the default spatial reference returned by the service if finding locations by address. /// + [SerializedMethod] public async Task> AddressToLocations(string address, List? categories, string? countryCode, bool? forStorage, Point? location, LocationType? locationType, string? magicKey, int? maxLocations, List? outFields, @@ -1490,6 +1525,7 @@ public async Task> AddressToLocations(string address, Lis /// /// Defines the extent within which the geocode server will search. Requires ArcGIS Server version 10.1 or greater. /// + [SerializedMethod] public async Task> AddressToLocations(string address, List? categories, string? countryCode, bool? forStorage, Point? location, LocationType? locationType, string? magicKey, int? maxLocations, List? outFields, @@ -1531,6 +1567,7 @@ public async Task> AddressToLocations(string address, Lis /// /// Additional options to be used for the data request /// + [SerializedMethod] public async Task> AddressToLocations(string address, List? categories = null, string? countryCode = null, bool? forStorage = null, Point? location = null, LocationType? locationType = null, string? magicKey = null, int? maxLocations = null, List? outFields = null, @@ -1576,6 +1613,7 @@ public async Task> AddressToLocations(string address, Lis /// /// The name of the single line address field for the ArcGIS Locator Service (for ArcGIS 10+), defaults to 'address'. /// + [SerializedMethod] public async Task> AddressToLocations(string address, List? categories = null, string? countryCode = null, bool? forStorage = null, Point? location = null, LocationType? locationType = null, string? magicKey = null, int? maxLocations = null, List? outFields = null, @@ -1900,6 +1938,7 @@ public Task> AddressToLocations(string url, string addres /// The name of the single line address field for the ArcGIS Locator Service (for ArcGIS 10+), defaults to 'address'. /// #pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters + [SerializedMethod] public async Task> AddressToLocations(string url, string address, #pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters List? categories = null, string? countryCode = null, bool? forStorage = null, Point? location = null, @@ -1923,6 +1962,7 @@ public async Task> AddressToLocations(string url, string /// The point at which to search for the closest address. The location should be in the same spatial reference as that of the geocode service. /// [CodeGenerationIgnore] + [SerializedMethod] public Task LocationToAddress(Point location) { return LocationToAddress(ESRIGeoLocationUrl, location); @@ -1993,7 +2033,7 @@ public Task LocationToAddress(Point location, LocationType? lo /// public Task LocationToAddress(string url, Point location) { - return InvokeAsync("locationToAddress", url, location, null, null, null); + return InvokeAsync(nameof(LocationService), parameters: [url, location, null, null, null]); } /// @@ -2008,7 +2048,7 @@ public Task LocationToAddress(string url, Point location) /// public Task LocationToAddress(string url, Point location, LocationType? locationType) { - return InvokeAsync("locationToAddress", url, location, locationType, null, null); + return InvokeAsync(nameof(LocationService), parameters: [url, location, locationType, null, null]); } /// @@ -2046,11 +2086,12 @@ public Task LocationToAddress(string url, Point location, Loca /// /// Additional options to be used for the data request /// + [SerializedMethod] public async Task LocationToAddress(string url, Point location, LocationType? locationType, SpatialReference? outSpatialReference, RequestOptions? requestOptions) { - return await InvokeAsync("locationToAddress", url, location, locationType, - outSpatialReference, requestOptions); + return await InvokeAsync(nameof(LocationService), parameters: [url, location, locationType, outSpatialReference, + requestOptions]); } /// @@ -2064,6 +2105,7 @@ public async Task LocationToAddress(string url, Point location /// The input text entered by a user which is used by the suggest operation to generate a list of possible matches. /// [CodeGenerationIgnore] + [SerializedMethod] public async Task> SuggestLocations(Point location, string text) { return await SuggestLocations(ESRIGeoLocationUrl, location, text); @@ -2082,6 +2124,7 @@ public async Task> SuggestLocations(Point location, strin /// /// A place or address type which can be used to filter suggest results. The parameter supports input of single category values or multiple comma-separated values. /// + [SerializedMethod] public async Task> SuggestLocations(Point location, string text, List? categories) { @@ -2104,6 +2147,7 @@ public async Task> SuggestLocations(Point location, strin /// /// Additional options to be used for the data request /// + [SerializedMethod] public async Task> SuggestLocations(Point location, string text, List? categories, RequestOptions? requestOptions) { @@ -2120,9 +2164,10 @@ public async Task> SuggestLocations(Point location, strin /// /// The input text entered by a user which is used by the suggest operation to generate a list of possible matches. /// + [SerializedMethod] public async Task> SuggestLocations(string url, Point location, string text) { - return await InvokeAsync>("suggestLocations", url, location, text, null, null); + return await InvokeAsync>(nameof(LocationService), parameters: [url, location, text, null, null]); } /// @@ -2138,11 +2183,11 @@ public async Task> SuggestLocations(string url, Point loc /// /// A place or address type which can be used to filter suggest results. The parameter supports input of single category values or multiple comma-separated values. /// + [SerializedMethod] public async Task> SuggestLocations(string url, Point location, string text, List? categories) { - return await InvokeAsync>("suggestLocations", url, location, text, - categories, null); + return await InvokeAsync>(nameof(LocationService), parameters: [url, location, text, categories, null]); } /// @@ -2161,11 +2206,12 @@ public async Task> SuggestLocations(string url, Point loc /// /// Additional options to be used for the data request /// + [SerializedMethod] public async Task> SuggestLocations(string url, Point location, string text, List? categories, RequestOptions? requestOptions) { - return await InvokeAsync>("suggestLocations", url, location, text, categories, - requestOptions); + return await InvokeAsync>(nameof(LocationService), parameters: [url, location, text, categories, + requestOptions]); } private async Task> AddressToLocations(string url, object address, @@ -2175,50 +2221,9 @@ private async Task> AddressToLocations(string url, object List? outFields = null, SpatialReference? outSpatialReference = null, Extent? searchExtent = null, RequestOptions? requestOptions = null, string? addressSearchStringParameterName = null) { - IJSStreamReference streamRef = await InvokeAsync("addressToLocations", url, address, - categories, countryCode, forStorage, location, locationType, magicKey, - maxLocations, outFields, outSpatialReference, searchExtent, requestOptions, addressSearchStringParameterName); - - return await streamRef.ReadJsStreamReferenceAsJSON>() ?? []; - } - - /// - /// Converts a list of addresses to locations. - /// - /// - /// URL to the ArcGIS Server REST resource that represents a locator service. - /// - /// - /// A list of addresses to be converted to locations. The addresses can be specified as a JSON object or an array of JSON objects. - /// - /// - /// Limit result to a specific country. For example, "US" for United States or "SE" for Sweden. - /// - /// - /// Limit result to one or more categories. For example, "Populated Place" or "Scandinavian Food". - /// - /// - /// Define the type of location, either "street" or "rooftop", of the point returned from the World Geocoding Service. - /// - /// - /// The spatial reference of the output geometries. If not specified, the output geometries are in the spatial reference of the input geometries when performing a reverse geocode and in the default spatial reference returned by the service if finding locations by address. - /// - /// - /// Additional options to be used for the data request - /// - /// - /// The name of the single line address field for the ArcGIS Locator Service (for ArcGIS 10+), defaults to 'address'. - /// - public async Task> AddressesToLocations(string url, object addresses, - string? countryCode = null, List? categories = null, LocationType? locationType = null, - SpatialReference? outSpatialReference = null, RequestOptions? requestOptions = null, - string? addressSearchStringParameterName = null) - { - IJSStreamReference streamRef = await InvokeAsync("addressesToLocations", url, - addresses, countryCode, categories, locationType, - outSpatialReference, requestOptions, addressSearchStringParameterName); - - return await streamRef.ReadJsStreamReferenceAsJSON>() ?? []; + return await InvokeAsync>(nameof(LocationService), parameters: [url, address, categories, countryCode, + forStorage, location, locationType, magicKey, maxLocations, outFields, outSpatialReference, + searchExtent, requestOptions, addressSearchStringParameterName]); } private const string ESRIGeoLocationUrl = "https://geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer"; diff --git a/src/dymaptic.GeoBlazor.Core/Extensions/StringExtensions.cs b/src/dymaptic.GeoBlazor.Core/Extensions/StringExtensions.cs index d413f40dd..e0fa509e2 100644 --- a/src/dymaptic.GeoBlazor.Core/Extensions/StringExtensions.cs +++ b/src/dymaptic.GeoBlazor.Core/Extensions/StringExtensions.cs @@ -1,4 +1,4 @@ -namespace dymaptic.GeoBlazor.Core.Extensions; +namespace dymaptic.GeoBlazor.Core.Extensions; internal static class StringExtensions { @@ -15,6 +15,19 @@ public static string ToLowerFirstChar(this string val) }); } + public static string ToUpperFirstChar(this string val) + { + return string.Create(val.Length, val, (span, txt) => + { + span[0] = char.ToUpper(txt[0]); + + for (var i = 1; i < txt.Length; i++) + { + span[i] = txt[i]; + } + }); + } + public static string ToKebabCase(this string val) { bool previousWasDigit = false; diff --git a/src/dymaptic.GeoBlazor.Core/JsModuleManager.cs b/src/dymaptic.GeoBlazor.Core/JsModuleManager.cs index 55718c82c..0a756d78e 100644 --- a/src/dymaptic.GeoBlazor.Core/JsModuleManager.cs +++ b/src/dymaptic.GeoBlazor.Core/JsModuleManager.cs @@ -52,6 +52,23 @@ public async ValueTask GetCoreJsModule(IJSRuntime jsRuntime, return _proModule; } + /// + /// Retrieves or creates a JavaScript wrapper for a logic component. + /// + /// The JS runtime to use for module loading. + /// The name of the logic component (e.g., "geometryEngine"). + /// A cancellation token. + /// A JavaScript object reference to the component wrapper. + public async ValueTask GetLogicComponent(IJSRuntime jsRuntime, string componentName, + CancellationToken cancellationToken) + { + IJSObjectReference? proModule = await GetProJsModule(jsRuntime, cancellationToken); + IJSObjectReference coreModule = await GetCoreJsModule(jsRuntime, proModule, cancellationToken); + + return await coreModule.InvokeAsync($"get{componentName.ToUpperFirstChar()}Wrapper", + cancellationToken); + } + private IJSObjectReference? _proModule; private IJSObjectReference? _coreModule; private bool _proChecked; diff --git a/src/dymaptic.GeoBlazor.Core/Model/LogicComponent.cs b/src/dymaptic.GeoBlazor.Core/Model/LogicComponent.cs index 5f48ec070..e8229fe55 100644 --- a/src/dymaptic.GeoBlazor.Core/Model/LogicComponent.cs +++ b/src/dymaptic.GeoBlazor.Core/Model/LogicComponent.cs @@ -1,21 +1,14 @@ -namespace dymaptic.GeoBlazor.Core.Model; +using System.Runtime.CompilerServices; + + +namespace dymaptic.GeoBlazor.Core.Model; /// /// A base class for non-map components, such as GeometryEngine, Projection, etc. /// -public abstract class LogicComponent : IDisposable +public abstract class LogicComponent(IAppValidator appValidator, IJSRuntime jsRuntime, + JsModuleManager jsModuleManager, AuthenticationManager authenticationManager) { - /// - /// Default constructor - /// - /// - /// Injected Identity Manager reference - /// - protected LogicComponent(AuthenticationManager authenticationManager) - { - AuthenticationManager = authenticationManager; - } - /// /// The name of the logic component. /// @@ -38,14 +31,6 @@ protected LogicComponent(AuthenticationManager authenticationManager) /// protected virtual string Library => "Core"; - /// - /// Disposes of the Logic Component and cancels all external calls - /// - public void Dispose() - { - CancellationTokenSource.Dispose(); - } - /// /// A JavaScript invokable method that returns a JS Error and converts it to an Exception. /// @@ -65,76 +50,69 @@ public void OnJavascriptError(JavascriptError error) /// /// Initializes the JavaScript reference component, if not already initialized. /// - public virtual async Task Initialize() + /// + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// + public virtual async Task Initialize(CancellationToken cancellationToken = default) { - if (Component is null) + if (!_validated) { - await AuthenticationManager.Initialize(); - IJSObjectReference module = await AuthenticationManager.GetCoreJsModule(); - - Component = await module.InvokeAsync($"get{ComponentName}Wrapper", - CancellationTokenSource.Token, DotNetComponentReference); + await appValidator.ValidateLicense(); + _validated = true; } - } - /// - /// Convenience method to invoke a JS function from the .NET logic component class. - /// - /// - /// The name of the JS function to call. - /// - /// - /// The collection of parameters to pass to the JS call. - /// - protected virtual async Task InvokeVoidAsync(string method, params object?[] parameters) - { - await Initialize(); + await authenticationManager.Initialize(); - await Component!.InvokeVoidAsync(method, CancellationTokenSource.Token, parameters); + Component ??= await jsModuleManager.GetLogicComponent(jsRuntime, ComponentName, cancellationToken); } /// /// Convenience method to invoke a JS function from the .NET logic component class. /// + /// + /// The name of the calling class. + /// /// /// The name of the JS function to call. /// /// /// The collection of parameters to pass to the JS call. /// - protected virtual async Task InvokeAsync(string method, params object?[] parameters) + internal virtual async Task InvokeVoidAsync(string className, [CallerMemberName] string method = "", + params object?[] parameters) { await Initialize(); - return await Component!.InvokeAsync(method, CancellationTokenSource.Token, parameters); + await Component!.InvokeVoidJsMethod(IsServer, method, className, CancellationToken.None, parameters); } - + /// /// Convenience method to invoke a JS function from the .NET logic component class. /// /// /// The name of the JS function to call. /// + /// + /// The name of the calling class. + /// /// /// The CancellationToken to cancel an asynchronous operation. /// /// /// The collection of parameters to pass to the JS call. /// - protected virtual async Task InvokeAsync(string method, CancellationToken cancellationToken, params object?[] parameters) + internal virtual async Task InvokeAsync(string className, [CallerMemberName]string method = "", + CancellationToken cancellationToken = default, params object?[] parameters) { - await Initialize(); - - return await Component!.InvokeAsync(method, cancellationToken, parameters); + await Initialize(cancellationToken); + + return await Component!.InvokeJsMethod(IsServer, method, className, cancellationToken, parameters); } + private bool _validated; + /// - /// The reference to the Authentication Manager. - /// - protected readonly AuthenticationManager AuthenticationManager; - - /// - /// Creates a cancellation token to control external calls + /// Boolean flag to identify if GeoBlazor is running in Blazor Server mode /// - protected readonly CancellationTokenSource CancellationTokenSource = new(); + protected bool IsServer => jsRuntime.GetType().Name.Contains("Remote"); } \ No newline at end of file diff --git a/src/dymaptic.GeoBlazor.Core/Model/ProjectionEngine.cs b/src/dymaptic.GeoBlazor.Core/Model/ProjectionEngine.cs index f75062fe5..544a2f342 100644 --- a/src/dymaptic.GeoBlazor.Core/Model/ProjectionEngine.cs +++ b/src/dymaptic.GeoBlazor.Core/Model/ProjectionEngine.cs @@ -1,4 +1,4 @@ -namespace dymaptic.GeoBlazor.Core.Model; +namespace dymaptic.GeoBlazor.Core.Model; /// /// A client-side projection engine for converting geometries from one SpatialReference to another. When projecting geometries the starting spatial reference must be specified on the input geometry. You can specify a specific geographic (datum) transformation for the project operation, or accept the default transformation if one is needed. @@ -10,11 +10,9 @@ public class ProjectionEngine : LogicComponent /// /// Default Constructor /// - /// - /// Injected Identity Manager reference - /// - public ProjectionEngine(AuthenticationManager authenticationManager) : - base(authenticationManager) + public ProjectionEngine(IAppValidator appValidator, IJSRuntime jsRuntime, JsModuleManager jsModuleManager, + AuthenticationManager authenticationManager) + : base(appValidator, jsRuntime, jsModuleManager, authenticationManager) { } @@ -33,9 +31,10 @@ public ProjectionEngine(AuthenticationManager authenticationManager) : /// /// A collection of projected geometries. /// + [SerializedMethod] public async Task Project(Geometry[] geometries, SpatialReference spatialReference) { - return await InvokeAsync("project", geometries, spatialReference, null); + return await InvokeAsync(nameof(ProjectionEngine), parameters: [geometries, spatialReference, null]); } /// @@ -53,11 +52,12 @@ public ProjectionEngine(AuthenticationManager authenticationManager) : /// /// A collection of projected geometries. /// + [SerializedMethod] public async Task Project(Geometry[] geometries, SpatialReference spatialReference, GeographicTransformation? geographicTransformation) { - return await InvokeAsync("project", geometries, spatialReference, - geographicTransformation); + return await InvokeAsync(nameof(ProjectionEngine), parameters: [geometries, spatialReference, + geographicTransformation]); } /// @@ -72,9 +72,10 @@ public ProjectionEngine(AuthenticationManager authenticationManager) : /// /// A projected geometry. /// + [SerializedMethod] public async Task Project(Geometry geometry, SpatialReference spatialReference) { - return await InvokeAsync("project", geometry, spatialReference, null); + return await InvokeAsync(nameof(ProjectionEngine), parameters: [geometry, spatialReference, null]); } /// @@ -92,11 +93,12 @@ public ProjectionEngine(AuthenticationManager authenticationManager) : /// /// A projected geometry. /// + [SerializedMethod] public async Task Project(Geometry geometry, SpatialReference spatialReference, GeographicTransformation? geographicTransformation) { - return await InvokeAsync("project", geometry, spatialReference, - geographicTransformation); + return await InvokeAsync(nameof(ProjectionEngine), parameters: [geometry, spatialReference, + geographicTransformation]); } /// @@ -114,11 +116,12 @@ public ProjectionEngine(AuthenticationManager authenticationManager) : /// /// A geographic transformation. /// + [SerializedMethod] public async Task GetTransformation(SpatialReference inSpatialReference, SpatialReference outSpatialReference, Extent extent) { - return await InvokeAsync("getTransformation", inSpatialReference, - outSpatialReference, extent); + return await InvokeAsync(nameof(ProjectionEngine), parameters: [inSpatialReference, + outSpatialReference, extent]); } /// @@ -136,10 +139,11 @@ public ProjectionEngine(AuthenticationManager authenticationManager) : /// /// A collection of geographic transformation. /// + [SerializedMethod] public async Task GetTransformations(SpatialReference inSpatialReference, SpatialReference outSpatialReference, Extent extent) { - return await InvokeAsync("getTransformations", - inSpatialReference, outSpatialReference, extent); + return await InvokeAsync(nameof(ProjectionEngine), + parameters: [inSpatialReference, outSpatialReference, extent]); } } \ No newline at end of file diff --git a/src/dymaptic.GeoBlazor.Core/Scripts/geoBlazorCore.ts b/src/dymaptic.GeoBlazor.Core/Scripts/geoBlazorCore.ts index 30e0410fd..99f5cc064 100644 --- a/src/dymaptic.GeoBlazor.Core/Scripts/geoBlazorCore.ts +++ b/src/dymaptic.GeoBlazor.Core/Scripts/geoBlazorCore.ts @@ -2,17 +2,12 @@ import { addArcGisLayer, graphicsRefs, buildArcGisMapView, - loadProtobuf, - GraphicCollectionSerializationRecord, popupTemplateRefs, actionHandlers, esriConfig, resetMapComponent } from './arcGisJsInterop'; import AuthenticationManager from "./authenticationManager"; -import ProjectionWrapper from "./projection"; -import GeometryEngineWrapper from "./geometryEngine"; -import LocatorWrapper from "./locationService"; import MapViewWrapper from "./mapView"; // backwards-compatibility re-export, since everything used to be in this module @@ -557,6 +552,22 @@ export function buildEncodedJson(object: any): Uint8Array { return encoder.encode(json!); } +export function getDefaultClassInstanceFromModule(module: any) { + if (module === null || module === undefined) { + return null; + } + if (module.default !== undefined) { + return new module.default(); + } + // find the first class in the module + for (const key in module) { + if (typeof module[key] === 'function') { + return new module[key](); + } + } + return null; +} + // Converts a base64 string to an ArrayBuffer export function base64ToArrayBuffer(base64): Uint8Array { const binaryString = atob(base64); @@ -580,26 +591,4 @@ export function getAuthenticationManager(dotNetRef: any, apiKey: string | null, return _authenticationManager; } -export async function getProjectionEngineWrapper(): Promise { - if (GraphicCollectionSerializationRecord === undefined) { - loadProtobuf(); - } - return new ProjectionWrapper(); -} - -export async function getGeometryEngineWrapper(): Promise { - if (GraphicCollectionSerializationRecord === undefined) { - loadProtobuf(); - } - return new GeometryEngineWrapper(); -} - -export async function getLocationServiceWrapper(): Promise { - if (GraphicCollectionSerializationRecord === undefined) { - loadProtobuf(); - } - - return new LocatorWrapper(); -} - // endregion \ No newline at end of file diff --git a/src/dymaptic.GeoBlazor.Core/Scripts/geometry.ts b/src/dymaptic.GeoBlazor.Core/Scripts/geometry.ts index 0bb27900e..c7ebc5c98 100644 --- a/src/dymaptic.GeoBlazor.Core/Scripts/geometry.ts +++ b/src/dymaptic.GeoBlazor.Core/Scripts/geometry.ts @@ -4,7 +4,10 @@ import {buildDotNetPolyline, buildJsPolyline} from "./polyline"; import {buildDotNetExtent, buildJsExtent} from "./extent"; import {buildDotNetMultipoint, buildJsMultipoint} from "./multipoint"; import {buildDotNetMesh, buildJsMesh} from "./mesh"; -import {hasValue} from './geoBlazorCore'; +import { + hasValue, + removeCircularReferences, +} from './geoBlazorCore'; export function buildDotNetGeometry(geometry: any): any { if (!hasValue(geometry)) { @@ -23,6 +26,9 @@ export function buildDotNetGeometry(geometry: any): any { return buildDotNetMultipoint(geometry); case "mesh": return buildDotNetMesh(geometry); + default: + // unknown type + return removeCircularReferences(geometry); } } diff --git a/src/dymaptic.GeoBlazor.Core/Scripts/geometryEngine.ts b/src/dymaptic.GeoBlazor.Core/Scripts/geometryEngine.ts index 1d07e3e01..09048391c 100644 --- a/src/dymaptic.GeoBlazor.Core/Scripts/geometryEngine.ts +++ b/src/dymaptic.GeoBlazor.Core/Scripts/geometryEngine.ts @@ -15,7 +15,7 @@ import {buildDotNetExtent, buildJsExtent} from "./extent"; import {buildDotNetPolygon, buildJsPolygon} from "./polygon"; import {buildDotNetGeometry, buildJsGeometry} from "./geometry"; import {buildDotNetPoint, buildJsPoint} from "./point"; -import {buildDotNetPolyline, buildJsPolyline} from "./polyline"; +import {buildDotNetPolyline, buildJsPathsOrRings, buildJsPolyline} from "./polyline"; import LinearUnits = __esri.LinearUnits; import SpatialReferenceInfo = __esri.SpatialReferenceInfo; import AreaUnits = __esri.AreaUnits; @@ -23,13 +23,9 @@ import Mesh from "@arcgis/core/geometry/Mesh"; import Multipoint from "@arcgis/core/geometry/Multipoint"; import { hasValue } from './geoBlazorCore'; import GeometryUnion = __esri.GeometryUnion; +import BaseComponent from "./baseComponent"; -export default class GeometryEngineWrapper { - private readonly returnToDotNet: boolean; - - constructor(returnToDotNet: boolean = true) { - this.returnToDotNet = returnToDotNet; - } +export default class GeometryEngineWrapper extends BaseComponent { async buffer(geometries: DotNetGeometry | Array, distances: number | Array, unit: LinearUnits | null, unionResults: boolean | null): Promise { @@ -47,7 +43,7 @@ export default class GeometryEngineWrapper { options.union = unionResults; } jsBuffer = bufferOperator.executeMany(jsGeometries as GeometryUnion[], distances as number[], options); - return this.returnToDotNet ? jsBuffer.map(g => buildDotNetPolygon(g) as DotNetPolygon) : jsBuffer; + return jsBuffer.map(g => buildDotNetPolygon(g) as DotNetPolygon); } jsGeometries = buildJsGeometry(geometries) as Geometry; @@ -56,22 +52,20 @@ export default class GeometryEngineWrapper { } jsBuffer = bufferOperator.execute(jsGeometries as GeometryUnion, distances as number, options) as Polygon; - return this.returnToDotNet ? buildDotNetPolygon(jsBuffer) : jsBuffer; + return buildDotNetPolygon(jsBuffer); } async clip(geometry: DotNetGeometry, extent: DotNetExtent): Promise { let clipOperator = await import('@arcgis/core/geometry/operators/clipOperator'); let jsClip = clipOperator.execute(buildJsGeometry(geometry) as any, buildJsExtent(extent, null)); - return this.returnToDotNet ? buildDotNetGeometry(jsClip) : jsClip; - + return buildDotNetGeometry(jsClip); } async contains(containerGeometry: DotNetGeometry, insideGeometry: DotNetGeometry): Promise { let containsOperator = await import('@arcgis/core/geometry/operators/containsOperator'); return containsOperator.execute(buildJsGeometry(containerGeometry) as GeometryUnion, buildJsGeometry(insideGeometry) as GeometryUnion); - } async convexHull(geometries: Array | DotNetGeometry, merge: boolean | null): Promise { @@ -86,27 +80,24 @@ export default class GeometryEngineWrapper { options.merge = merge; } jsHull = convexHullOperator.executeMany(jsGeometries as GeometryUnion[], options) as GeometryUnion[]; - return this.returnToDotNet ? jsHull.map(g => buildDotNetGeometry(g) as DotNetGeometry) : jsHull; + return jsHull.map(g => buildDotNetGeometry(g) as DotNetGeometry); } jsGeometries = buildJsGeometry(geometries) as Geometry; jsHull = convexHullOperator.execute(jsGeometries as any) as GeometryUnion; - return this.returnToDotNet ? buildDotNetGeometry(jsHull) : jsHull; - + return buildDotNetGeometry(jsHull); } async crosses(geometry1: DotNetGeometry, geometry2: DotNetGeometry): Promise { let crossesOperator = await import('@arcgis/core/geometry/operators/crossesOperator'); return crossesOperator.execute(buildJsGeometry(geometry1) as GeometryUnion, buildJsGeometry(geometry2) as GeometryUnion); - } async cut(geometry: DotNetGeometry, cutter: DotNetPolyline): Promise { let cutOperator = await import('@arcgis/core/geometry/operators/cutOperator'); let jsCut = cutOperator.execute(buildJsGeometry(geometry) as GeometryUnion, buildJsPolyline(cutter) as Polyline); - return this.returnToDotNet ? jsCut.map(g => buildDotNetGeometry(g) as DotNetGeometry) : jsCut; - + return jsCut.map(g => buildDotNetGeometry(g) as DotNetGeometry); } async densify(geometry: DotNetGeometry, maxSegmentLength: number, maxSegmentLengthUnit: LinearUnits | null) @@ -120,8 +111,7 @@ export default class GeometryEngineWrapper { } jsDensified = densifyOperator.execute(jsGeometry, maxSegmentLength, options); - return this.returnToDotNet ? buildDotNetGeometry(jsDensified) : jsDensified; - + return buildDotNetGeometry(jsDensified); } async difference(geometries: Array | DotNetGeometry, subtractor: DotNetGeometry) @@ -133,18 +123,17 @@ export default class GeometryEngineWrapper { jsGeometries = []; geometries.forEach(g => (jsGeometries as Array).push(buildJsGeometry(g) as GeometryUnion)); jsDifference = differenceOperator.executeMany(jsGeometries as GeometryUnion[], buildJsGeometry(subtractor) as GeometryUnion); - return this.returnToDotNet ? jsDifference.map(g => buildDotNetGeometry(g) as DotNetGeometry) : jsDifference; + return jsDifference.map(g => buildDotNetGeometry(g) as DotNetGeometry); } jsGeometries = buildJsGeometry(geometries) as GeometryUnion; jsDifference = differenceOperator.execute(jsGeometries as any, buildJsGeometry(subtractor) as GeometryUnion); - return this.returnToDotNet ? buildDotNetGeometry(jsDifference) : jsDifference; + return buildDotNetGeometry(jsDifference); } async disjoint(geometry1: DotNetGeometry, geometry2: DotNetGeometry): Promise { let disjointOperator = await import('@arcgis/core/geometry/operators/disjointOperator'); return disjointOperator.execute(buildJsGeometry(geometry1) as GeometryUnion, buildJsGeometry(geometry2) as GeometryUnion); - } async distance(geometry1: DotNetGeometry, geometry2: DotNetGeometry, distanceUnit: LinearUnits | null) @@ -160,7 +149,7 @@ export default class GeometryEngineWrapper { return distanceOperator.execute(jsGeometry1, jsGeometry2, options); } - async equals(geometry1: DotNetGeometry, geometry2: DotNetGeometry): Promise { + async areEqual(geometry1: DotNetGeometry, geometry2: DotNetGeometry): Promise { let equalsOperator = await import('@arcgis/core/geometry/operators/equalsOperator'); return equalsOperator.execute(buildJsGeometry(geometry1) as GeometryUnion, buildJsGeometry(geometry2) as GeometryUnion); } @@ -192,8 +181,7 @@ export default class GeometryEngineWrapper { transformation.flipY(y0, y1); let jsFlip = affineTransformOperator.execute(jsGeometry as any, transformation); - return this.returnToDotNet ? buildDotNetGeometry(jsFlip) : jsFlip; - + return buildDotNetGeometry(jsFlip); } async flipVertical(geometry: DotNetGeometry, flipOrigin: DotNetPoint | null): Promise { @@ -217,8 +205,7 @@ export default class GeometryEngineWrapper { transformation.flipX(x0, x1); let jsFlip = affineTransformOperator.execute(jsGeometry as any, transformation); - return this.returnToDotNet ? buildDotNetGeometry(jsFlip) : jsFlip; - + return buildDotNetGeometry(jsFlip); } async generalize(geometry: DotNetGeometry, maxDeviation: number, removeDegenerateParts: boolean | null, @@ -236,8 +223,7 @@ export default class GeometryEngineWrapper { jsGeneralize = generalizeOperator.execute(jsGeometry as GeometryUnion, maxDeviation, options) as GeometryUnion; - return this.returnToDotNet ? buildDotNetGeometry(jsGeneralize) : jsGeneralize; - + return buildDotNetGeometry(jsGeneralize); } async geodesicArea(geometry: DotNetPolygon, unit: AreaUnits | null): Promise { @@ -276,12 +262,12 @@ export default class GeometryEngineWrapper { jsGeometries = []; geometries.forEach(g => (jsGeometries as Array).push(buildJsGeometry(g) as GeometryUnion)); jsBuffer = geodesicBufferOperator.executeMany(jsGeometries as GeometryUnion[], distances as number[], options); - return this.returnToDotNet ? jsBuffer.map(g => buildDotNetPolygon(g) as DotNetPolygon) : jsBuffer; + return jsBuffer.map(g => buildDotNetPolygon(g) as DotNetPolygon); } jsGeometries = buildJsGeometry(geometries) as Geometry; jsBuffer = geodesicBufferOperator.execute(jsGeometries as GeometryUnion, distances as number, options) as Polygon; - return this.returnToDotNet ? buildDotNetPolygon(jsBuffer) : jsBuffer; + return buildDotNetPolygon(jsBuffer); } async geodesicDensify(geometry: DotNetGeometry, maxSegmentLength: number, @@ -297,7 +283,7 @@ export default class GeometryEngineWrapper { } jsDensify = geodeticDensifyOperator.execute(buildJsGeometry(geometry) as GeometryUnion, maxSegmentLength, options) as GeometryUnion; - return this.returnToDotNet ? buildDotNetGeometry(jsDensify) : jsDensify; + return buildDotNetGeometry(jsDensify); } @@ -322,12 +308,12 @@ export default class GeometryEngineWrapper { jsGeometries = []; geometry1.forEach(g => (jsGeometries as Array).push(buildJsGeometry(g) as GeometryUnion)); jsIntersection = intersectionOperator.executeMany(jsGeometries as GeometryUnion[], buildJsGeometry(geometry2) as GeometryUnion); - return this.returnToDotNet ? jsIntersection.map(g => buildDotNetGeometry(g) as DotNetGeometry) : jsIntersection; + return jsIntersection.map(g => buildDotNetGeometry(g) as DotNetGeometry); } jsGeometries = buildJsGeometry(geometry1) as GeometryUnion; jsIntersection = intersectionOperator.execute(jsGeometries, buildJsGeometry(geometry2) as GeometryUnion) as GeometryUnion; - return this.returnToDotNet ? buildDotNetGeometry(jsIntersection) : jsIntersection; + return buildDotNetGeometry(jsIntersection); } @@ -347,24 +333,24 @@ export default class GeometryEngineWrapper { : Promise { let proximityOperator = await import('@arcgis/core/geometry/operators/proximityOperator'); let jsResult = proximityOperator.getNearestCoordinate(buildJsGeometry(geometry) as any, buildJsPoint(inputPoint) as Point); - return this.returnToDotNet ? { + return { coordinate: buildJsPoint(jsResult.coordinate), distance: jsResult.distance, isEmpty: jsResult.isEmpty, vertexIndex: jsResult.vertexIndex, isRightSide: jsResult.isRightSide - } : jsResult; + }; } async nearestVertex(geometry: DotNetGeometry, inputPoint: DotNetPoint): Promise { let proximityOperator = await import('@arcgis/core/geometry/operators/proximityOperator'); let jsResult = proximityOperator.getNearestVertex(buildJsGeometry(geometry) as GeometryUnion, buildJsPoint(inputPoint) as Point); - return this.returnToDotNet ? { + return { coordinate: buildDotNetPoint(jsResult.coordinate) as DotNetPoint, distance: jsResult.distance, vertexIndex: jsResult.vertexIndex, isEmpty: jsResult.isEmpty - } : jsResult; + }; } @@ -374,14 +360,14 @@ export default class GeometryEngineWrapper { let jsResult = proximityOperator.getNearestVertices( buildJsGeometry(geometry) as GeometryUnion, buildJsPoint(inputPoint) as Point, searchRadius, maxVertexCountToReturn); - return this.returnToDotNet ? jsResult.filter(v => v !== undefined).map(r => { + return jsResult.filter(v => v !== undefined).map(r => { return { coordinate: buildDotNetPoint(r.coordinate) as DotNetPoint, distance: r.distance, vertexIndex: r.vertexIndex, isEmpty: r.isEmpty }; - }) : jsResult.filter(v => v !== undefined); + }); } @@ -404,12 +390,12 @@ export default class GeometryEngineWrapper { jsGeometries = []; geometries.forEach(g => (jsGeometries as Array).push(buildJsGeometry(g) as GeometryUnion)); let jsOffset = offsetOperator.executeMany(jsGeometries as GeometryUnion[], offsetDistance, options); - return this.returnToDotNet ? jsOffset.map(g => buildDotNetGeometry(g) as DotNetGeometry) : jsOffset; + return jsOffset.map(g => buildDotNetGeometry(g) as DotNetGeometry); } jsGeometries = buildJsGeometry(geometries as DotNetGeometry) as GeometryUnion; let jsOffset = offsetOperator.execute(jsGeometries as GeometryUnion, offsetDistance, options) as GeometryUnion; - return this.returnToDotNet ? buildDotNetGeometry(jsOffset) : jsOffset; + return buildDotNetGeometry(jsOffset); } async overlaps(geometry1: DotNetGeometry, geometry2: DotNetGeometry): Promise { @@ -449,14 +435,14 @@ export default class GeometryEngineWrapper { transformation.rotate(angle, jsRotationOrigin.x, jsRotationOrigin.y) let jsRotated = affineTransformOperator.execute(buildJsGeometry(geometry) as GeometryUnion, transformation); - return this.returnToDotNet ? buildDotNetGeometry(jsRotated) : jsRotated; + return buildDotNetGeometry(jsRotated); } async simplify(geometry: DotNetGeometry): Promise { let simplifyOperator = await import('@arcgis/core/geometry/operators/simplifyOperator'); let jsSimplified = simplifyOperator.execute(buildJsGeometry(geometry) as GeometryUnion); - return this.returnToDotNet ? buildDotNetGeometry(jsSimplified) : jsSimplified; + return buildDotNetGeometry(jsSimplified); } @@ -468,12 +454,12 @@ export default class GeometryEngineWrapper { jsGeometries = []; leftGeometry.forEach(g => (jsGeometries as Array).push(buildJsGeometry(g) as GeometryUnion)); let jsDifference = symmetricDifferenceOperator.executeMany(jsGeometries as GeometryUnion[], buildJsGeometry(rightGeometry) as GeometryUnion); - return this.returnToDotNet ? jsDifference.map(g => buildDotNetGeometry(g) as DotNetGeometry) : jsDifference; + return jsDifference.map(g => buildDotNetGeometry(g) as DotNetGeometry); } jsGeometries = buildJsGeometry(leftGeometry) as GeometryUnion; let jsDifference = symmetricDifferenceOperator.execute(jsGeometries as GeometryUnion, buildJsGeometry(rightGeometry) as GeometryUnion); - return this.returnToDotNet ? buildDotNetGeometry(jsDifference) : jsDifference; + return buildDotNetGeometry(jsDifference); } @@ -493,7 +479,7 @@ export default class GeometryEngineWrapper { } let jsUnion = unionOperator.executeMany(jsGeometries); - return this.returnToDotNet ? buildDotNetGeometry(jsUnion) : jsUnion; + return buildDotNetGeometry(jsUnion); } async within(innerGeometry: DotNetGeometry, outerGeometry: DotNetGeometry): Promise { @@ -502,7 +488,7 @@ export default class GeometryEngineWrapper { buildJsGeometry(outerGeometry) as GeometryUnion); } - async fromJSON(json: string, typeName: string): Promise { + fromArcGisJson(json: string, typeName: string): any { let jsGeometry: Geometry; let jsonObject = JSON.parse(json); switch (typeName) { @@ -527,158 +513,129 @@ export default class GeometryEngineWrapper { default: throw new Error("Invalid geometry type"); } - return this.returnToDotNet ? buildDotNetGeometry(jsGeometry) : jsGeometry; + return buildDotNetGeometry(jsGeometry); } - async toJSON(geometry: any): Promise { + toArcGisJson(geometry: any): string { let jsGeometry = buildJsGeometry(geometry) as Geometry; return JSON.stringify(jsGeometry.toJSON()); } - async clone(geometry: DotNetGeometry): Promise { + clone(geometry: DotNetGeometry): any { let jsGeometry = buildJsGeometry(geometry) as Geometry; let clonedGeometry = jsGeometry.clone(); - return this.returnToDotNet ? buildDotNetGeometry(clonedGeometry) : clonedGeometry; + return buildDotNetGeometry(clonedGeometry); } - async centerExtentAt(extent: DotNetExtent, center: DotNetPoint): Promise { + centerExtentAt(extent: DotNetExtent, center: DotNetPoint): any { let jsExtent = buildJsExtent(extent, null) as Extent; let newExtent = jsExtent.centerAt(buildJsPoint(center) as Point); - return this.returnToDotNet ? buildDotNetExtent(newExtent) : newExtent; + return buildDotNetExtent(newExtent); } - async expand(extent: DotNetExtent, factor: number): Promise { + expand(extent: DotNetExtent, factor: number): any { let jsExtent = buildJsExtent(extent, null) as Extent; let newExtent = jsExtent.expand(factor); - return this.returnToDotNet ? buildDotNetExtent(newExtent) : newExtent; + return buildDotNetExtent(newExtent); } - async normalizeExtent(extent: DotNetExtent): Promise { + normalizeExtent(extent: DotNetExtent): DotNetExtent[] { let jsExtent = buildJsExtent(extent, null) as Extent; let newExtents = jsExtent.normalize(); return newExtents.map(e => buildDotNetExtent(e) as DotNetExtent); } - async offsetExtent(extent: DotNetExtent, dx: number, dy: number, dz: number): Promise { + offsetExtent(extent: DotNetExtent, dx: number, dy: number, dz: number): DotNetExtent { let jsExtent = buildJsExtent(extent, null) as Extent; let newExtent = jsExtent.offset(dx, dy, dz); return buildDotNetExtent(newExtent); } - async normalizePoint(point: DotNetPoint): Promise { + normalizePoint(point: DotNetPoint): DotNetPoint { let jsPoint = buildJsPoint(point) as Point; let newPoint = jsPoint.normalize(); return buildDotNetPoint(newPoint); } - async addPath(polyline: DotNetPolyline, path: any): Promise { + addPath(polyline: DotNetPolyline, path: any): DotNetPolyline { let jsPolyline = buildJsPolyline(polyline) as Polyline; - let newPolyline = jsPolyline.addPath(path); + let jsPaths = buildJsPathsOrRings([path]); + let newPolyline = jsPolyline.addPath(jsPaths[0]); return buildDotNetPolyline(newPolyline); } - async getPointOnPolyline(polyline: DotNetPolyline, pathIndex: number, pointIndex: number) - : Promise { - let jsPolyline = buildJsPolyline(polyline) as Polyline; - let jsPoint = jsPolyline.getPoint(pathIndex, pointIndex); + getPoint(geometry: DotNetGeometry, firstIndex: number, secondIndex: number) : DotNetPoint | null { + let jsGeometry = buildJsGeometry(geometry) as Geometry; + let jsPoint = (jsGeometry as any).getPoint(firstIndex, secondIndex); return buildDotNetPoint(jsPoint); } - async insertPointOnPolyline(polyline: DotNetPolyline, pathIndex: number, pointIndex: number, point: DotNetPoint) - : Promise { - let jsPolyline = buildJsPolyline(polyline) as Polyline; + insertPoint(geometry: DotNetGeometry, firstIndex: number, secondIndex: number, point: DotNetPoint) + : DotNetPolyline | null { + let jsGeometry = buildJsGeometry(geometry) as Geometry; let jsPoint = buildJsPoint(point) as Point; - let newPolyline = jsPolyline.insertPoint(pathIndex, pointIndex, jsPoint); - return buildDotNetPolyline(newPolyline); + let newPolyline = (jsGeometry as any).insertPoint(firstIndex, secondIndex, jsPoint); + return buildDotNetGeometry(newPolyline); } - async removePath(polyline: DotNetPolyline, pathIndex: number): Promise { + removePath(polyline: DotNetPolyline, pathIndex: number): any | null { let jsPolyline = buildJsPolyline(polyline) as Polyline; let path = jsPolyline.removePath(pathIndex); let newLine = buildDotNetPolyline(jsPolyline) as DotNetPolyline; return { - polyLine: newLine, + geometry: newLine, path: path?.map(p => buildDotNetPoint(p) as DotNetPoint) } } - async removePointOnPolyline(polyline: DotNetPolyline, pathIndex: number, pointIndex: number): Promise { - let jsPolyline = buildJsPolyline(polyline) as Polyline; - let point = jsPolyline.removePoint(pathIndex, pointIndex); + removePoint(geometry: DotNetGeometry, firstIndex: number, secondIndex: number): any | null { + let jsGeometry = buildJsGeometry(geometry) as Geometry; + let point = (jsGeometry as any).removePoint(firstIndex, secondIndex); return { - polyLine: buildDotNetPolyline(jsPolyline) as DotNetPolyline, + geometry: buildDotNetGeometry(jsGeometry) as DotNetGeometry, point: buildDotNetPoint(point) as DotNetPoint }; } - async setPointOnPolyline(polyline: DotNetPolyline, pathIndex: number, pointIndex: number, point: DotNetPoint) - : Promise { - let jsPolyline = buildJsPolyline(polyline) as Polyline; + setPoint(geometry: DotNetGeometry, firstIndex: number, secondIndex: number, point: DotNetPoint) + : DotNetPolyline | null { + let jsGeometry = buildJsGeometry(geometry) as Geometry; let jsPoint = buildJsPoint(point) as Point; - let newPolyline = jsPolyline.setPoint(pathIndex, pointIndex, jsPoint); - return buildDotNetPolyline(newPolyline); + let newPolyline = (jsGeometry as any).setPoint(firstIndex, secondIndex, jsPoint); + return buildDotNetGeometry(newPolyline); } - async addRing(polygon: DotNetPolygon, ring: any): Promise { + addRing(polygon: DotNetPolygon, ring: any): DotNetPolygon { let jsPolygon = buildJsPolygon(polygon) as Polygon; - let newPolygon = jsPolygon.addRing(ring); + let jsRings = buildJsPathsOrRings([ring]); + let newPolygon = jsPolygon.addRing(jsRings[0]); return buildDotNetPolygon(newPolygon); } - async fromExtent(extent: DotNetExtent): Promise { + polygonFromExtent(extent: DotNetExtent): DotNetPolygon { let jsExtent = buildJsExtent(extent, null) as Extent; let jsPolygon = Polygon.fromExtent(jsExtent); return buildDotNetPolygon(jsPolygon); } - async getPointOnPolygon(polygon: DotNetPolygon, ringIndex: number, pointIndex: number): Promise { - let jsPolygon = buildJsPolygon(polygon) as Polygon; - let jsPoint = jsPolygon.getPoint(ringIndex, pointIndex); - return buildDotNetPoint(jsPoint); - } - async insertPointOnPolygon(polygon: DotNetPolygon, ringIndex: number, pointIndex: number, point: DotNetPoint) - : Promise { + isClockwise(polygon: DotNetPolygon, ring: any): boolean { let jsPolygon = buildJsPolygon(polygon) as Polygon; - let jsPoint = buildJsPoint(point) as Point; - let newPolygon = jsPolygon.insertPoint(ringIndex, pointIndex, jsPoint); - return buildDotNetPolygon(newPolygon); - } - - - async isClockwise(polygon: DotNetPolygon, ring: any): Promise { - let jsPolygon = buildJsPolygon(polygon) as Polygon; - return jsPolygon.isClockwise(ring); + let jsRings = buildJsPathsOrRings([ring]); + return jsPolygon.isClockwise(jsRings[0]); } - async removePointOnPolygon(polygon: DotNetPolygon, ringIndex: number, pointIndex: number): Promise { - let jsPolygon = buildJsPolygon(polygon) as Polygon; - let point = jsPolygon.removePoint(ringIndex, pointIndex); - return { - polygon: buildDotNetPolygon(jsPolygon) as DotNetPolygon, - point: buildDotNetPoint(point) as DotNetPoint - }; - } - - async removeRing(polygon: DotNetPolygon, index: number): Promise { + removeRing(polygon: DotNetPolygon, index: number): any { let jsPolygon = buildJsPolygon(polygon) as Polygon; let ring = jsPolygon.removeRing(index); return { - polygon: buildDotNetPolygon(jsPolygon) as DotNetPolygon, - ring: ring?.map(p => buildDotNetPoint(p) as DotNetPoint) + geometry: buildDotNetPolygon(jsPolygon) as DotNetPolygon, + path: ring?.map(p => buildDotNetPoint(p) as DotNetPoint) }; } - async setPointOnPolygon(polygon: DotNetPolygon, ringIndex: number, pointIndex: number, point: DotNetPoint) - : Promise { - let jsPolygon = buildJsPolygon(polygon) as Polygon; - let jsPoint = buildJsPoint(point) as Point; - let newPolygon = jsPolygon.setPoint(ringIndex, pointIndex, jsPoint); - return buildDotNetPolygon(newPolygon); - } - getExtentCenter(extent: DotNetExtent): DotNetPoint { let jsExtent = buildJsExtent(extent, null); return buildDotNetPoint(jsExtent.center) as DotNetPoint; @@ -742,12 +699,21 @@ export default class GeometryEngineWrapper { return pointResults; } + + async convertMultipartGeometriesToSinglePartGeometries(geometries: DotNetGeometry[], simplifyPolygons: boolean) + : Promise { + const jsGeometries = geometries.map(buildJsGeometry); + let multiPartToSinglePartOperator = await import("@arcgis/core/geometry/operators/multiPartToSinglePartOperator"); + const results = multiPartToSinglePartOperator.executeMany(jsGeometries, + {simplifyPolygons: simplifyPolygons}); + return results.map(buildDotNetGeometry); + } } export async function buildJsGeometryEngine(dotNetObject: any, viewId: string | null): Promise { - return new GeometryEngineWrapper(dotNetObject); + // excluded } export async function buildDotNetGeometryEngine(jsObject: any, viewId: string | null): Promise { - return null; // not used + // excluded } diff --git a/src/dymaptic.GeoBlazor.Core/Scripts/locationService.ts b/src/dymaptic.GeoBlazor.Core/Scripts/locationService.ts index 260b704a3..e0d6582dc 100644 --- a/src/dymaptic.GeoBlazor.Core/Scripts/locationService.ts +++ b/src/dymaptic.GeoBlazor.Core/Scripts/locationService.ts @@ -11,9 +11,10 @@ import locatorLocationToAddressParams = __esri.locatorLocationToAddressParams; import locatorAddressToLocationsParams = __esri.locatorAddressToLocationsParams; import locatorAddressesToLocationsParams = __esri.locatorAddressesToLocationsParams; import SuggestionResult = __esri.SuggestionResult; +import BaseComponent from "./baseComponent"; + +export default class LocatorWrapper extends BaseComponent { -export default class LocatorWrapper { - async addressesToLocations(url: string, addresses: any, countryCode: string | null, categories: string[] | null, locationType: string | null, outSpatialReference: DotNetSpatialReference | null, @@ -65,11 +66,7 @@ export default class LocatorWrapper { } let {buildDotNetAddressCandidate} = await import('./addressCandidate'); - let dotNetResult = result.map(r => buildDotNetAddressCandidate(r)); - - let json = JSON.stringify(dotNetResult); - let encoded = new TextEncoder().encode(json); - return encoded; + return result.map(r => buildDotNetAddressCandidate(r)); } async addressToLocations(url: string, address: any, categories: string[] | null, countryCode: string | null, @@ -133,11 +130,7 @@ export default class LocatorWrapper { } let {buildDotNetAddressCandidate} = await import('./addressCandidate'); - let dotNetResult = result.map(r => buildDotNetAddressCandidate(r)); - - let json = JSON.stringify(dotNetResult); - let encoded = new TextEncoder().encode(json); - return encoded; + return result.map(r => buildDotNetAddressCandidate(r)); } async locationToAddress(url: string, location: DotNetPoint, locationType: string | null, diff --git a/src/dymaptic.GeoBlazor.Core/Scripts/projection.ts b/src/dymaptic.GeoBlazor.Core/Scripts/projectionEngine.ts similarity index 88% rename from src/dymaptic.GeoBlazor.Core/Scripts/projection.ts rename to src/dymaptic.GeoBlazor.Core/Scripts/projectionEngine.ts index ae5e99a9b..7e2576337 100644 --- a/src/dymaptic.GeoBlazor.Core/Scripts/projection.ts +++ b/src/dymaptic.GeoBlazor.Core/Scripts/projectionEngine.ts @@ -4,14 +4,9 @@ import {buildDotNetGeometry, buildJsGeometry} from "./geometry"; import {buildJsSpatialReference} from "./spatialReference"; import {hasValue} from './geoBlazorCore'; import {buildJsExtent} from "./extent"; +import BaseComponent from "./baseComponent"; -export default class ProjectionWrapper { - private readonly returnToDotNet: boolean; - - constructor(returnToDotNet: boolean = true) { - this.returnToDotNet = returnToDotNet; - } - +export default class ProjectionWrapper extends BaseComponent { async load(): Promise { let projectionOperator = await import('@arcgis/core/geometry/operators/projectOperator'); if (!projectionOperator.isLoaded()) { @@ -35,9 +30,7 @@ export default class ProjectionWrapper { let jsGeometries = geometry.map(g => buildJsGeometry(g)); let result = projectionOperator.executeMany(jsGeometries, buildJsSpatialReference(outSpatialReference) as any, options); - if (!this.returnToDotNet) { - return result; - } + let resultArray: DotNetGeometry[] = []; (result as Geometry[]).forEach(g => { let dotNetGeom = buildDotNetGeometry(g); @@ -53,7 +46,7 @@ export default class ProjectionWrapper { let result = projectionOperator.execute(jsGeometry, buildJsSpatialReference(outSpatialReference) as any, options); - return this.returnToDotNet ? buildDotNetGeometry(result) : result; + return buildDotNetGeometry(result); } async getTransformation(inSpatialReference, outSpatialReference, extent): @@ -73,7 +66,7 @@ export default class ProjectionWrapper { buildJsSpatialReference(outSpatialReference) as any) } let {buildDotNetGeographicTransformation} = await import('./geographicTransformation'); - return this.returnToDotNet ? buildDotNetGeographicTransformation(geoTransform) : geoTransform; + return buildDotNetGeographicTransformation(geoTransform); } async getTransformations(inSpatialReference, outSpatialReference, extent): @@ -100,14 +93,14 @@ export default class ProjectionWrapper { dotNetTransforms.push(dotNetT); } }); - return this.returnToDotNet ? dotNetTransforms : geoTransforms; + return dotNetTransforms; } } export async function buildJsProjection(dotNetObject: any, layerId: string | null, viewId: string | null): Promise { - return new ProjectionWrapper(); + // not used } export async function buildDotNetProjection(jsObject: any, layerId: string | null, viewId: string | null): Promise { - return null; + // not used } diff --git a/test/dymaptic.GeoBlazor.Core.Test.Blazor.Shared/Components/GeometryEngineTests.cs b/test/dymaptic.GeoBlazor.Core.Test.Blazor.Shared/Components/GeometryEngineTests.cs index 12f16247a..f0fda16a7 100644 --- a/test/dymaptic.GeoBlazor.Core.Test.Blazor.Shared/Components/GeometryEngineTests.cs +++ b/test/dymaptic.GeoBlazor.Core.Test.Blazor.Shared/Components/GeometryEngineTests.cs @@ -1456,5 +1456,166 @@ public async Task TestWithinFalse() Assert.IsFalse(within); } + [TestMethod] + public async Task TestToAndFromArcGisJson() + { + Polygon polygon1 = + new Polygon([ + [ + new MapPoint(0, 0), + new MapPoint(0, 10), + new MapPoint(10, 10), + new MapPoint(10, 0), + new MapPoint(0, 0) + ] + ], new SpatialReference(102100)); + + string json = await GeometryEngine.ToArcGisJson(polygon1); + Geometry fromJson = await GeometryEngine.FromArcGisJson(json); + Assert.IsNotNull(fromJson); + Assert.AreEqual(polygon1.Type, fromJson.Type); + Assert.AreEqual(polygon1.Rings[0], ((Polygon)fromJson).Rings[0]); + } + + [TestMethod] + public async Task TestClone_ToFromJson() + { + Polygon polygon = new Polygon([new MapPath(new MapPoint(0, 0), new MapPoint(0, 1), new MapPoint(1, 1), new MapPoint(1, 0), new MapPoint(0, 0)) + ], new SpatialReference(102100)); + + // Clone + Polygon clone = await GeometryEngine.Clone(polygon); + Assert.IsNotNull(clone); + Assert.AreEqual(polygon.Type, clone.Type); + + // To/From ArcGIS JSON + string json = await GeometryEngine.ToArcGisJson(polygon); + Assert.IsFalse(string.IsNullOrWhiteSpace(json)); + + Polygon from = await GeometryEngine.FromArcGisJson(json); + Assert.IsNotNull(from); + Assert.AreEqual(polygon.Type, from.Type); + } + + [TestMethod] + public async Task TestExtentHelpers_And_NormalizePoint() + { + Extent extent = new Extent(10, 0, 10, 0); // xmax, xmin, ymax, ymin (constructor used in codebase) + Point center = new Point(2, 3); + + Extent centered = await GeometryEngine.CenterExtentAt(extent, center); + Assert.IsNotNull(centered); + + Extent expanded = await GeometryEngine.Expand(extent, 2.0); + Assert.IsNotNull(expanded); + + Extent[] normalizedExtents = await GeometryEngine.NormalizeExtent(new Extent(200, -200, 10, 0)); + Assert.IsNotNull(normalizedExtents); + Assert.IsGreaterThanOrEqualTo(1, normalizedExtents.Length); + + Extent offsetExtent = await GeometryEngine.OffsetExtent(extent, 1, 2, 3); + Assert.IsNotNull(offsetExtent); + + Point p = new Point(190, 10); + Point normalizedPoint = await GeometryEngine.NormalizePoint(p); + Assert.IsNotNull(normalizedPoint); + Assert.IsTrue(normalizedPoint.X is <= 180 and >= -180); + + // get extent properties + Point? maybeCenter = await GeometryEngine.GetExtentCenter(extent); + double? maybeHeight = await GeometryEngine.GetExtentHeight(extent); + double? maybeWidth = await GeometryEngine.GetExtentWidth(extent); + + Assert.IsNotNull(maybeCenter); + Assert.IsNotNull(maybeHeight); + Assert.IsNotNull(maybeWidth); + } + + [TestMethod] + public async Task TestPolyline_PathAndPoint_Mutations() + { + Polyline polyline = new Polyline([[[0,0],[1,1]]], new SpatialReference(102100)); + + // AddPath using Point[] overload + Polyline added = await GeometryEngine.AddPath(polyline, [new Point(1, 2), new Point(3, 3)]); + Assert.IsNotNull(added); + Assert.IsGreaterThanOrEqualTo(2, added.Paths.Count); + + // GetPoint for newly added path + Point pt = await GeometryEngine.GetPoint(added, 1, 0); + Assert.AreEqual(1, pt.X); + Assert.AreEqual(2, pt.Y); + + // InsertPoint + Polyline inserted = await GeometryEngine.InsertPoint(added, 1, 1, new Point(2.5, 2.5)); + Point got = await GeometryEngine.GetPoint(inserted, 1, 1); + Assert.AreEqual(2.5, got.X); + + // SetPoint + Polyline set = await GeometryEngine.SetPoint(inserted, 1, 1, new Point(5, 5)); + Point gotSet = await GeometryEngine.GetPoint(set, 1, 1); + Assert.AreEqual(5, gotSet.X); + + // RemovePoint + (Polyline Polyline, Point Point) removeResult = await GeometryEngine.RemovePoint(set, 1, 1); +#pragma warning disable MSTEST0032 + Assert.IsNotNull(removeResult); + Assert.IsInstanceOfType(removeResult.Point, typeof(Point)); + + // RemovePath + (Polyline PolyLine, Point[] Path) removePathResult = await GeometryEngine.RemovePath(removeResult.Polyline, 1); + Assert.IsNotNull(removePathResult); + Assert.IsGreaterThanOrEqualTo(1, removePathResult.Path.Length); + } + + [TestMethod] + public async Task TestPolygon_RingAndPoint_Mutations() + { + Polygon polygon = new Polygon([new MapPath(new MapPoint(0, 0), new MapPoint(0, 5), new MapPoint(5, 5), new MapPoint(5, 0), new MapPoint(0, 0)) + ], new SpatialReference(102100)); + int beforeRings = polygon.Rings.Count; + + // AddRing with Point[] + Polygon withRing = await GeometryEngine.AddRing(polygon, [new Point(10, 10), new Point(10, 20), new Point(20, 20), new Point(10, 10) + ]); + Assert.IsNotNull(withRing); + Assert.HasCount(beforeRings + 1, withRing.Rings); + + // GetPoint from newly added ring + Point ringPoint = await GeometryEngine.GetPoint(withRing, beforeRings, 0); + Assert.AreEqual(10, ringPoint.X); + + // InsertPoint + Polygon inserted = await GeometryEngine.InsertPoint(withRing, beforeRings, 1, new Point(11, 11)); + Point got = await GeometryEngine.GetPoint(inserted, beforeRings, 1); + Assert.AreEqual(11, got.X); + + // SetPoint + Polygon set = await GeometryEngine.SetPoint(inserted, beforeRings, 1, new Point(12, 12)); + Point gotSet = await GeometryEngine.GetPoint(set, beforeRings, 1); + Assert.AreEqual(12, gotSet.X); + + // RemovePoint + (Polygon Polygon, Point Point) removed = await GeometryEngine.RemovePoint(set, beforeRings, 1); + Assert.IsNotNull(removed); + Assert.IsInstanceOfType(removed.Point, typeof(Point)); + + // RemoveRing + (Polygon Polygon, Point[] Ring) removedRing = await GeometryEngine.RemoveRing(removed.Polygon, beforeRings); + Assert.IsNotNull(removedRing); +#pragma warning restore MSTEST0032 + Assert.HasCount(beforeRings, removedRing.Polygon.Rings); + } + + [TestMethod] + public async Task TestIsClockwise() + { + MapPath ring = new MapPath(new MapPoint(0, 0), new MapPoint(0, 10), new MapPoint(10, 10), new MapPoint(10, 0), new MapPoint(0, 0)); + Polygon polygon = new Polygon([ring], new SpatialReference(102100)); + + bool result = await GeometryEngine.IsClockwise(polygon, ring); + Assert.IsInstanceOfType(result, typeof(bool)); + } + private readonly Random _random = new(); } \ No newline at end of file From 526d0f8dcbe4da7b9ba4382447f22ec37de0a410 Mon Sep 17 00:00:00 2001 From: Tim Purdum Date: Tue, 16 Dec 2025 09:50:46 -0600 Subject: [PATCH 02/89] missing geometry updates --- .../Scripts/polygon.ts | 19 ++++--- .../Scripts/polyline.ts | 50 +++++++++++++++---- 2 files changed, 50 insertions(+), 19 deletions(-) diff --git a/src/dymaptic.GeoBlazor.Core/Scripts/polygon.ts b/src/dymaptic.GeoBlazor.Core/Scripts/polygon.ts index 307929ba0..865fb82b8 100644 --- a/src/dymaptic.GeoBlazor.Core/Scripts/polygon.ts +++ b/src/dymaptic.GeoBlazor.Core/Scripts/polygon.ts @@ -1,7 +1,7 @@ import {buildDotNetExtent} from "./extent"; import {buildDotNetSpatialReference, buildJsSpatialReference} from "./spatialReference"; import Polygon from "@arcgis/core/geometry/Polygon"; -import {arcGisObjectRefs, hasValue, jsObjectRefs} from './geoBlazorCore'; +import {arcGisObjectRefs, copyValuesIfExists, hasValue, jsObjectRefs} from './geoBlazorCore'; import Circle from "@arcgis/core/geometry/Circle"; import {buildDotNetPoint, buildJsPoint} from "./point"; import * as simplifyOperator from '@arcgis/core/geometry/operators/simplifyOperator'; @@ -49,7 +49,12 @@ export function buildDotNetPolygon(polygon: any): any { dnPolygon.geodesic = polygon.geodesic; } - dnPolygon.isSimple = simplifyOperator.isSimple(polygon); + try { + dnPolygon.isSimple = simplifyOperator.isSimple(polygon); + } catch (e) { + // invalid token + console.error(e); + } if (hasValue(polygon.isSelfIntersecting)) { dnPolygon.isSelfIntersecting = polygon.isSelfIntersecting; @@ -70,18 +75,16 @@ export function buildJsPolygon(dnPolygon: any): any { if (dnPolygon === undefined || dnPolygon === null) return null; let properties : any = {}; + if (hasValue(dnPolygon.rings)) { properties.rings = buildJsPathsOrRings(dnPolygon.rings); } + if (hasValue(dnPolygon.spatialReference)) { properties.spatialReference = buildJsSpatialReference(dnPolygon.spatialReference); } - if (hasValue(dnPolygon.hasM)) { - properties.hasM = dnPolygon.hasM; - } - if (hasValue(dnPolygon.hasZ)) { - properties.hasZ = dnPolygon.hasZ; - } + + copyValuesIfExists(dnPolygon, properties, 'hasM', 'hasZ'); let polygon: Polygon; if (hasValue(dnPolygon.center) && hasValue(dnPolygon.radius)) { diff --git a/src/dymaptic.GeoBlazor.Core/Scripts/polyline.ts b/src/dymaptic.GeoBlazor.Core/Scripts/polyline.ts index 95e804878..d4058ba7e 100644 --- a/src/dymaptic.GeoBlazor.Core/Scripts/polyline.ts +++ b/src/dymaptic.GeoBlazor.Core/Scripts/polyline.ts @@ -1,37 +1,45 @@ import {buildDotNetExtent} from "./extent"; import {buildDotNetSpatialReference, buildJsSpatialReference} from "./spatialReference"; import Polyline from "@arcgis/core/geometry/Polyline"; -import {arcGisObjectRefs, hasValue, jsObjectRefs} from './geoBlazorCore'; +import {arcGisObjectRefs, copyValuesIfExists, hasValue, jsObjectRefs} from './geoBlazorCore'; import * as simplifyOperator from '@arcgis/core/geometry/operators/simplifyOperator'; export function buildDotNetPolyline(polyline: any): any { - return { + let dnPolyline: any = { type: 'polyline', paths: polyline.paths, hasM: polyline.hasM, hasZ: polyline.hasZ, extent: buildDotNetExtent(polyline.extent), - spatialReference: buildDotNetSpatialReference(polyline.spatialReference), - isSimple: simplifyOperator.isSimple(polyline) + spatialReference: buildDotNetSpatialReference(polyline.spatialReference) }; + + try { + dnPolyline.isSimple = simplifyOperator.isSimple(polyline); + } catch (e) { + // invalid token + console.error(e); + } + + return dnPolyline; } export function buildJsPolyline(dnPolyline: any): any { if (dnPolyline === undefined || dnPolyline === null) return null; let properties: any = {}; + if (hasValue(dnPolyline.paths)) { properties.paths = buildJsPathsOrRings(dnPolyline.paths); } - if (hasValue(dnPolyline.hasZ)) { - properties.hasZ = dnPolyline.hasZ; - } - if (hasValue(dnPolyline.hasM)) { - properties.hasM = dnPolyline.hasM; - } + if (hasValue(dnPolyline.spatialReference)) { properties.spatialReference = buildJsSpatialReference(dnPolyline.spatialReference); } + + copyValuesIfExists(dnPolyline, properties, 'hasM', 'hasZ') + let polyline = new Polyline(properties); + let jsObjectRef = DotNet.createJSObjectReference(polyline); jsObjectRefs[dnPolyline.id] = jsObjectRef; arcGisObjectRefs[dnPolyline.id] = polyline; @@ -39,7 +47,7 @@ export function buildJsPolyline(dnPolyline: any): any { return polyline; } -function buildJsPathsOrRings(pathsOrRings: any) { +export function buildJsPathsOrRings(pathsOrRings: any) { if (!hasValue(pathsOrRings)) return null; if (pathsOrRings[0].hasOwnProperty("points")) { let array: [][][] = []; @@ -55,3 +63,23 @@ function buildJsPathsOrRings(pathsOrRings: any) { } return pathsOrRings; } + +export function buildProtobufPathsOrRings(pathsOrRings: any) { + if (!hasValue(pathsOrRings)) return null; + let array: any = []; + for (let i = 0; i < pathsOrRings.length; i++) { + let points = pathsOrRings[i]; + let ring : any = { + points: [] + } + + for (let j = 0; j < points.length; j++) { + ring.points.push({ + coordinates: points[j] + }) + } + array.push(ring); + } + + return array; +} From dd1ca1c655d6ccc629ac725be7651e7d4dcdde01 Mon Sep 17 00:00:00 2001 From: Tim Purdum Date: Tue, 16 Dec 2025 10:28:59 -0600 Subject: [PATCH 03/89] finished testing --- .../JsModuleManager.cs | 23 +++++++++++++------ src/dymaptic.GeoBlazor.Core/esbuild.js | 7 +++++- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/src/dymaptic.GeoBlazor.Core/JsModuleManager.cs b/src/dymaptic.GeoBlazor.Core/JsModuleManager.cs index 0a756d78e..bd750b72e 100644 --- a/src/dymaptic.GeoBlazor.Core/JsModuleManager.cs +++ b/src/dymaptic.GeoBlazor.Core/JsModuleManager.cs @@ -56,19 +56,28 @@ public async ValueTask GetCoreJsModule(IJSRuntime jsRuntime, /// Retrieves or creates a JavaScript wrapper for a logic component. /// /// The JS runtime to use for module loading. - /// The name of the logic component (e.g., "geometryEngine"). + /// The name of the logic component (e.g., "geometryEngine"). /// A cancellation token. /// A JavaScript object reference to the component wrapper. - public async ValueTask GetLogicComponent(IJSRuntime jsRuntime, string componentName, + public async ValueTask GetLogicComponent(IJSRuntime jsRuntime, string moduleName, CancellationToken cancellationToken) { - IJSObjectReference? proModule = await GetProJsModule(jsRuntime, cancellationToken); - IJSObjectReference coreModule = await GetCoreJsModule(jsRuntime, proModule, cancellationToken); + if (!_proChecked) + { + await GetProJsModule(jsRuntime, cancellationToken); + } - return await coreModule.InvokeAsync($"get{componentName.ToUpperFirstChar()}Wrapper", - cancellationToken); - } + if (_coreModule is null) + { + await GetCoreJsModule(jsRuntime, _proModule, cancellationToken); + } + string modulePath = $"./_content/dymaptic.GeoBlazor.{(_proModule is null ? "Core" : "Pro")}/js/{moduleName}.js?v={_version}"; + IJSObjectReference module = await jsRuntime.InvokeAsync("import", cancellationToken, modulePath); + // load the default export class from the module + return await _coreModule!.InvokeAsync("getDefaultClassInstanceFromModule", cancellationToken, module); + } + private IJSObjectReference? _proModule; private IJSObjectReference? _coreModule; private bool _proChecked; diff --git a/src/dymaptic.GeoBlazor.Core/esbuild.js b/src/dymaptic.GeoBlazor.Core/esbuild.js index 4b51d11cc..fc9b9f51e 100644 --- a/src/dymaptic.GeoBlazor.Core/esbuild.js +++ b/src/dymaptic.GeoBlazor.Core/esbuild.js @@ -11,7 +11,12 @@ const isRelease = args.includes('--release'); const OUTPUT_DIR = path.resolve('./wwwroot/js'); let options = { - entryPoints: ['./Scripts/geoBlazorCore.ts'], + entryPoints: [ + './Scripts/geoBlazorCore.ts', // main entry point + './Scripts/geometryEngine.ts', // logic components + './Scripts/locationService.ts', + './Scripts/projectionEngine.ts' + ], chunkNames: 'core_[name]_[hash]', bundle: true, sourcemap: true, From 3f9b5320cbd77f1be9574625934ee870252222c3 Mon Sep 17 00:00:00 2001 From: timpurdum Date: Fri, 16 Jan 2026 17:09:06 -0600 Subject: [PATCH 04/89] adding mac-specific needs --- Directory.Build.props | 2 +- src/dymaptic.GeoBlazor.Core/esBuild.ps1 | 48 +++++++++++++------ .../TestConfig.cs | 2 +- 3 files changed, 36 insertions(+), 16 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index 6a3345c08..ba504cfed 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,6 +1,6 @@ - + enable enable diff --git a/src/dymaptic.GeoBlazor.Core/esBuild.ps1 b/src/dymaptic.GeoBlazor.Core/esBuild.ps1 index 03d5d366a..ceab76d5e 100644 --- a/src/dymaptic.GeoBlazor.Core/esBuild.ps1 +++ b/src/dymaptic.GeoBlazor.Core/esBuild.ps1 @@ -2,6 +2,9 @@ param([string][Alias("c")]$Configuration = "Debug", [switch][Alias("f")]$Force, [switch][Alias("h")]$Help) +# Allow $IsLinux and $IsMacOS to be accessed safely in Windows PowerShell +Set-StrictMode -Off + if ($Help) { Write-Host "ESBuild TypeScript -> JavaScript Compilation Script" Write-Host "" @@ -103,15 +106,32 @@ if (-not $needsBuild) { exit 0 } +function WriteToDialog { + param([System.Diagnostics.Process]$DialogProcess, + [string]$Content) + if ($DialogProcess.StandardInput -eq $null) { + return; + } + + $DialogProcess.StandardInput.WriteLine($Content); +} + # Start dialog process only if we're actually going to build $ShowDialogPath = Join-Path -Path $PSScriptRoot ".." ".." "showDialog.ps1" -$DialogArgs = "-Message `"Starting GeoBlazor Core ESBuild process...`" -Title `"GeoBlazor Core ESBuild`" -Buttons None -ListenForInput" +$DialogArgs = '-Message "Starting GeoBlazor Core ESBuild process..." -Title "GeoBlazor Core ESBuild" -ListenForInput' $DialogStartInfo = New-Object System.Diagnostics.ProcessStartInfo $DialogStartInfo.FileName = "pwsh" -$DialogStartInfo.Arguments = "-NoProfile -ExecutionPolicy ByPass -File `"$ShowDialogPath`" $DialogArgs" -$DialogStartInfo.RedirectStandardInput = $true $DialogStartInfo.UseShellExecute = $false -$DialogStartInfo.CreateNoWindow = $true + +if ($IsMacOS) { + $DialogStartInfo.Arguments = "`"$ShowDialogPath`" $DialogArgs" +} elseif($IsLinux) { + $DialogStartInfo.Arguments = "`"$ShowDialogPath`" $DialogArgs" +} else { + $DialogStartInfo.Arguments = "-NoProfile -ExecutionPolicy ByPass -File `"$ShowDialogPath`" $DialogArgs" + $DialogStartInfo.RedirectStandardInput = $true +} + $DialogProcess = [System.Diagnostics.Process]::Start($DialogStartInfo) # Check if the process is locked for the current configuration @@ -163,16 +183,16 @@ try Write-Output $Install foreach ($line in $Install) { - $DialogProcess.StandardInput.WriteLine($line) + WriteToDialog -DialogProcess $DialogProcess -Content $line } $HasError = ($Install -like "*Error*") $HasWarning = ($Install -like "*Warning*") Write-Output "-----" - $DialogProcess.StandardInput.WriteLine("-----") + WriteToDialog -DialogProcess $DialogProcess -Content "-----" if ($HasError -ne $null -or $HasWarning -ne $null) { Write-Output "NPM Install failed" - $DialogProcess.StandardInput.WriteLine("NPM Install failed") + WriteToDialog -DialogProcess $DialogProcess -Content "NPM Install failed" exit 1 } @@ -182,12 +202,12 @@ try Write-Output $Build foreach ($line in $Build) { - $DialogProcess.StandardInput.WriteLine($line) + WriteToDialog -DialogProcess $DialogProcess -Content $line } $HasError = ($Build -like "*Error*") $HasWarning = ($Build -like "*Warning*") Write-Output "-----" - $DialogProcess.StandardInput.WriteLine("-----") + WriteToDialog -DialogProcess $DialogProcess -Content "-----" if ($HasError -ne $null -or $HasWarning -ne $null) { exit 1 @@ -199,19 +219,19 @@ try Write-Output $Build foreach ($line in $Build) { - $DialogProcess.StandardInput.WriteLine($line) + WriteToDialog -DialogProcess $DialogProcess -Content $line } $HasError = ($Build -like "*Error*") $HasWarning = ($Build -like "*Warning*") Write-Output "-----" - $DialogProcess.StandardInput.WriteLine("-----") + WriteToDialog -DialogProcess $DialogProcess -Content "-----" if ($HasError -ne $null -or $HasWarning -ne $null) { exit 1 } } Write-Output "NPM Build Complete" - $DialogProcess.StandardInput.WriteLine("NPM Build Complete") + WriteToDialog -DialogProcess $DialogProcess -Content "NPM Build Complete" Start-Sleep -Seconds 4 $DialogProcess.Kill() exit 0 @@ -219,9 +239,9 @@ try catch { Write-Output "An error occurred in esBuild.ps1" - $DialogProcess.StandardInput.WriteLine("An error occurred in esBuild.ps1") + WriteToDialog -DialogProcess $DialogProcess -Content "An error occurred in esBuild.ps1" Write-Output $_ - $DialogProcess.StandardInput.WriteLine($_) + WriteToDialog -DialogProcess $DialogProcess -Content $_ exit 1 } finally diff --git a/test/dymaptic.GeoBlazor.Core.Test.Automation/TestConfig.cs b/test/dymaptic.GeoBlazor.Core.Test.Automation/TestConfig.cs index 3233ed911..d36ee6325 100644 --- a/test/dymaptic.GeoBlazor.Core.Test.Automation/TestConfig.cs +++ b/test/dymaptic.GeoBlazor.Core.Test.Automation/TestConfig.cs @@ -334,7 +334,7 @@ private static async Task StartTestApp() string[] args = [ - "run", "--project", $"\"{TestAppPath}\"", + "run", "--project", TestAppPath, "--urls", $"{TestAppUrl};{TestAppHttpUrl}", "--", "-c", "Release", "/p:GenerateXmlComments=false", "/p:GeneratePackage=false", From f96e928165944227a4e19ccd2110eff12e4a4fa4 Mon Sep 17 00:00:00 2001 From: timpurdum Date: Mon, 19 Jan 2026 09:32:57 -0600 Subject: [PATCH 05/89] mac updates --- .../TestConfig.cs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/test/dymaptic.GeoBlazor.Core.Test.Automation/TestConfig.cs b/test/dymaptic.GeoBlazor.Core.Test.Automation/TestConfig.cs index 94d9aa22b..0cf6aa095 100644 --- a/test/dymaptic.GeoBlazor.Core.Test.Automation/TestConfig.cs +++ b/test/dymaptic.GeoBlazor.Core.Test.Automation/TestConfig.cs @@ -680,13 +680,25 @@ await Cli.Wrap("pwsh") .WithArguments($"Get-NetTCPConnection -LocalPort {_httpsPort } -State Listen | Select-Object -ExpandProperty OwningProcess | ForEach-Object {{ Stop-Process -Id $_ -Force }}") .WithValidation(CommandResultValidation.None) + .WithStandardOutputPipe(PipeTarget.ToDelegate(line => + Trace.WriteLine(line, "TEST_PORT_SHUTDOWN"))) + .WithStandardErrorPipe(PipeTarget.ToDelegate(line => + Trace.WriteLine(line, "TEST_PORT_SHUTDOWN_ERROR"))) .ExecuteAsync(); } else { await Cli.Wrap("/bin/bash") - .WithArguments($"lsof -i:{_httpsPort} | awk '{{if(NR>1)print $2}}' | xargs -t -r kill -9") + .WithArguments( + [ + "-c", + $"lsof -i:{_httpsPort} | awk '{{if(NR>1)print $2}}' | xargs -t -r kill -9" + ]) .WithValidation(CommandResultValidation.None) + .WithStandardOutputPipe(PipeTarget.ToDelegate(line => + Trace.WriteLine(line, "TEST_PORT_SHUTDOWN"))) + .WithStandardErrorPipe(PipeTarget.ToDelegate(line => + Trace.WriteLine(line, "TEST_PORT_SHUTDOWN_ERROR"))) .ExecuteAsync(); } } From 472cfbb1ecd0b150351ca0da90994e6409626252 Mon Sep 17 00:00:00 2001 From: Tim Purdum Date: Thu, 22 Jan 2026 12:51:26 -0600 Subject: [PATCH 06/89] Logic component implementation and adding cancellation token support. First step --- Dockerfile | 9 +- .../SerializationGenerator.cs | 3 +- .../Components/GeometryEngine.cs | 1102 ++++++++++++----- .../Components/LocationService.cs | 674 ++++++---- .../Model/ProjectionEngine.cs | 45 +- .../Scripts/geometryEngine.ts | 40 +- 6 files changed, 1255 insertions(+), 618 deletions(-) diff --git a/Dockerfile b/Dockerfile index 832b91c9a..17a29e3eb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,3 +1,4 @@ +# syntax=docker/dockerfile:1.4 FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build ARG ARCGIS_API_KEY ARG GEOBLAZOR_LICENSE_KEY @@ -19,7 +20,7 @@ RUN apt-get update \ WORKDIR /work WORKDIR /work/src/dymaptic.GeoBlazor.Core COPY ./src/dymaptic.GeoBlazor.Core/package.json ./package.json -RUN npm install +RUN --mount=type=cache,target=/root/.npm npm install WORKDIR /work COPY ./src/ ./src/ @@ -37,7 +38,8 @@ COPY ./test/dymaptic.GeoBlazor.Core.Test.WebApp/dymaptic.GeoBlazor.Core.Test.Web COPY ./test/dymaptic.GeoBlazor.Core.Test.WebApp/dymaptic.GeoBlazor.Core.Test.WebApp.Client/dymaptic.GeoBlazor.Core.Test.WebApp.Client.csproj ./test/dymaptic.GeoBlazor.Core.Test.WebApp/dymaptic.GeoBlazor.Core.Test.WebApp.Client/dymaptic.GeoBlazor.Core.Test.WebApp.Client.csproj # Use UsePackageReference=false to build from source (enables code coverage with PDB symbols) -RUN dotnet restore ./test/dymaptic.GeoBlazor.Core.Test.WebApp/dymaptic.GeoBlazor.Core.Test.WebApp/dymaptic.GeoBlazor.Core.Test.WebApp.csproj /p:UsePackageReference=false +RUN --mount=type=cache,target=/root/.nuget/packages \ + dotnet restore ./test/dymaptic.GeoBlazor.Core.Test.WebApp/dymaptic.GeoBlazor.Core.Test.WebApp/dymaptic.GeoBlazor.Core.Test.WebApp.csproj /p:UsePackageReference=false COPY ./test/dymaptic.GeoBlazor.Core.Test.Blazor.Shared ./test/dymaptic.GeoBlazor.Core.Test.Blazor.Shared COPY ./test/dymaptic.GeoBlazor.Core.Test.WebApp ./test/dymaptic.GeoBlazor.Core.Test.WebApp @@ -54,7 +56,8 @@ RUN dotnet ./build-tools/BuildAppSettings.dll \ # Build from source with debug symbols for code coverage # UsePackageReference=false builds GeoBlazor from source instead of NuGet # DebugSymbols=true and DebugType=portable ensure PDB files are generated -RUN dotnet publish ./test/dymaptic.GeoBlazor.Core.Test.WebApp/dymaptic.GeoBlazor.Core.Test.WebApp/dymaptic.GeoBlazor.Core.Test.WebApp.csproj \ +RUN --mount=type=cache,target=/root/.nuget/packages \ + dotnet publish ./test/dymaptic.GeoBlazor.Core.Test.WebApp/dymaptic.GeoBlazor.Core.Test.WebApp/dymaptic.GeoBlazor.Core.Test.WebApp.csproj \ -c Release \ /p:UsePackageReference=false \ /p:PipelineBuild=true \ diff --git a/src/dymaptic.GeoBlazor.Core.SourceGenerator.Shared/SerializationGenerator.cs b/src/dymaptic.GeoBlazor.Core.SourceGenerator.Shared/SerializationGenerator.cs index 27d92c4d0..cdd1c77fd 100644 --- a/src/dymaptic.GeoBlazor.Core.SourceGenerator.Shared/SerializationGenerator.cs +++ b/src/dymaptic.GeoBlazor.Core.SourceGenerator.Shared/SerializationGenerator.cs @@ -390,7 +390,8 @@ private static string GenerateSerializableMethodRecords( """); } - foreach (var classGroup in serializedMethodsCollection.GroupBy(m => m.ClassName)) + foreach (var classGroup in serializedMethodsCollection + .GroupBy(m => m.ClassName)) { outputBuilder.AppendLine($$""" ["{{classGroup.Key}}"] = diff --git a/src/dymaptic.GeoBlazor.Core/Components/GeometryEngine.cs b/src/dymaptic.GeoBlazor.Core/Components/GeometryEngine.cs index abcd58598..9a5ae33ae 100644 --- a/src/dymaptic.GeoBlazor.Core/Components/GeometryEngine.cs +++ b/src/dymaptic.GeoBlazor.Core/Components/GeometryEngine.cs @@ -1,14 +1,19 @@ // ReSharper disable RedundantEnumerableCastCall namespace dymaptic.GeoBlazor.Core.Components; + /// -/// A client-side geometry engine for testing, measuring, and analyzing the spatial relationship between two or more 2D geometries. If more than one geometry is required for any of the methods below, all geometries must have the same spatial reference for the methods to work as expected. -/// ArcGIS Maps SDK for JavaScript +/// A client-side geometry engine for testing, measuring, and analyzing the spatial relationship between two or more 2D +/// geometries. If more than one geometry is required for any of the methods below, all geometries must have the same +/// spatial reference for the methods to work as expected. +/// +/// ArcGIS +/// Maps SDK for JavaScript +/// /// [CodeGenerationIgnore] public class GeometryEngine : LogicComponent { - // TODO: Add CancellationToken support to all methods - /// /// Default Constructor /// @@ -18,14 +23,15 @@ public GeometryEngine(IAppValidator appValidator, IJSRuntime jsRuntime, JsModule { } - /// + /// protected override string ComponentName => nameof(GeometryEngine).ToLowerFirstChar(); /// /// Creates planar (or Euclidean) buffer polygons at a specified distance around the input geometries. /// /// - /// The GeometryEngine has two methods for buffering geometries client-side: buffer and geodesicBuffer. Use caution when deciding which method to use. As a general rule, use geodesicBuffer if the input geometries have a spatial + /// The GeometryEngine has two methods for buffering geometries client-side: buffer and geodesicBuffer. Use caution + /// when deciding which method to use. As a general rule, use geodesicBuffer if the input geometries have a spatial /// reference of either WGS84 (wkid: 4326) or Web Mercator. Only use buffer (this method) when attempting to buffer /// geometries with a projected coordinate system other than Web Mercator. If you need to buffer geometries with a /// geographic coordinate system other than WGS84 (wkid: 4326), use geometryService.buffer(). @@ -34,22 +40,29 @@ public GeometryEngine(IAppValidator appValidator, IJSRuntime jsRuntime, JsModule /// The buffer input geometries. /// /// - /// The specified distance(s) for buffering. The length of the geometry array does not have to equal the length of the distance array. For example, if you pass an array of four geometries: [g1, g2, g3, g4] and an array with one distance: [d1], all four geometries will be buffered by the single distance value. If instead you use an array of three distances: [d1, d2, d3], g1 will be buffered by d1, g2 by d2, and g3 and g4 will both be buffered by d3. The value of the geometry array will be matched one to one with those in the distance array until the final value of the distance array is reached, in which case that value will be applied to the remaining geometries. + /// The specified distance(s) for buffering. The length of the geometry array does not have to equal the length of the + /// distance array. For example, if you pass an array of four geometries: [g1, g2, g3, g4] and an array with one + /// distance: [d1], all four geometries will be buffered by the single distance value. If instead you use an array of + /// three distances: [d1, d2, d3], g1 will be buffered by d1, g2 by d2, and g3 and g4 will both be buffered by d3. The + /// value of the geometry array will be matched one to one with those in the distance array until the final value of + /// the distance array is reached, in which case that value will be applied to the remaining geometries. /// /// /// The resulting buffers. /// - [SerializedMethod] - public async Task Buffer(IEnumerable geometries, IEnumerable distances) + /// The cancellation token to use for the operation. + public Task Buffer(IEnumerable geometries, IEnumerable distances, + CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(GeometryEngine), parameters: [geometries, distances, null, null]); + return Buffer(geometries, distances, null, null, cancellationToken); } /// /// Creates planar (or Euclidean) buffer polygons at a specified distance around the input geometries. /// /// - /// The GeometryEngine has two methods for buffering geometries client-side: buffer and geodesicBuffer. Use caution when deciding which method to use. As a general rule, use geodesicBuffer if the input geometries have a spatial + /// The GeometryEngine has two methods for buffering geometries client-side: buffer and geodesicBuffer. Use caution + /// when deciding which method to use. As a general rule, use geodesicBuffer if the input geometries have a spatial /// reference of either WGS84 (wkid: 4326) or Web Mercator. Only use buffer (this method) when attempting to buffer /// geometries with a projected coordinate system other than Web Mercator. If you need to buffer geometries with a /// geographic coordinate system other than WGS84 (wkid: 4326), use geometryService.buffer(). @@ -58,7 +71,12 @@ public async Task Buffer(IEnumerable geometries, IEnumerabl /// The buffer input geometries. /// /// - /// The specified distance(s) for buffering. The length of the geometry array does not have to equal the length of the distance array. For example, if you pass an array of four geometries: [g1, g2, g3, g4] and an array with one distance: [d1], all four geometries will be buffered by the single distance value. If instead you use an array of three distances: [d1, d2, d3], g1 will be buffered by d1, g2 by d2, and g3 and g4 will both be buffered by d3. The value of the geometry array will be matched one to one with those in the distance array until the final value of the distance array is reached, in which case that value will be applied to the remaining geometries. + /// The specified distance(s) for buffering. The length of the geometry array does not have to equal the length of the + /// distance array. For example, if you pass an array of four geometries: [g1, g2, g3, g4] and an array with one + /// distance: [d1], all four geometries will be buffered by the single distance value. If instead you use an array of + /// three distances: [d1, d2, d3], g1 will be buffered by d1, g2 by d2, and g3 and g4 will both be buffered by d3. The + /// value of the geometry array will be matched one to one with those in the distance array until the final value of + /// the distance array is reached, in which case that value will be applied to the remaining geometries. /// /// /// Measurement unit of the distance(s). Defaults to the units of the input geometries. @@ -66,18 +84,19 @@ public async Task Buffer(IEnumerable geometries, IEnumerabl /// /// The resulting buffers. /// - [SerializedMethod] - public async Task Buffer(IEnumerable geometries, IEnumerable distances, - GeometryEngineLinearUnit? unit) + /// The cancellation token to use for the operation. + public Task Buffer(IEnumerable geometries, IEnumerable distances, + GeometryEngineLinearUnit? unit, CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(GeometryEngine), parameters: [geometries, distances, unit, null]); + return Buffer(geometries, distances, unit, null, cancellationToken); } /// /// Creates planar (or Euclidean) buffer polygons at a specified distance around the input geometries. /// /// - /// The GeometryEngine has two methods for buffering geometries client-side: buffer and geodesicBuffer. Use caution when deciding which method to use. As a general rule, use geodesicBuffer if the input geometries have a spatial + /// The GeometryEngine has two methods for buffering geometries client-side: buffer and geodesicBuffer. Use caution + /// when deciding which method to use. As a general rule, use geodesicBuffer if the input geometries have a spatial /// reference of either WGS84 (wkid: 4326) or Web Mercator. Only use buffer (this method) when attempting to buffer /// geometries with a projected coordinate system other than Web Mercator. If you need to buffer geometries with a /// geographic coordinate system other than WGS84 (wkid: 4326), use geometryService.buffer(). @@ -86,7 +105,12 @@ public async Task Buffer(IEnumerable geometries, IEnumerabl /// The buffer input geometries. /// /// - /// The specified distance(s) for buffering. The length of the geometry array does not have to equal the length of the distance array. For example, if you pass an array of four geometries: [g1, g2, g3, g4] and an array with one distance: [d1], all four geometries will be buffered by the single distance value. If instead you use an array of three distances: [d1, d2, d3], g1 will be buffered by d1, g2 by d2, and g3 and g4 will both be buffered by d3. The value of the geometry array will be matched one to one with those in the distance array until the final value of the distance array is reached, in which case that value will be applied to the remaining geometries. + /// The specified distance(s) for buffering. The length of the geometry array does not have to equal the length of the + /// distance array. For example, if you pass an array of four geometries: [g1, g2, g3, g4] and an array with one + /// distance: [d1], all four geometries will be buffered by the single distance value. If instead you use an array of + /// three distances: [d1, d2, d3], g1 will be buffered by d1, g2 by d2, and g3 and g4 will both be buffered by d3. The + /// value of the geometry array will be matched one to one with those in the distance array until the final value of + /// the distance array is reached, in which case that value will be applied to the remaining geometries. /// /// /// Measurement unit of the distance(s). Defaults to the units of the input geometries. @@ -97,18 +121,21 @@ public async Task Buffer(IEnumerable geometries, IEnumerabl /// /// The resulting buffers. /// + /// The cancellation token to use for the operation. [SerializedMethod] - public async Task Buffer(IEnumerable geometries, IEnumerable distances, - GeometryEngineLinearUnit? unit, bool? unionResults) + public async Task Buffer(IEnumerable geometries, IEnumerable distances, + GeometryEngineLinearUnit? unit, bool? unionResults, CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(GeometryEngine), parameters: [geometries, distances, unit, unionResults]); + return await InvokeAsync(nameof(ProjectionEngine), nameof(distances), + cancellationToken, geometries, distances, unit, unionResults); } /// /// Creates planar (or Euclidean) buffer polygons at a specified distance around the input geometries. /// /// - /// The GeometryEngine has two methods for buffering geometries client-side: buffer and geodesicBuffer. Use caution when deciding which method to use. As a general rule, use geodesicBuffer if the input geometries have a spatial + /// The GeometryEngine has two methods for buffering geometries client-side: buffer and geodesicBuffer. Use caution + /// when deciding which method to use. As a general rule, use geodesicBuffer if the input geometries have a spatial /// reference of either WGS84 (wkid: 4326) or Web Mercator. Only use buffer (this method) when attempting to buffer /// geometries with a projected coordinate system other than Web Mercator. If you need to buffer geometries with a /// geographic coordinate system other than WGS84 (wkid: 4326), use geometryService.buffer(). @@ -122,17 +149,18 @@ public async Task Buffer(IEnumerable geometries, IEnumerabl /// /// The resulting buffer. /// - [SerializedMethod] - public async Task Buffer(Geometry geometry, double distance) + /// The cancellation token to use for the operation. + public Task Buffer(Geometry geometry, double distance, CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(GeometryEngine), parameters: [geometry, distance, null]); + return Buffer(geometry, distance, null, cancellationToken); } /// /// Creates planar (or Euclidean) buffer polygons at a specified distance around the input geometries. /// /// - /// The GeometryEngine has two methods for buffering geometries client-side: buffer and geodesicBuffer. Use caution when deciding which method to use. As a general rule, use geodesicBuffer if the input geometries have a spatial + /// The GeometryEngine has two methods for buffering geometries client-side: buffer and geodesicBuffer. Use caution + /// when deciding which method to use. As a general rule, use geodesicBuffer if the input geometries have a spatial /// reference of either WGS84 (wkid: 4326) or Web Mercator. Only use buffer (this method) when attempting to buffer /// geometries with a projected coordinate system other than Web Mercator. If you need to buffer geometries with a /// geographic coordinate system other than WGS84 (wkid: 4326), use geometryService.buffer(). @@ -149,10 +177,13 @@ public async Task Buffer(Geometry geometry, double distance) /// /// The resulting buffer. /// + /// The cancellation token to use for the operation. [SerializedMethod] - public async Task Buffer(Geometry geometry, double distance, GeometryEngineLinearUnit? unit) + public async Task Buffer(Geometry geometry, double distance, GeometryEngineLinearUnit? unit, + CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(GeometryEngine), parameters: [geometry, distance, unit]); + return await InvokeAsync(nameof(ProjectionEngine), nameof(Buffer), + cancellationToken, geometry, distance, unit); } /// @@ -167,17 +198,20 @@ public async Task Buffer(Geometry geometry, double distance, GeometryEn /// /// Clipped geometry. /// + /// The cancellation token to use for the operation. [SerializedMethod] - public async Task Clip(Geometry geometry, Extent extent) + public async Task Clip(Geometry geometry, Extent extent, CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(GeometryEngine), parameters: [geometry, extent]); + return await InvokeAsync(nameof(ProjectionEngine), nameof(Clip), + cancellationToken, geometry, extent); } /// /// Indicates if one geometry contains another geometry. /// /// - /// The geometry that is tested for the "contains" relationship to the other geometry. Think of this geometry as the potential "container" of the insideGeometry. + /// The geometry that is tested for the "contains" relationship to the other geometry. Think of this geometry as the + /// potential "container" of the insideGeometry. /// /// /// The geometry that is tested for the "within" relationship to the containerGeometry. @@ -185,14 +219,19 @@ public async Task Buffer(Geometry geometry, double distance, GeometryEn /// /// Returns true if the containerGeometry contains the insideGeometry. /// + /// The cancellation token to use for the operation. [SerializedMethod] - public async Task Contains(Geometry containerGeometry, Geometry insideGeometry) + public async Task Contains(Geometry containerGeometry, Geometry insideGeometry, + CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(GeometryEngine), parameters: [containerGeometry, insideGeometry]); + return await InvokeAsync(nameof(ProjectionEngine), nameof(Contains), + cancellationToken, containerGeometry, insideGeometry); } /// - /// Calculates the convex hull of one or more geometries. A convex hull is the smallest convex polygon that encloses a group of geometries or vertices. The input can be a single geometry (such as a polyline) or an array of any geometry type. The hull is typically a polygon but can also be a polyline or a point in degenerate cases. + /// Calculates the convex hull of one or more geometries. A convex hull is the smallest convex polygon that encloses a + /// group of geometries or vertices. The input can be a single geometry (such as a polyline) or an array of any + /// geometry type. The hull is typically a polygon but can also be a polyline or a point in degenerate cases. /// /// /// The input geometries used to calculate the convex hull. The input array can include various geometry types. @@ -204,14 +243,19 @@ public async Task Contains(Geometry containerGeometry, Geometry insideGeom /// Returns the convex hull of the input geometries. This is usually a polygon, but can also be a polyline (if the /// input is a set of points or polylines forming a straight line), or a point (in degenerate cases). /// + /// The cancellation token to use for the operation. [SerializedMethod] - public async Task ConvexHull(IEnumerable geometries, bool? merge = null) + public async Task ConvexHull(IEnumerable geometries, bool? merge = null, + CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(GeometryEngine), parameters: [geometries, merge]); + return await InvokeAsync(nameof(ProjectionEngine), nameof(ConvexHull), + cancellationToken, geometries, merge); } /// - /// Calculates the convex hull of one or more geometries. A convex hull is the smallest convex polygon that encloses a group of geometries or vertices. The input can be a single geometry (such as a polyline) or an array of any geometry type. The hull is typically a polygon but can also be a polyline or a point in degenerate cases. + /// Calculates the convex hull of one or more geometries. A convex hull is the smallest convex polygon that encloses a + /// group of geometries or vertices. The input can be a single geometry (such as a polyline) or an array of any + /// geometry type. The hull is typically a polygon but can also be a polyline or a point in degenerate cases. /// /// /// The input geometry used to calculate the convex hull. @@ -220,10 +264,12 @@ public async Task ConvexHull(IEnumerable geometries, bool? /// Returns the convex hull of the input geometries. This is usually a polygon, but can also be a polyline (if the /// input is a set of points or polylines forming a straight line), or a point (in degenerate cases). /// + /// The cancellation token to use for the operation. [SerializedMethod] - public async Task ConvexHull(Geometry geometry) + public async Task ConvexHull(Geometry geometry, CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(GeometryEngine), parameters: [geometry]); + return await InvokeAsync(nameof(ProjectionEngine), nameof(ConvexHull), + cancellationToken, geometry); } /// @@ -238,14 +284,23 @@ public async Task ConvexHull(Geometry geometry) /// /// Returns true if geometry1 crosses geometry2. /// + /// The cancellation token to use for the operation. [SerializedMethod] - public async Task Crosses(Geometry geometry1, Geometry geometry2) + public async Task Crosses(Geometry geometry1, Geometry geometry2, + CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(GeometryEngine), parameters: [geometry1, geometry2]); + return await InvokeAsync(nameof(ProjectionEngine), nameof(Crosses), + cancellationToken, geometry1, geometry2); } /// - /// Splits the input Polyline or Polygon where it crosses a cutting Polyline. For Polylines, all left cuts are grouped together in the first Geometry. Right cuts and coincident cuts are grouped in the second Geometry and each undefined cut, along with any uncut parts, are output as separate Polylines. For Polygons, all left cuts are grouped in the first Polygon, all right cuts are grouped in the second Polygon, and each undefined cut, along with any leftover parts after cutting, are output as a separate Polygon. If no cuts are returned then the array will be empty. An undefined cut will only be produced if a left cut or right cut was produced and there was a part left over after cutting, or a cut is bounded to the left and right of the cutter. + /// Splits the input Polyline or Polygon where it crosses a cutting Polyline. For Polylines, all left cuts are grouped + /// together in the first Geometry. Right cuts and coincident cuts are grouped in the second Geometry and each + /// undefined cut, along with any uncut parts, are output as separate Polylines. For Polygons, all left cuts are + /// grouped in the first Polygon, all right cuts are grouped in the second Polygon, and each undefined cut, along with + /// any leftover parts after cutting, are output as a separate Polygon. If no cuts are returned then the array will be + /// empty. An undefined cut will only be produced if a left cut or right cut was produced and there was a part left + /// over after cutting, or a cut is bounded to the left and right of the cutter. /// /// /// The geometry to be cut. @@ -256,10 +311,12 @@ public async Task Crosses(Geometry geometry1, Geometry geometry2) /// /// Returns an array of geometries created by cutting the input geometry with the cutter. /// + /// The cancellation token to use for the operation. [SerializedMethod] - public async Task Cut(Geometry geometry, Polyline cutter) + public async Task Cut(Geometry geometry, Polyline cutter, CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(GeometryEngine), parameters: [geometry, cutter]); + return await InvokeAsync(nameof(ProjectionEngine), nameof(Cut), + cancellationToken, geometry, cutter); } /// @@ -277,14 +334,18 @@ public async Task Cut(Geometry geometry, Polyline cutter) /// /// The densified geometry. /// + /// The cancellation token to use for the operation. [SerializedMethod] - public async Task Densify(Geometry geometry, double maxSegmentLength, GeometryEngineLinearUnit? maxSegmentLengthUnit = null) + public async Task Densify(Geometry geometry, double maxSegmentLength, + GeometryEngineLinearUnit? maxSegmentLengthUnit = null, CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(GeometryEngine), parameters: [geometry, maxSegmentLength, maxSegmentLengthUnit]); + return await InvokeAsync(nameof(ProjectionEngine), nameof(Densify), + cancellationToken, geometry, maxSegmentLength, maxSegmentLengthUnit); } /// - /// Creates the difference of two geometries. The resultant geometry is the portion of inputGeometry not in the subtractor. The dimension of the subtractor has to be equal to or greater than that of the inputGeometry. + /// Creates the difference of two geometries. The resultant geometry is the portion of inputGeometry not in the + /// subtractor. The dimension of the subtractor has to be equal to or greater than that of the inputGeometry. /// /// /// The input geometries to subtract from. @@ -295,14 +356,18 @@ public async Task Densify(Geometry geometry, double maxSegmentLength, /// /// Returns the geometry of inputGeometry minus the subtractor geometry. /// + /// The cancellation token to use for the operation. [SerializedMethod] - public async Task Difference(IEnumerable geometries, Geometry subtractor) + public async Task Difference(IEnumerable geometries, Geometry subtractor, + CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(GeometryEngine), parameters: [geometries, subtractor]); + return await InvokeAsync(nameof(ProjectionEngine), nameof(geometries), + cancellationToken, geometries, subtractor); } /// - /// Creates the difference of two geometries. The resultant geometry is the portion of inputGeometry not in the subtractor. The dimension of the subtractor has to be equal to or greater than that of the inputGeometry. + /// Creates the difference of two geometries. The resultant geometry is the portion of inputGeometry not in the + /// subtractor. The dimension of the subtractor has to be equal to or greater than that of the inputGeometry. /// /// /// The input geometry to subtract from. @@ -313,10 +378,13 @@ public async Task Difference(IEnumerable geometries, Geome /// /// Returns the geometry of inputGeometry minus the subtractor geometry. /// + /// The cancellation token to use for the operation. [SerializedMethod] - public async Task Difference(Geometry geometry, Geometry subtractor) + public async Task Difference(Geometry geometry, Geometry subtractor, + CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(GeometryEngine), parameters: [geometry, subtractor]); + return await InvokeAsync(nameof(ProjectionEngine), nameof(Difference), + cancellationToken, geometry, subtractor); } /// @@ -331,14 +399,18 @@ public async Task Difference(Geometry geometry, Geometry subtractor) /// /// Returns true if geometry1 and geometry2 are disjoint (don't intersect in any way). /// + /// The cancellation token to use for the operation. [SerializedMethod] - public async Task Disjoint(Geometry geometry1, Geometry geometry2) + public async Task Disjoint(Geometry geometry1, Geometry geometry2, + CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(GeometryEngine), parameters: [geometry1, geometry2]); + return await InvokeAsync(nameof(ProjectionEngine), nameof(Disjoint), + cancellationToken, geometry1, geometry2); } /// - /// Calculates the shortest planar distance between two geometries. Distance is reported in the linear units specified by distanceUnit or, if distanceUnit is null, the units of the spatialReference of input geometry. + /// Calculates the shortest planar distance between two geometries. Distance is reported in the linear units specified + /// by distanceUnit or, if distanceUnit is null, the units of the spatialReference of input geometry. /// /// /// First input geometry. @@ -352,17 +424,21 @@ public async Task Disjoint(Geometry geometry1, Geometry geometry2) /// /// Distance between the two input geometries. /// + /// The cancellation token to use for the operation. [SerializedMethod] - public async Task Distance(Geometry geometry1, Geometry geometry2, GeometryEngineLinearUnit? distanceUnit = null) + public async Task Distance(Geometry geometry1, Geometry geometry2, + GeometryEngineLinearUnit? distanceUnit = null, CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(GeometryEngine), parameters: [geometry1, geometry2, distanceUnit]); + return await InvokeAsync(nameof(ProjectionEngine), nameof(Distance), + cancellationToken, geometry1, geometry2, distanceUnit); } /// /// Indicates if two geometries are equal. /// /// - /// In ArcGIS for JS, this method is called `Equals`. However, this term has special meaning in .NET, so we have renamed here. + /// In ArcGIS for JS, this method is called `Equals`. However, this term has special meaning in .NET, so we have + /// renamed here. /// /// /// First input geometry. @@ -373,10 +449,13 @@ public async Task Distance(Geometry geometry1, Geometry geometry2, Geome /// /// Returns true if the two input geometries are equal. /// + /// The cancellation token to use for the operation. [SerializedMethod] - public async Task AreEqual(Geometry geometry1, Geometry geometry2) + public async Task AreEqual(Geometry geometry1, Geometry geometry2, + CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(GeometryEngine), parameters: [geometry1, geometry2]); + return await InvokeAsync(nameof(ProjectionEngine), nameof(AreEqual), + cancellationToken, geometry1, geometry2); } /// @@ -386,12 +465,15 @@ public async Task AreEqual(Geometry geometry1, Geometry geometry2) /// The input spatial reference. /// /// - /// Resolves to a object. + /// Resolves to a object. /// + /// The cancellation token to use for the operation. [SerializedMethod] - public async Task ExtendedSpatialReferenceInfo(SpatialReference spatialReference) + public async Task ExtendedSpatialReferenceInfo(SpatialReference spatialReference, + CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(GeometryEngine), parameters: [spatialReference]); + return await InvokeAsync(nameof(ProjectionEngine), nameof(ExtendedSpatialReferenceInfo), + cancellationToken, spatialReference); } /// @@ -406,10 +488,13 @@ public async Task ExtendedSpatialReferenceInfo(SpatialRefe /// /// The flipped geometry. /// + /// The cancellation token to use for the operation. [SerializedMethod] - public async Task FlipHorizontal(Geometry geometry, Point? flipOrigin = null) + public async Task FlipHorizontal(Geometry geometry, Point? flipOrigin = null, + CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(GeometryEngine), parameters: [geometry, flipOrigin]); + return await InvokeAsync(nameof(ProjectionEngine), nameof(FlipHorizontal), + cancellationToken, geometry, flipOrigin); } /// @@ -424,14 +509,18 @@ public async Task FlipHorizontal(Geometry geometry, Point? flipOrigin /// /// The flipped geometry. /// + /// The cancellation token to use for the operation. [SerializedMethod] - public async Task FlipVertical(Geometry geometry, Point? flipOrigin = null) + public async Task FlipVertical(Geometry geometry, Point? flipOrigin = null, + CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(GeometryEngine), parameters: [geometry, flipOrigin]); + return await InvokeAsync(nameof(ProjectionEngine), nameof(FlipVertical), + cancellationToken, geometry, flipOrigin); } /// - /// Performs the generalize operation on the geometries in the cursor. Point and Multipoint geometries are left unchanged. Envelope is converted to a Polygon and then generalized. + /// Performs the generalize operation on the geometries in the cursor. Point and Multipoint geometries are left + /// unchanged. Envelope is converted to a Polygon and then generalized. /// /// /// The input geometry to be generalized. @@ -448,14 +537,21 @@ public async Task FlipVertical(Geometry geometry, Point? flipOrigin = /// /// The generalized geometry. /// + /// The cancellation token to use for the operation. [SerializedMethod] - public async Task Generalize(Geometry geometry, double maxDeviation, bool? removeDegenerateParts = null, GeometryEngineLinearUnit? maxDeviationUnit = null) + public async Task Generalize(Geometry geometry, double maxDeviation, bool? removeDegenerateParts = null, + GeometryEngineLinearUnit? maxDeviationUnit = null, CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(GeometryEngine), parameters: [geometry, maxDeviation, removeDegenerateParts, maxDeviationUnit]); + return await InvokeAsync(nameof(ProjectionEngine), nameof(Generalize), + cancellationToken, geometry, maxDeviation, removeDegenerateParts, maxDeviationUnit); } /// - /// Calculates the area of the input geometry. As opposed to planarArea(), geodesicArea takes into account the curvature of the earth when performing this calculation. Therefore, when using input geometries with a spatial reference of either WGS84 (wkid: 4326) or Web Mercator, it is best practice to calculate areas using geodesicArea(). If the input geometries have a projected coordinate system other than Web Mercator, use planarArea() instead. + /// Calculates the area of the input geometry. As opposed to planarArea(), geodesicArea takes into account the + /// curvature of the earth when performing this calculation. Therefore, when using input geometries with a spatial + /// reference of either WGS84 (wkid: 4326) or Web Mercator, it is best practice to calculate areas using + /// geodesicArea(). If the input geometries have a projected coordinate system other than Web Mercator, use + /// planarArea() instead. /// /// /// This method only works with WGS84 (wkid: 4326) and Web Mercator spatial references. @@ -469,17 +565,24 @@ public async Task Generalize(Geometry geometry, double maxDeviation, b /// /// Area of the input geometry. /// + /// The cancellation token to use for the operation. [SerializedMethod] - public async Task GeodesicArea(Polygon geometry, GeometryEngineAreaUnit? unit = null) + public async Task GeodesicArea(Polygon geometry, GeometryEngineAreaUnit? unit = null, + CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(GeometryEngine), parameters: [geometry, unit]); + return await InvokeAsync(nameof(ProjectionEngine), nameof(GeodesicArea), + cancellationToken, geometry, unit); } /// - /// Creates geodesic buffer polygons at a specified distance around the input geometries. When calculating distances, this method takes the curvature of the earth into account, which provides highly accurate results when dealing with very large geometries and/or geometries that spatially vary on a global scale where one projected coordinate system could not accurately plot coordinates and measure distances for all the geometries. + /// Creates geodesic buffer polygons at a specified distance around the input geometries. When calculating distances, + /// this method takes the curvature of the earth into account, which provides highly accurate results when dealing with + /// very large geometries and/or geometries that spatially vary on a global scale where one projected coordinate system + /// could not accurately plot coordinates and measure distances for all the geometries. /// /// - /// This method only works with WGS84 (wkid: 4326) and Web Mercator spatial references. In general, if your input geometries are assigned one of those two spatial references, you should always use geodesicBuffer() to obtain the + /// This method only works with WGS84 (wkid: 4326) and Web Mercator spatial references. In general, if your input + /// geometries are assigned one of those two spatial references, you should always use geodesicBuffer() to obtain the /// most accurate results for those geometries. If needing to buffer points assigned a projected coordinate system /// other than Web Mercator, use buffer() instead. If the input geometries have a geographic coordinate system other /// than WGS84 (wkid: 4326), use geometryService.buffer(). @@ -488,22 +591,32 @@ public async Task GeodesicArea(Polygon geometry, GeometryEngineAreaUnit? /// The buffer input geometries /// /// - /// The specified distance(s) for buffering. The length of the geometry array does not have to equal the length of the distance array. For example, if you pass an array of four geometries: [g1, g2, g3, g4] and an array with one distance: [d1], all four geometries will be buffered by the single distance value. If instead you use an array of three distances: [d1, d2, d3], g1 will be buffered by d1, g2 by d2, and g3 and g4 will both be buffered by d3. The value of the geometry array will be matched one to one with those in the distance array until the final value of the distance array is reached, in which case that value will be applied to the remaining geometries. + /// The specified distance(s) for buffering. The length of the geometry array does not have to equal the length of the + /// distance array. For example, if you pass an array of four geometries: [g1, g2, g3, g4] and an array with one + /// distance: [d1], all four geometries will be buffered by the single distance value. If instead you use an array of + /// three distances: [d1, d2, d3], g1 will be buffered by d1, g2 by d2, and g3 and g4 will both be buffered by d3. The + /// value of the geometry array will be matched one to one with those in the distance array until the final value of + /// the distance array is reached, in which case that value will be applied to the remaining geometries. /// /// /// The resulting buffers /// - [SerializedMethod] - public async Task GeodesicBuffer(IEnumerable geometries, IEnumerable distances) + /// The cancellation token to use for the operation. + public Task GeodesicBuffer(IEnumerable geometries, IEnumerable distances, + CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(GeometryEngine), parameters: [geometries, distances, null, null]); + return GeodesicBuffer(geometries, distances, null, null, cancellationToken); } /// - /// Creates geodesic buffer polygons at a specified distance around the input geometries. When calculating distances, this method takes the curvature of the earth into account, which provides highly accurate results when dealing with very large geometries and/or geometries that spatially vary on a global scale where one projected coordinate system could not accurately plot coordinates and measure distances for all the geometries. + /// Creates geodesic buffer polygons at a specified distance around the input geometries. When calculating distances, + /// this method takes the curvature of the earth into account, which provides highly accurate results when dealing with + /// very large geometries and/or geometries that spatially vary on a global scale where one projected coordinate system + /// could not accurately plot coordinates and measure distances for all the geometries. /// /// - /// This method only works with WGS84 (wkid: 4326) and Web Mercator spatial references. In general, if your input geometries are assigned one of those two spatial references, you should always use geodesicBuffer() to obtain the + /// This method only works with WGS84 (wkid: 4326) and Web Mercator spatial references. In general, if your input + /// geometries are assigned one of those two spatial references, you should always use geodesicBuffer() to obtain the /// most accurate results for those geometries. If needing to buffer points assigned a projected coordinate system /// other than Web Mercator, use buffer() instead. If the input geometries have a geographic coordinate system other /// than WGS84 (wkid: 4326), use geometryService.buffer(). @@ -512,7 +625,12 @@ public async Task GeodesicBuffer(IEnumerable geometries, IE /// The buffer input geometries /// /// - /// The specified distance(s) for buffering. The length of the geometry array does not have to equal the length of the distance array. For example, if you pass an array of four geometries: [g1, g2, g3, g4] and an array with one distance: [d1], all four geometries will be buffered by the single distance value. If instead you use an array of three distances: [d1, d2, d3], g1 will be buffered by d1, g2 by d2, and g3 and g4 will both be buffered by d3. The value of the geometry array will be matched one to one with those in the distance array until the final value of the distance array is reached, in which case that value will be applied to the remaining geometries. + /// The specified distance(s) for buffering. The length of the geometry array does not have to equal the length of the + /// distance array. For example, if you pass an array of four geometries: [g1, g2, g3, g4] and an array with one + /// distance: [d1], all four geometries will be buffered by the single distance value. If instead you use an array of + /// three distances: [d1, d2, d3], g1 will be buffered by d1, g2 by d2, and g3 and g4 will both be buffered by d3. The + /// value of the geometry array will be matched one to one with those in the distance array until the final value of + /// the distance array is reached, in which case that value will be applied to the remaining geometries. /// /// /// Measurement unit of the distance(s). Defaults to the units of the input geometries. @@ -520,18 +638,22 @@ public async Task GeodesicBuffer(IEnumerable geometries, IE /// /// The resulting buffers /// - [SerializedMethod] - public async Task GeodesicBuffer(IEnumerable geometries, IEnumerable distances, - GeometryEngineLinearUnit? unit) + /// The cancellation token to use for the operation. + public Task GeodesicBuffer(IEnumerable geometries, IEnumerable distances, + GeometryEngineLinearUnit? unit, CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(GeometryEngine), parameters: [geometries, distances, unit, null]); + return GeodesicBuffer(geometries, distances, unit, null, cancellationToken); } /// - /// Creates geodesic buffer polygons at a specified distance around the input geometries. When calculating distances, this method takes the curvature of the earth into account, which provides highly accurate results when dealing with very large geometries and/or geometries that spatially vary on a global scale where one projected coordinate system could not accurately plot coordinates and measure distances for all the geometries. + /// Creates geodesic buffer polygons at a specified distance around the input geometries. When calculating distances, + /// this method takes the curvature of the earth into account, which provides highly accurate results when dealing with + /// very large geometries and/or geometries that spatially vary on a global scale where one projected coordinate system + /// could not accurately plot coordinates and measure distances for all the geometries. /// /// - /// This method only works with WGS84 (wkid: 4326) and Web Mercator spatial references. In general, if your input geometries are assigned one of those two spatial references, you should always use geodesicBuffer() to obtain the + /// This method only works with WGS84 (wkid: 4326) and Web Mercator spatial references. In general, if your input + /// geometries are assigned one of those two spatial references, you should always use geodesicBuffer() to obtain the /// most accurate results for those geometries. If needing to buffer points assigned a projected coordinate system /// other than Web Mercator, use buffer() instead. If the input geometries have a geographic coordinate system other /// than WGS84 (wkid: 4326), use geometryService.buffer(). @@ -540,7 +662,12 @@ public async Task GeodesicBuffer(IEnumerable geometries, IE /// The buffer input geometries /// /// - /// The specified distance(s) for buffering. The length of the geometry array does not have to equal the length of the distance array. For example, if you pass an array of four geometries: [g1, g2, g3, g4] and an array with one distance: [d1], all four geometries will be buffered by the single distance value. If instead you use an array of three distances: [d1, d2, d3], g1 will be buffered by d1, g2 by d2, and g3 and g4 will both be buffered by d3. The value of the geometry array will be matched one to one with those in the distance array until the final value of the distance array is reached, in which case that value will be applied to the remaining geometries. + /// The specified distance(s) for buffering. The length of the geometry array does not have to equal the length of the + /// distance array. For example, if you pass an array of four geometries: [g1, g2, g3, g4] and an array with one + /// distance: [d1], all four geometries will be buffered by the single distance value. If instead you use an array of + /// three distances: [d1, d2, d3], g1 will be buffered by d1, g2 by d2, and g3 and g4 will both be buffered by d3. The + /// value of the geometry array will be matched one to one with those in the distance array until the final value of + /// the distance array is reached, in which case that value will be applied to the remaining geometries. /// /// /// Measurement unit of the distance(s). Defaults to the units of the input geometries. @@ -551,18 +678,24 @@ public async Task GeodesicBuffer(IEnumerable geometries, IE /// /// The resulting buffers /// + /// The cancellation token to use for the operation. [SerializedMethod] - public async Task GeodesicBuffer(IEnumerable geometries, IEnumerable distances, - GeometryEngineLinearUnit? unit, bool? unionResults) + public async Task GeodesicBuffer(IEnumerable geometries, IEnumerable distances, + GeometryEngineLinearUnit? unit, bool? unionResults, CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(GeometryEngine), parameters: [geometries, distances, unit, unionResults]); + return await InvokeAsync(nameof(ProjectionEngine), nameof(distances), + cancellationToken, geometries, distances, unit, unionResults); } /// - /// Creates geodesic buffer polygons at a specified distance around the input geometries. When calculating distances, this method takes the curvature of the earth into account, which provides highly accurate results when dealing with very large geometries and/or geometries that spatially vary on a global scale where one projected coordinate system could not accurately plot coordinates and measure distances for all the geometries. + /// Creates geodesic buffer polygons at a specified distance around the input geometries. When calculating distances, + /// this method takes the curvature of the earth into account, which provides highly accurate results when dealing with + /// very large geometries and/or geometries that spatially vary on a global scale where one projected coordinate system + /// could not accurately plot coordinates and measure distances for all the geometries. /// /// - /// This method only works with WGS84 (wkid: 4326) and Web Mercator spatial references. In general, if your input geometries are assigned one of those two spatial references, you should always use geodesicBuffer() to obtain the + /// This method only works with WGS84 (wkid: 4326) and Web Mercator spatial references. In general, if your input + /// geometries are assigned one of those two spatial references, you should always use geodesicBuffer() to obtain the /// most accurate results for those geometries. If needing to buffer points assigned a projected coordinate system /// other than Web Mercator, use buffer() instead. If the input geometries have a geographic coordinate system other /// than WGS84 (wkid: 4326), use geometryService.buffer(). @@ -576,17 +709,22 @@ public async Task GeodesicBuffer(IEnumerable geometries, IE /// /// The resulting buffers /// - [SerializedMethod] - public async Task GeodesicBuffer(Geometry geometry, double distance) + /// The cancellation token to use for the operation. + public Task GeodesicBuffer(Geometry geometry, double distance, + CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(GeometryEngine), parameters: [geometry, distance, null]); + return GeodesicBuffer(geometry, distance, null, cancellationToken); } /// - /// Creates geodesic buffer polygons at a specified distance around the input geometries. When calculating distances, this method takes the curvature of the earth into account, which provides highly accurate results when dealing with very large geometries and/or geometries that spatially vary on a global scale where one projected coordinate system could not accurately plot coordinates and measure distances for all the geometries. + /// Creates geodesic buffer polygons at a specified distance around the input geometries. When calculating distances, + /// this method takes the curvature of the earth into account, which provides highly accurate results when dealing with + /// very large geometries and/or geometries that spatially vary on a global scale where one projected coordinate system + /// could not accurately plot coordinates and measure distances for all the geometries. /// /// - /// This method only works with WGS84 (wkid: 4326) and Web Mercator spatial references. In general, if your input geometries are assigned one of those two spatial references, you should always use geodesicBuffer() to obtain the + /// This method only works with WGS84 (wkid: 4326) and Web Mercator spatial references. In general, if your input + /// geometries are assigned one of those two spatial references, you should always use geodesicBuffer() to obtain the /// most accurate results for those geometries. If needing to buffer points assigned a projected coordinate system /// other than Web Mercator, use buffer() instead. If the input geometries have a geographic coordinate system other /// than WGS84 (wkid: 4326), use geometryService.buffer(). @@ -603,38 +741,46 @@ public async Task GeodesicBuffer(Geometry geometry, double distance) /// /// The resulting buffers /// + /// The cancellation token to use for the operation. [SerializedMethod] - public async Task GeodesicBuffer(Geometry geometry, double distance, GeometryEngineLinearUnit? unit) + public async Task GeodesicBuffer(Geometry geometry, double distance, GeometryEngineLinearUnit? unit, + CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(GeometryEngine), parameters: [geometry, distance, unit]); + return await InvokeAsync(nameof(ProjectionEngine), nameof(GeodesicBuffer), + cancellationToken, geometry, distance, unit); } /// - /// Returns a geodesically densified version of the input geometry. Use this function to draw the line(s) of the geometry along great circles. + /// Returns a geodesically densified version of the input geometry. Use this function to draw the line(s) of the + /// geometry along great circles. /// /// /// A polyline or polygon to densify. /// /// - /// The maximum segment length allowed (in meters if a maxSegmentLengthUnit is not provided). This must be a positive value. + /// The maximum segment length allowed (in meters if a maxSegmentLengthUnit is not provided). This must be a positive + /// value. /// /// /// Returns the densified geometry. /// - [SerializedMethod] - public async Task GeodesicDensify(Geometry geometry, double maxSegmentLength) + /// The cancellation token to use for the operation. + public Task GeodesicDensify(Geometry geometry, double maxSegmentLength, + CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(GeometryEngine), parameters: [geometry, maxSegmentLength, null]); + return GeodesicDensify(geometry, maxSegmentLength, null, cancellationToken); } - + /// - /// Returns a geodesically densified version of the input geometry. Use this function to draw the line(s) of the geometry along great circles. + /// Returns a geodesically densified version of the input geometry. Use this function to draw the line(s) of the + /// geometry along great circles. /// /// /// A polyline or polygon to densify. /// /// - /// The maximum segment length allowed (in meters if a maxSegmentLengthUnit is not provided). This must be a positive value. + /// The maximum segment length allowed (in meters if a maxSegmentLengthUnit is not provided). This must be a positive + /// value. /// /// /// Measurement unit for maxSegmentLength. If not provided, the unit will default to meters. @@ -642,15 +788,21 @@ public async Task GeodesicDensify(Geometry geometry, double maxSegment /// /// Returns the densified geometry. /// + /// The cancellation token to use for the operation. [SerializedMethod] - public async Task GeodesicDensify(Geometry geometry, double maxSegmentLength, - GeometryEngineLinearUnit? maxSegmentLengthUnit) + public async Task GeodesicDensify(Geometry geometry, double maxSegmentLength, + GeometryEngineLinearUnit? maxSegmentLengthUnit, CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(GeometryEngine), parameters: [geometry, maxSegmentLength, maxSegmentLengthUnit]); + return await InvokeAsync(nameof(ProjectionEngine), nameof(GeodesicDensify), + cancellationToken, geometry, maxSegmentLength, maxSegmentLengthUnit); } /// - /// Calculates the length of the input geometry. As opposed to planarLength(), geodesicLength() takes into account the curvature of the earth when performing this calculation. Therefore, when using input geometries with a spatial reference of either WGS84 (wkid: 4326) or Web Mercator, it is best practice to calculate lengths using geodesicLength(). If the input geometries have a projected coordinate system other than Web Mercator, use planarLength() instead. + /// Calculates the length of the input geometry. As opposed to planarLength(), geodesicLength() takes into account the + /// curvature of the earth when performing this calculation. Therefore, when using input geometries with a spatial + /// reference of either WGS84 (wkid: 4326) or Web Mercator, it is best practice to calculate lengths using + /// geodesicLength(). If the input geometries have a projected coordinate system other than Web Mercator, use + /// planarLength() instead. /// /// /// This method only works with WGS84 (wkid: 4326) and Web Mercator spatial references. @@ -664,14 +816,19 @@ public async Task GeodesicDensify(Geometry geometry, double maxSegment /// /// Length of the input geometry. /// + /// The cancellation token to use for the operation. [SerializedMethod] - public async Task GeodesicLength(Geometry geometry, GeometryEngineLinearUnit? unit = null) + public async Task GeodesicLength(Geometry geometry, GeometryEngineLinearUnit? unit = null, + CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(GeometryEngine), parameters: [geometry, unit]); + return await InvokeAsync(nameof(ProjectionEngine), nameof(GeodesicLength), + cancellationToken, geometry, unit); } /// - /// Creates new geometries from the intersections between two geometries. If the input geometries have different dimensions (i.e. point = 0; polyline = 1; polygon = 2), then the result's dimension will be equal to the lowest dimension of the inputs. The table below describes the expected output for various combinations of geometry types. + /// Creates new geometries from the intersections between two geometries. If the input geometries have different + /// dimensions (i.e. point = 0; polyline = 1; polygon = 2), then the result's dimension will be equal to the lowest + /// dimension of the inputs. The table below describes the expected output for various combinations of geometry types. /// /// /// The input array of geometries. @@ -682,14 +839,19 @@ public async Task GeodesicLength(Geometry geometry, GeometryEngineLinear /// /// The intersections of the geometries. /// + /// The cancellation token to use for the operation. [SerializedMethod] - public async Task Intersect(IEnumerable geometries1, Geometry geometry2) + public async Task Intersect(IEnumerable geometries1, Geometry geometry2, + CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(GeometryEngine), parameters: [geometries1, geometry2]); + return await InvokeAsync(nameof(ProjectionEngine), nameof(geometries1), + cancellationToken, geometries1, geometry2); } /// - /// Creates new geometries from the intersections between two geometries. If the input geometries have different dimensions (i.e. point = 0; polyline = 1; polygon = 2), then the result's dimension will be equal to the lowest dimension of the inputs. + /// Creates new geometries from the intersections between two geometries. If the input geometries have different + /// dimensions (i.e. point = 0; polyline = 1; polygon = 2), then the result's dimension will be equal to the lowest + /// dimension of the inputs. /// /// /// The input geometry. @@ -700,10 +862,13 @@ public async Task Intersect(IEnumerable geometries1, Geome /// /// The intersections of the geometries. /// + /// The cancellation token to use for the operation. [SerializedMethod] - public async Task Intersect(Geometry geometry1, Geometry geometry2) + public async Task Intersect(Geometry geometry1, Geometry geometry2, + CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(GeometryEngine), parameters: [geometry1, geometry2]); + return await InvokeAsync(nameof(ProjectionEngine), nameof(Intersect), + cancellationToken, geometry1, geometry2); } /// @@ -718,14 +883,18 @@ public async Task Intersect(Geometry geometry1, Geometry geometry2) /// /// Returns true if the input geometries intersect each other. /// + /// The cancellation token to use for the operation. [SerializedMethod] - public async Task Intersects(Geometry geometry1, Geometry geometry2) + public async Task Intersects(Geometry geometry1, Geometry geometry2, + CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(GeometryEngine), parameters: [geometry1, geometry2]); + return await InvokeAsync(nameof(ProjectionEngine), nameof(Intersects), + cancellationToken, geometry1, geometry2); } /// - /// Indicates if the given geometry is topologically simple. In a simplified geometry, no polygon rings or polyline paths will overlap, and no self-intersection will occur. + /// Indicates if the given geometry is topologically simple. In a simplified geometry, no polygon rings or polyline + /// paths will overlap, and no self-intersection will occur. /// /// /// The input geometry. @@ -733,10 +902,12 @@ public async Task Intersects(Geometry geometry1, Geometry geometry2) /// /// Returns true if the geometry is topologically simple. /// + /// The cancellation token to use for the operation. [SerializedMethod] - public async Task IsSimple(Geometry geometry) + public async Task IsSimple(Geometry geometry, CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(GeometryEngine), parameters: [geometry]); + return await InvokeAsync(nameof(ProjectionEngine), nameof(IsSimple), + cancellationToken, geometry); } /// @@ -751,10 +922,13 @@ public async Task IsSimple(Geometry geometry) /// /// Returns an object containing the nearest coordinate. /// + /// The cancellation token to use for the operation. [SerializedMethod] - public async Task NearestCoordinate(Geometry geometry, Point inputPoint) + public async Task NearestCoordinate(Geometry geometry, Point inputPoint, + CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(GeometryEngine), parameters: [geometry, inputPoint]); + return await InvokeAsync(nameof(ProjectionEngine), nameof(NearestCoordinate), + cancellationToken, geometry, inputPoint); } /// @@ -769,14 +943,18 @@ public async Task NearestCoordinate(Geometry geometry, Point /// /// Returns an object containing the nearest vertex. /// + /// The cancellation token to use for the operation. [SerializedMethod] - public async Task NearestVertex(Geometry geometry, Point inputPoint) + public async Task NearestVertex(Geometry geometry, Point inputPoint, + CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(GeometryEngine), parameters: [geometry, inputPoint]); + return await InvokeAsync(nameof(ProjectionEngine), nameof(NearestVertex), + cancellationToken, geometry, inputPoint); } /// - /// Finds all vertices in the given distance from the specified point, sorted from the closest to the furthest and returns them as an array of Objects. + /// Finds all vertices in the given distance from the specified point, sorted from the closest to the furthest and + /// returns them as an array of Objects. /// /// /// The geometry to consider. @@ -793,38 +971,53 @@ public async Task NearestVertex(Geometry geometry, Point inp /// /// An array of objects containing the nearest vertices within the given searchRadius. /// + /// The cancellation token to use for the operation. [SerializedMethod] - public async Task NearestVertices(Geometry geometry, Point inputPoint, double searchRadius, int maxVertexCountToReturn) + public async Task NearestVertices(Geometry geometry, Point inputPoint, double searchRadius, + int maxVertexCountToReturn, CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(GeometryEngine), parameters: [geometry, inputPoint, searchRadius, maxVertexCountToReturn]); + return await InvokeAsync(nameof(ProjectionEngine), nameof(NearestVertices), + cancellationToken, geometry, inputPoint, searchRadius, maxVertexCountToReturn); } /// - /// The offset operation creates a geometry that is a constant planar distance from an input polyline or polygon. It is similar to buffering, but produces a one-sided result. + /// The offset operation creates a geometry that is a constant planar distance from an input polyline or polygon. It is + /// similar to buffering, but produces a one-sided result. /// /// /// The geometries to offset. /// /// - /// The planar distance to offset from the input geometry. If offsetDistance > 0, then the offset geometry is constructed to the right of the oriented input geometry, if offsetDistance = 0, then there is no change in the geometries, otherwise it is constructed to the left. For a simple polygon, the orientation of outer rings is clockwise and for inner rings it is counter clockwise. So the "right side" of a simple polygon is always its inside. + /// The planar distance to offset from the input geometry. If offsetDistance > 0, then the offset geometry is + /// constructed to the right of the oriented input geometry, if offsetDistance = 0, then there is no change in the + /// geometries, otherwise it is constructed to the left. For a simple polygon, the orientation of outer rings is + /// clockwise and for inner rings it is counter clockwise. So the "right side" of a simple polygon is always its + /// inside. /// /// /// The offset geometries. /// - [SerializedMethod] - public async Task Offset(IEnumerable geometries, double offsetDistance) + /// The cancellation token to use for the operation. + public Task Offset(IEnumerable geometries, double offsetDistance, + CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(GeometryEngine), parameters: [geometries, offsetDistance, null, null, null, null]); + return Offset(geometries, offsetDistance, null, null, null, null, + cancellationToken); } /// - /// The offset operation creates a geometry that is a constant planar distance from an input polyline or polygon. It is similar to buffering, but produces a one-sided result. + /// The offset operation creates a geometry that is a constant planar distance from an input polyline or polygon. It is + /// similar to buffering, but produces a one-sided result. /// /// /// The geometries to offset. /// /// - /// The planar distance to offset from the input geometry. If offsetDistance > 0, then the offset geometry is constructed to the right of the oriented input geometry, if offsetDistance = 0, then there is no change in the geometries, otherwise it is constructed to the left. For a simple polygon, the orientation of outer rings is clockwise and for inner rings it is counter clockwise. So the "right side" of a simple polygon is always its inside. + /// The planar distance to offset from the input geometry. If offsetDistance > 0, then the offset geometry is + /// constructed to the right of the oriented input geometry, if offsetDistance = 0, then there is no change in the + /// geometries, otherwise it is constructed to the left. For a simple polygon, the orientation of outer rings is + /// clockwise and for inner rings it is counter clockwise. So the "right side" of a simple polygon is always its + /// inside. /// /// /// Measurement unit of the offset distance. Defaults to the units of the input geometries. @@ -832,21 +1025,27 @@ public async Task Offset(IEnumerable geometries, double of /// /// The offset geometries. /// - [SerializedMethod] - public async Task Offset(IEnumerable geometries, double offsetDistance, - GeometryEngineLinearUnit? offsetUnit) + /// The cancellation token to use for the operation. + public Task Offset(IEnumerable geometries, double offsetDistance, + GeometryEngineLinearUnit? offsetUnit, CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(GeometryEngine), parameters: [geometries, offsetDistance, offsetUnit, null, null, null]); + return Offset(geometries, offsetDistance, offsetUnit, null, null, null, + cancellationToken); } /// - /// The offset operation creates a geometry that is a constant planar distance from an input polyline or polygon. It is similar to buffering, but produces a one-sided result. + /// The offset operation creates a geometry that is a constant planar distance from an input polyline or polygon. It is + /// similar to buffering, but produces a one-sided result. /// /// /// The geometries to offset. /// /// - /// The planar distance to offset from the input geometry. If offsetDistance > 0, then the offset geometry is constructed to the right of the oriented input geometry, if offsetDistance = 0, then there is no change in the geometries, otherwise it is constructed to the left. For a simple polygon, the orientation of outer rings is clockwise and for inner rings it is counter clockwise. So the "right side" of a simple polygon is always its inside. + /// The planar distance to offset from the input geometry. If offsetDistance > 0, then the offset geometry is + /// constructed to the right of the oriented input geometry, if offsetDistance = 0, then there is no change in the + /// geometries, otherwise it is constructed to the left. For a simple polygon, the orientation of outer rings is + /// clockwise and for inner rings it is counter clockwise. So the "right side" of a simple polygon is always its + /// inside. /// /// /// Measurement unit of the offset distance. Defaults to the units of the input geometries. @@ -857,21 +1056,27 @@ public async Task Offset(IEnumerable geometries, double of /// /// The offset geometries. /// - [SerializedMethod] - public async Task Offset(IEnumerable geometries, double offsetDistance, - GeometryEngineLinearUnit? offsetUnit, JoinType? joinType) + /// The cancellation token to use for the operation. + public Task Offset(IEnumerable geometries, double offsetDistance, + GeometryEngineLinearUnit? offsetUnit, JoinType? joinType, CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(GeometryEngine), parameters: [geometries, offsetDistance, offsetUnit, joinType, null, null]); + return Offset(geometries, offsetDistance, offsetUnit, joinType, null, null, + cancellationToken); } /// - /// The offset operation creates a geometry that is a constant planar distance from an input polyline or polygon. It is similar to buffering, but produces a one-sided result. + /// The offset operation creates a geometry that is a constant planar distance from an input polyline or polygon. It is + /// similar to buffering, but produces a one-sided result. /// /// /// The geometries to offset. /// /// - /// The planar distance to offset from the input geometry. If offsetDistance > 0, then the offset geometry is constructed to the right of the oriented input geometry, if offsetDistance = 0, then there is no change in the geometries, otherwise it is constructed to the left. For a simple polygon, the orientation of outer rings is clockwise and for inner rings it is counter clockwise. So the "right side" of a simple polygon is always its inside. + /// The planar distance to offset from the input geometry. If offsetDistance > 0, then the offset geometry is + /// constructed to the right of the oriented input geometry, if offsetDistance = 0, then there is no change in the + /// geometries, otherwise it is constructed to the left. For a simple polygon, the orientation of outer rings is + /// clockwise and for inner rings it is counter clockwise. So the "right side" of a simple polygon is always its + /// inside. /// /// /// Measurement unit of the offset distance. Defaults to the units of the input geometries. @@ -880,76 +1085,100 @@ public async Task Offset(IEnumerable geometries, double of /// The /// /// - /// Applicable when joinType = 'miter'; bevelRatio is multiplied by the offset distance and the result determines how far a mitered offset intersection can be located before it is beveled. + /// Applicable when joinType = 'miter'; bevelRatio is multiplied by the offset distance and the result determines how + /// far a mitered offset intersection can be located before it is beveled. /// /// /// The offset geometries. /// - [SerializedMethod] - public async Task Offset(IEnumerable geometries, double offsetDistance, - GeometryEngineLinearUnit? offsetUnit, JoinType? joinType, double? bevelRatio) + /// The cancellation token to use for the operation. + public Task Offset(IEnumerable geometries, double offsetDistance, + GeometryEngineLinearUnit? offsetUnit, JoinType? joinType, double? bevelRatio, + CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(GeometryEngine), parameters: [geometries, offsetDistance, offsetUnit, joinType, bevelRatio, null]); + return Offset(geometries, offsetDistance, offsetUnit, joinType, bevelRatio, null, + cancellationToken); } /// - /// The offset operation creates a geometry that is a constant planar distance from an input polyline or polygon. It is similar to buffering, but produces a one-sided result. + /// The offset operation creates a geometry that is a constant planar distance from an input polyline or polygon. It is + /// similar to buffering, but produces a one-sided result. /// /// /// The geometries to offset. /// /// - /// The planar distance to offset from the input geometry. If offsetDistance > 0, then the offset geometry is constructed to the right of the oriented input geometry, if offsetDistance = 0, then there is no change in the geometries, otherwise it is constructed to the left. For a simple polygon, the orientation of outer rings is clockwise and for inner rings it is counter clockwise. So the "right side" of a simple polygon is always its inside. + /// The planar distance to offset from the input geometry. If offsetDistance > 0, then the offset geometry is + /// constructed to the right of the oriented input geometry, if offsetDistance = 0, then there is no change in the + /// geometries, otherwise it is constructed to the left. For a simple polygon, the orientation of outer rings is + /// clockwise and for inner rings it is counter clockwise. So the "right side" of a simple polygon is always its + /// inside. /// /// /// Measurement unit of the offset distance. Defaults to the units of the input geometries. /// /// - /// The + /// The /// /// - /// Applicable when joinType = 'miter'; bevelRatio is multiplied by the offset distance and the result determines how far a mitered offset intersection can be located before it is beveled. + /// Applicable when joinType = 'miter'; bevelRatio is multiplied by the offset distance and the result determines how + /// far a mitered offset intersection can be located before it is beveled. /// /// - /// Applicable when joinType = 'round'; flattenError determines the maximum distance of the resulting segments compared to the true circular arc. The algorithm never produces more than around 180 vertices for each round join. + /// Applicable when joinType = 'round'; flattenError determines the maximum distance of the resulting segments compared + /// to the true circular arc. The algorithm never produces more than around 180 vertices for each round join. /// /// /// The offset geometries. /// + /// The cancellation token to use for the operation. [SerializedMethod] public async Task Offset(IEnumerable geometries, double offsetDistance, GeometryEngineLinearUnit? offsetUnit, JoinType? joinType, double? bevelRatio, - double? flattenError) + double? flattenError, CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(GeometryEngine), parameters: [geometries, offsetDistance, offsetUnit, joinType, bevelRatio, flattenError]); + return await InvokeAsync(nameof(ProjectionEngine), nameof(Offset), + cancellationToken, geometries, offsetDistance, offsetUnit, joinType, bevelRatio, flattenError); } /// - /// The offset operation creates a geometry that is a constant planar distance from an input polyline or polygon. It is similar to buffering, but produces a one-sided result. + /// The offset operation creates a geometry that is a constant planar distance from an input polyline or polygon. It is + /// similar to buffering, but produces a one-sided result. /// /// /// The geometry to offset. /// /// - /// The planar distance to offset from the input geometry. If offsetDistance > 0, then the offset geometry is constructed to the right of the oriented input geometry, if offsetDistance = 0, then there is no change in the geometries, otherwise it is constructed to the left. For a simple polygon, the orientation of outer rings is clockwise and for inner rings it is counter clockwise. So the "right side" of a simple polygon is always its inside. + /// The planar distance to offset from the input geometry. If offsetDistance > 0, then the offset geometry is + /// constructed to the right of the oriented input geometry, if offsetDistance = 0, then there is no change in the + /// geometries, otherwise it is constructed to the left. For a simple polygon, the orientation of outer rings is + /// clockwise and for inner rings it is counter clockwise. So the "right side" of a simple polygon is always its + /// inside. /// /// /// The offset geometry. /// - [SerializedMethod] - public async Task Offset(Geometry geometry, double offsetDistance) + /// The cancellation token to use for the operation. + public Task Offset(Geometry geometry, double offsetDistance, + CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(GeometryEngine), parameters: [geometry, offsetDistance, null, null, null, null]); + return Offset(geometry, offsetDistance, null, null, null, null, + cancellationToken); } /// - /// The offset operation creates a geometry that is a constant planar distance from an input polyline or polygon. It is similar to buffering, but produces a one-sided result. + /// The offset operation creates a geometry that is a constant planar distance from an input polyline or polygon. It is + /// similar to buffering, but produces a one-sided result. /// /// /// The geometry to offset. /// /// - /// The planar distance to offset from the input geometry. If offsetDistance > 0, then the offset geometry is constructed to the right of the oriented input geometry, if offsetDistance = 0, then there is no change in the geometries, otherwise it is constructed to the left. For a simple polygon, the orientation of outer rings is clockwise and for inner rings it is counter clockwise. So the "right side" of a simple polygon is always its inside. + /// The planar distance to offset from the input geometry. If offsetDistance > 0, then the offset geometry is + /// constructed to the right of the oriented input geometry, if offsetDistance = 0, then there is no change in the + /// geometries, otherwise it is constructed to the left. For a simple polygon, the orientation of outer rings is + /// clockwise and for inner rings it is counter clockwise. So the "right side" of a simple polygon is always its + /// inside. /// /// /// Measurement unit of the offset distance. Defaults to the units of the input geometries. @@ -957,21 +1186,27 @@ public async Task Offset(Geometry geometry, double offsetDistance) /// /// The offset geometry. /// - [SerializedMethod] - public async Task Offset(Geometry geometry, double offsetDistance, - GeometryEngineLinearUnit? offsetUnit) + /// The cancellation token to use for the operation. + public Task Offset(Geometry geometry, double offsetDistance, + GeometryEngineLinearUnit? offsetUnit, CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(GeometryEngine), parameters: [geometry, offsetDistance, offsetUnit, null, null, null]); + return Offset(geometry, offsetDistance, offsetUnit, null, null, null, + cancellationToken); } /// - /// The offset operation creates a geometry that is a constant planar distance from an input polyline or polygon. It is similar to buffering, but produces a one-sided result. + /// The offset operation creates a geometry that is a constant planar distance from an input polyline or polygon. It is + /// similar to buffering, but produces a one-sided result. /// /// /// The geometry to offset. /// /// - /// The planar distance to offset from the input geometry. If offsetDistance > 0, then the offset geometry is constructed to the right of the oriented input geometry, if offsetDistance = 0, then there is no change in the geometries, otherwise it is constructed to the left. For a simple polygon, the orientation of outer rings is clockwise and for inner rings it is counter clockwise. So the "right side" of a simple polygon is always its inside. + /// The planar distance to offset from the input geometry. If offsetDistance > 0, then the offset geometry is + /// constructed to the right of the oriented input geometry, if offsetDistance = 0, then there is no change in the + /// geometries, otherwise it is constructed to the left. For a simple polygon, the orientation of outer rings is + /// clockwise and for inner rings it is counter clockwise. So the "right side" of a simple polygon is always its + /// inside. /// /// /// Measurement unit of the offset distance. Defaults to the units of the input geometries. @@ -982,21 +1217,27 @@ public async Task Offset(Geometry geometry, double offsetDistance, /// /// The offset geometry. /// - [SerializedMethod] - public async Task Offset(Geometry geometry, double offsetDistance, - GeometryEngineLinearUnit? offsetUnit, JoinType? joinType) + /// The cancellation token to use for the operation. + public Task Offset(Geometry geometry, double offsetDistance, + GeometryEngineLinearUnit? offsetUnit, JoinType? joinType, CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(GeometryEngine), parameters: [geometry, offsetDistance, offsetUnit, joinType, null, null]); + return Offset(geometry, offsetDistance, offsetUnit, joinType, null, null, + cancellationToken); } /// - /// The offset operation creates a geometry that is a constant planar distance from an input polyline or polygon. It is similar to buffering, but produces a one-sided result. + /// The offset operation creates a geometry that is a constant planar distance from an input polyline or polygon. It is + /// similar to buffering, but produces a one-sided result. /// /// /// The geometry to offset. /// /// - /// The planar distance to offset from the input geometry. If offsetDistance > 0, then the offset geometry is constructed to the right of the oriented input geometry, if offsetDistance = 0, then there is no change in the geometries, otherwise it is constructed to the left. For a simple polygon, the orientation of outer rings is clockwise and for inner rings it is counter clockwise. So the "right side" of a simple polygon is always its inside. + /// The planar distance to offset from the input geometry. If offsetDistance > 0, then the offset geometry is + /// constructed to the right of the oriented input geometry, if offsetDistance = 0, then there is no change in the + /// geometries, otherwise it is constructed to the left. For a simple polygon, the orientation of outer rings is + /// clockwise and for inner rings it is counter clockwise. So the "right side" of a simple polygon is always its + /// inside. /// /// /// Measurement unit of the offset distance. Defaults to the units of the input geometries. @@ -1005,48 +1246,60 @@ public async Task Offset(Geometry geometry, double offsetDistance, /// The /// /// - /// Applicable when joinType = 'miter'; bevelRatio is multiplied by the offset distance and the result determines how far a mitered offset intersection can be located before it is beveled. + /// Applicable when joinType = 'miter'; bevelRatio is multiplied by the offset distance and the result determines how + /// far a mitered offset intersection can be located before it is beveled. /// /// /// The offset geometry. /// - [SerializedMethod] - public async Task Offset(Geometry geometry, double offsetDistance, - GeometryEngineLinearUnit? offsetUnit, JoinType? joinType, double? bevelRatio) + /// The cancellation token to use for the operation. + public Task Offset(Geometry geometry, double offsetDistance, + GeometryEngineLinearUnit? offsetUnit, JoinType? joinType, double? bevelRatio, + CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(GeometryEngine), parameters: [geometry, offsetDistance, offsetUnit, joinType, bevelRatio, null]); + return Offset(geometry, offsetDistance, offsetUnit, joinType, bevelRatio, null, + cancellationToken); } /// - /// The offset operation creates a geometry that is a constant planar distance from an input polyline or polygon. It is similar to buffering, but produces a one-sided result. + /// The offset operation creates a geometry that is a constant planar distance from an input polyline or polygon. It is + /// similar to buffering, but produces a one-sided result. /// /// /// The geometry to offset. /// /// - /// The planar distance to offset from the input geometry. If offsetDistance > 0, then the offset geometry is constructed to the right of the oriented input geometry, if offsetDistance = 0, then there is no change in the geometries, otherwise it is constructed to the left. For a simple polygon, the orientation of outer rings is clockwise and for inner rings it is counter clockwise. So the "right side" of a simple polygon is always its inside. + /// The planar distance to offset from the input geometry. If offsetDistance > 0, then the offset geometry is + /// constructed to the right of the oriented input geometry, if offsetDistance = 0, then there is no change in the + /// geometries, otherwise it is constructed to the left. For a simple polygon, the orientation of outer rings is + /// clockwise and for inner rings it is counter clockwise. So the "right side" of a simple polygon is always its + /// inside. /// /// /// Measurement unit of the offset distance. Defaults to the units of the input geometries. /// /// - /// The + /// The /// /// - /// Applicable when joinType = 'miter'; bevelRatio is multiplied by the offset distance and the result determines how far a mitered offset intersection can be located before it is beveled. + /// Applicable when joinType = 'miter'; bevelRatio is multiplied by the offset distance and the result determines how + /// far a mitered offset intersection can be located before it is beveled. /// /// - /// Applicable when joinType = 'round'; flattenError determines the maximum distance of the resulting segments compared to the true circular arc. The algorithm never produces more than around 180 vertices for each round join. + /// Applicable when joinType = 'round'; flattenError determines the maximum distance of the resulting segments compared + /// to the true circular arc. The algorithm never produces more than around 180 vertices for each round join. /// /// /// The offset geometry. /// + /// The cancellation token to use for the operation. [SerializedMethod] public async Task Offset(Geometry geometry, double offsetDistance, GeometryEngineLinearUnit? offsetUnit, JoinType? joinType, double? bevelRatio, - double? flattenError) + double? flattenError, CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(GeometryEngine), parameters: [geometry, offsetDistance, offsetUnit, joinType, bevelRatio, flattenError]); + return await InvokeAsync(nameof(ProjectionEngine), nameof(Offset), + cancellationToken, geometry, offsetDistance, offsetUnit, joinType, bevelRatio, flattenError); } /// @@ -1061,26 +1314,37 @@ public async Task Offset(Geometry geometry, double offsetDistance, /// /// Returns true if the two geometries overlap. /// + /// The cancellation token to use for the operation. [SerializedMethod] - public async Task Overlaps(Geometry geometry1, Geometry geometry2) + public async Task Overlaps(Geometry geometry1, Geometry geometry2, + CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(GeometryEngine), parameters: [geometry1, geometry2]); + return await InvokeAsync(nameof(ProjectionEngine), nameof(Overlaps), + cancellationToken, geometry1, geometry2); } /// - /// Calculates the area of the input geometry. As opposed to geodesicArea(), planarArea() performs this calculation using projected coordinates and does not take into account the earth's curvature. When using input geometries with a spatial reference of either WGS84 (wkid: 4326) or Web Mercator, it is best practice to calculate areas using geodesicArea(). If the input geometries have a projected coordinate system other than Web Mercator, use planarArea() instead. + /// Calculates the area of the input geometry. As opposed to geodesicArea(), planarArea() performs this calculation + /// using projected coordinates and does not take into account the earth's curvature. When using input geometries with + /// a spatial reference of either WGS84 (wkid: 4326) or Web Mercator, it is best practice to calculate areas using + /// geodesicArea(). If the input geometries have a projected coordinate system other than Web Mercator, use + /// planarArea() instead. /// /// /// The input polygon. /// - [SerializedMethod] - public async Task PlanarArea(Polygon geometry) + /// The cancellation token to use for the operation. + public Task PlanarArea(Polygon geometry, CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(GeometryEngine), parameters: [geometry, null]); + return PlanarArea(geometry, null, cancellationToken); } - + /// - /// Calculates the area of the input geometry. As opposed to geodesicArea(), planarArea() performs this calculation using projected coordinates and does not take into account the earth's curvature. When using input geometries with a spatial reference of either WGS84 (wkid: 4326) or Web Mercator, it is best practice to calculate areas using geodesicArea(). If the input geometries have a projected coordinate system other than Web Mercator, use planarArea() instead. + /// Calculates the area of the input geometry. As opposed to geodesicArea(), planarArea() performs this calculation + /// using projected coordinates and does not take into account the earth's curvature. When using input geometries with + /// a spatial reference of either WGS84 (wkid: 4326) or Web Mercator, it is best practice to calculate areas using + /// geodesicArea(). If the input geometries have a projected coordinate system other than Web Mercator, use + /// planarArea() instead. /// /// /// The input polygon. @@ -1091,14 +1355,21 @@ public async Task PlanarArea(Polygon geometry) /// /// The area of the input geometry. /// + /// The cancellation token to use for the operation. [SerializedMethod] - public async Task PlanarArea(Polygon geometry, GeometryEngineAreaUnit? unit) + public async Task PlanarArea(Polygon geometry, GeometryEngineAreaUnit? unit, + CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(GeometryEngine), parameters: [geometry, unit]); + return await InvokeAsync(nameof(ProjectionEngine), nameof(PlanarArea), + cancellationToken, geometry, unit); } /// - /// Calculates the length of the input geometry. As opposed to geodesicLength(), planarLength() uses projected coordinates and does not take into account the curvature of the earth when performing this calculation. When using input geometries with a spatial reference of either WGS84 (wkid: 4326) or Web Mercator, it is best practice to calculate lengths using geodesicLength(). If the input geometries have a projected coordinate system other than Web Mercator, use planarLength() instead. + /// Calculates the length of the input geometry. As opposed to geodesicLength(), planarLength() uses projected + /// coordinates and does not take into account the curvature of the earth when performing this calculation. When using + /// input geometries with a spatial reference of either WGS84 (wkid: 4326) or Web Mercator, it is best practice to + /// calculate lengths using geodesicLength(). If the input geometries have a projected coordinate system other than Web + /// Mercator, use planarLength() instead. /// /// /// The input geometry. @@ -1106,14 +1377,20 @@ public async Task PlanarArea(Polygon geometry, GeometryEngineAreaUnit? u /// /// The length of the input geometry. /// + /// The cancellation token to use for the operation. [SerializedMethod] - public async Task PlanarLength(Geometry geometry) + public async Task PlanarLength(Geometry geometry, CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(GeometryEngine), parameters: [geometry]); + return await InvokeAsync(nameof(ProjectionEngine), nameof(PlanarLength), + cancellationToken, geometry); } - + /// - /// Calculates the length of the input geometry. As opposed to geodesicLength(), planarLength() uses projected coordinates and does not take into account the curvature of the earth when performing this calculation. When using input geometries with a spatial reference of either WGS84 (wkid: 4326) or Web Mercator, it is best practice to calculate lengths using geodesicLength(). If the input geometries have a projected coordinate system other than Web Mercator, use planarLength() instead. + /// Calculates the length of the input geometry. As opposed to geodesicLength(), planarLength() uses projected + /// coordinates and does not take into account the curvature of the earth when performing this calculation. When using + /// input geometries with a spatial reference of either WGS84 (wkid: 4326) or Web Mercator, it is best practice to + /// calculate lengths using geodesicLength(). If the input geometries have a projected coordinate system other than Web + /// Mercator, use planarLength() instead. /// /// /// The input geometry. @@ -1124,10 +1401,13 @@ public async Task PlanarLength(Geometry geometry) /// /// The length of the input geometry. /// + /// The cancellation token to use for the operation. [SerializedMethod] - public async Task PlanarLength(Geometry geometry, GeometryEngineLinearUnit? unit) + public async Task PlanarLength(Geometry geometry, GeometryEngineLinearUnit? unit, + CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(GeometryEngine), parameters: [geometry, unit]); + return await InvokeAsync(nameof(ProjectionEngine), nameof(PlanarLength), + cancellationToken, geometry, unit); } /// @@ -1140,9 +1420,16 @@ public async Task PlanarLength(Geometry geometry, GeometryEngineLinearUn /// The second geometry for the relation. /// /// - /// The Dimensionally Extended 9 Intersection Model (DE-9IM) matrix relation (encoded as a string) to test against the relationship of the two geometries. This string contains the test result of each intersection represented in the DE-9IM matrix. Each result is one character of the string and may be represented as either a number (maximum dimension returned: 0,1,2), a Boolean value (T or F), or a mask character (for ignoring results: '*'). For example, each of the following DE-9IM string codes are valid for testing whether a polygon geometry completely contains a line geometry: TTTFFTFFT (Boolean), 'T******FF*' (ignore irrelevant intersections), or '102FF*FF*' (dimension form). Each returns the same result. See + /// The Dimensionally Extended 9 Intersection Model (DE-9IM) matrix relation (encoded as a string) to test against the + /// relationship of the two geometries. This string contains the test result of each intersection represented in the + /// DE-9IM matrix. Each result is one character of the string and may be represented as either a number (maximum + /// dimension returned: 0,1,2), a Boolean value (T or F), or a mask character (for ignoring results: '*'). For example, + /// each of the following DE-9IM string codes are valid for testing whether a polygon geometry completely contains a + /// line geometry: TTTFFTFFT (Boolean), 'T******FF*' (ignore irrelevant intersections), or '102FF*FF*' (dimension + /// form). Each returns the same result. See /// this article and - /// + /// /// this /// ArcGIS help page /// @@ -1151,14 +1438,18 @@ public async Task PlanarLength(Geometry geometry, GeometryEngineLinearUn /// /// Returns true if the relation of the input geometries is accurate. /// + /// The cancellation token to use for the operation. [SerializedMethod] - public async Task Relate(Geometry geometry1, Geometry geometry2, string relation) + public async Task Relate(Geometry geometry1, Geometry geometry2, string relation, + CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(GeometryEngine), parameters: [geometry1, geometry2, relation]); + return await InvokeAsync(nameof(ProjectionEngine), nameof(Relate), + cancellationToken, geometry1, geometry2, relation); } /// - /// Rotates a geometry counterclockwise by the specified number of degrees. Rotation is around the centroid, or a given rotation point. + /// Rotates a geometry counterclockwise by the specified number of degrees. Rotation is around the centroid, or a given + /// rotation point. /// /// /// The geometry to rotate. @@ -1172,14 +1463,19 @@ public async Task Relate(Geometry geometry1, Geometry geometry2, string re /// /// The rotated geometry. /// + /// The cancellation token to use for the operation. [SerializedMethod] - public async Task Rotate(Geometry geometry, double angle, Point rotationOrigin) + public async Task Rotate(Geometry geometry, double angle, Point rotationOrigin, + CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(GeometryEngine), parameters: [geometry, angle, rotationOrigin]); + return await InvokeAsync(nameof(ProjectionEngine), nameof(Rotate), + cancellationToken, geometry, angle, rotationOrigin); } /// - /// Performs the simplify operation on the geometry, which alters the given geometries to make their definitions topologically legal with respect to their geometry type. At the end of a simplify operation, no polygon rings or polyline paths will overlap, and no self-intersection will occur. + /// Performs the simplify operation on the geometry, which alters the given geometries to make their definitions + /// topologically legal with respect to their geometry type. At the end of a simplify operation, no polygon rings or + /// polyline paths will overlap, and no self-intersection will occur. /// /// /// The geometry to be simplified. @@ -1187,14 +1483,17 @@ public async Task Rotate(Geometry geometry, double angle, Point rotati /// /// The simplified geometry. /// + /// The cancellation token to use for the operation. [SerializedMethod] - public async Task Simplify(Geometry geometry) + public async Task Simplify(Geometry geometry, CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(GeometryEngine), parameters: [geometry]); + return await InvokeAsync(nameof(ProjectionEngine), nameof(Simplify), + cancellationToken, geometry); } /// - /// Creates the symmetric difference of two geometries. The symmetric difference includes the parts that are in either of the sets, but not in both. + /// Creates the symmetric difference of two geometries. The symmetric difference includes the parts that are in either + /// of the sets, but not in both. /// /// /// One of the Geometry instances in the XOR operation. @@ -1205,14 +1504,18 @@ public async Task Simplify(Geometry geometry) /// /// The symmetric differences of the two geometries. /// + /// The cancellation token to use for the operation. [SerializedMethod] - public async Task SymmetricDifference(IEnumerable leftGeometries, Geometry rightGeometry) + public async Task SymmetricDifference(IEnumerable leftGeometries, Geometry rightGeometry, + CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(GeometryEngine), parameters: [leftGeometries, rightGeometry]); + return await InvokeAsync(nameof(ProjectionEngine), nameof(SymmetricDifference), + cancellationToken, leftGeometries, rightGeometry); } /// - /// Creates the symmetric difference of two geometries. The symmetric difference includes the parts that are in either of the sets, but not in both. + /// Creates the symmetric difference of two geometries. The symmetric difference includes the parts that are in either + /// of the sets, but not in both. /// /// /// One of the Geometry instances in the XOR operation. @@ -1223,10 +1526,13 @@ public async Task SymmetricDifference(IEnumerable leftGeom /// /// The symmetric differences of the two geometries. /// + /// The cancellation token to use for the operation. [SerializedMethod] - public async Task SymmetricDifference(Geometry leftGeometry, Geometry rightGeometry) + public async Task SymmetricDifference(Geometry leftGeometry, Geometry rightGeometry, + CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(GeometryEngine), parameters: [leftGeometry, rightGeometry]); + return await InvokeAsync(nameof(ProjectionEngine), nameof(SymmetricDifference), + cancellationToken, leftGeometry, rightGeometry); } /// @@ -1241,10 +1547,13 @@ public async Task SymmetricDifference(Geometry leftGeometry, Geometry /// /// When true, geometry1 touches geometry2. /// + /// The cancellation token to use for the operation. [SerializedMethod] - public async Task Touches(Geometry geometry1, Geometry geometry2) + public async Task Touches(Geometry geometry1, Geometry geometry2, + CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(GeometryEngine), parameters: [geometry1, geometry2]); + return await InvokeAsync(nameof(ProjectionEngine), nameof(Touches), + cancellationToken, geometry1, geometry2); } /// @@ -1256,10 +1565,9 @@ public async Task Touches(Geometry geometry1, Geometry geometry2) /// /// The union of the geometries /// - [SerializedMethod] - public async Task Union(params Geometry[] geometries) + public Task Union(params Geometry[] geometries) { - return await InvokeAsync(nameof(GeometryEngine), parameters: [geometries.Cast()]); + return Union(geometries, CancellationToken.None); } /// @@ -1271,10 +1579,12 @@ public async Task Union(params Geometry[] geometries) /// /// The union of the geometries /// + /// The cancellation token to use for the operation. [SerializedMethod] - public async Task Union(IEnumerable geometries) + public async Task Union(IEnumerable geometries, CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(GeometryEngine), parameters: [geometries.Cast()]); + return await InvokeAsync(nameof(ProjectionEngine), nameof(Union), + cancellationToken, geometries.Cast()); } /// @@ -1289,55 +1599,89 @@ public async Task Union(IEnumerable geometries) /// /// Returns true if innerGeometry is within outerGeometry. /// + /// The cancellation token to use for the operation. [SerializedMethod] - public async Task Within(Geometry innerGeometry, Geometry outerGeometry) + public async Task Within(Geometry innerGeometry, Geometry outerGeometry, + CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(GeometryEngine), parameters: [innerGeometry, outerGeometry]); + return await InvokeAsync(nameof(ProjectionEngine), nameof(Within), + cancellationToken, innerGeometry, outerGeometry); } /// - /// Creates a new instance of this class and initializes it with values from a JSON object generated from an ArcGIS product. The object passed into the input json parameter often comes from a response to a query operation in the REST API or a toJSON() method from another ArcGIS product. See the Using fromJSON() topic in the Guide for details and examples of when and how to use this function. + /// Creates a new instance of this class and initializes it with values from a JSON object generated from an ArcGIS + /// product. The object passed into the input json parameter often comes from a response to a query operation in the + /// REST API or a toJSON() method from another ArcGIS product. See the + /// + /// Using + /// fromJSON() + /// + /// topic in the Guide for details and examples of when and how to use this function. /// /// - /// A JSON representation of the instance in the ArcGIS format. See the ArcGIS REST API documentation for examples of the structure of various input JSON objects. + /// A JSON representation of the instance in the ArcGIS format. See the + /// + /// ArcGIS + /// REST API documentation + /// + /// for examples of the structure of various input JSON objects. /// /// /// Returns a new geometry instance. /// + /// The cancellation token to use for the operation. [SerializedMethod] - public async Task FromArcGisJson(string json) + public async Task FromArcGisJson(string json, CancellationToken cancellationToken = default) where T : Geometry { - return await InvokeAsync(nameof(GeometryEngine), parameters: [json, typeof(T).Name]); + return await InvokeAsync(nameof(ProjectionEngine), nameof(FromArcGisJson), + cancellationToken, json, typeof(T).Name); } /// - /// Converts an instance of this class to its ArcGIS portal JSON representation. See the Using fromJSON() guide topic for more information. + /// Converts an instance of this class to its ArcGIS portal JSON representation. See the + /// + /// Using + /// fromJSON() + /// + /// guide topic for more information. /// /// /// The geometry to convert. /// /// - /// The ArcGIS portal JSON representation of an instance of this class. + /// The + /// + /// ArcGIS + /// portal JSON + /// + /// representation of an instance of this class. /// + /// The cancellation token to use for the operation. [SerializedMethod] - public async Task ToArcGisJson(T geometry) + public async Task ToArcGisJson(T geometry, CancellationToken cancellationToken = default) where T : Geometry { - return await InvokeAsync(nameof(GeometryEngine), parameters: [geometry]); + return await InvokeAsync(nameof(ProjectionEngine), nameof(ToArcGisJson), + cancellationToken, geometry); } /// /// Creates a deep clone of the geometry. /// /// - /// Unlike the Clone methods in the Geometry classes, this method does a loop through the ArcGIS JS SDK. Therefore, if you are having issues with unpopulated fields in the geometry, try using this method instead. + /// Unlike the Clone methods in the Geometry classes, this method does a loop through the ArcGIS JS SDK. Therefore, if + /// you are having issues with unpopulated fields in the geometry, try using this method instead. /// + /// The geometry to clone + /// The cancellation token to use for the operation. [SerializedMethod] - public async Task Clone(T geometry) + public async Task Clone(T geometry, CancellationToken cancellationToken = default) where T : Geometry { - return await InvokeAsync(nameof(GeometryEngine), parameters: [geometry]); + return await InvokeAsync(nameof(ProjectionEngine), nameof(Clone), + cancellationToken, geometry); } /// @@ -1352,14 +1696,17 @@ public async Task Clone(T geometry) /// /// The centered extent. /// + /// The cancellation token to use for the operation. [SerializedMethod] - public async Task CenterExtentAt(Extent extent, Point point) + public async Task CenterExtentAt(Extent extent, Point point, CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(GeometryEngine), parameters: [extent, point]); + return await InvokeAsync(nameof(ProjectionEngine), nameof(CenterExtentAt), + cancellationToken, extent, point); } /// - /// Expands the extent by the given factor. For example, a value of 1.5 will expand the extent to be 50 percent larger than the original extent. + /// Expands the extent by the given factor. For example, a value of 1.5 will expand the extent to be 50 percent larger + /// than the original extent. /// /// /// The input extent. @@ -1370,25 +1717,31 @@ public async Task CenterExtentAt(Extent extent, Point point) /// /// The expanded extent. /// + /// The cancellation token to use for the operation. [SerializedMethod] - public async Task Expand(Extent extent, double factor) + public async Task Expand(Extent extent, double factor, CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(GeometryEngine), parameters: [extent, factor]); + return await InvokeAsync(nameof(ProjectionEngine), nameof(Expand), + cancellationToken, extent, factor); } /// - /// Returns an array with either one Extent that's been shifted to within +/- 180 or two Extents if the original extent intersects the International Dateline. + /// Returns an array with either one Extent that's been shifted to within +/- 180 or two Extents if the original extent + /// intersects the International Dateline. /// /// /// The input extent. /// /// - /// An array with either one Extent that's been shifted to within +/- 180 or two Extents if the original extent intersects the International Dateline. + /// An array with either one Extent that's been shifted to within +/- 180 or two Extents if the original extent + /// intersects the International Dateline. /// + /// The cancellation token to use for the operation. [SerializedMethod] - public async Task NormalizeExtent(Extent extent) + public async Task NormalizeExtent(Extent extent, CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(GeometryEngine), parameters: [extent]); + return await InvokeAsync(nameof(ProjectionEngine), nameof(NormalizeExtent), + cancellationToken, extent); } /// @@ -1407,10 +1760,13 @@ public async Task NormalizeExtent(Extent extent) /// The offset distance in map units for the Z-coordinate. /// /// + /// The cancellation token to use for the operation. [SerializedMethod] - public async Task OffsetExtent(Extent extent, double dx, double dy, double dz = 0) + public async Task OffsetExtent(Extent extent, double dx, double dy, double dz = 0, + CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(GeometryEngine), parameters: [extent, dx, dy, dz]); + return await InvokeAsync(nameof(ProjectionEngine), nameof(OffsetExtent), + cancellationToken, extent, dx, dy, dz); } /// @@ -1422,10 +1778,12 @@ public async Task OffsetExtent(Extent extent, double dx, double dy, doub /// /// Returns a point with a normalized x-value. /// + /// The cancellation token to use for the operation. [SerializedMethod] - public async Task NormalizePoint(Point point) + public async Task NormalizePoint(Point point, CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(GeometryEngine), parameters: [point]); + return await InvokeAsync(nameof(ProjectionEngine), nameof(NormalizePoint), + cancellationToken, point); } /// @@ -1435,15 +1793,18 @@ public async Task NormalizePoint(Point point) /// The polyline to add the path to. Will return a new modified copy. /// /// - /// The polyline path to add as a . + /// The polyline path to add as a . /// /// /// Returns a new polyline with the added path. /// + /// The cancellation token to use for the operation. [SerializedMethod] - public async Task AddPath(Polyline polyline, MapPath points) + public async Task AddPath(Polyline polyline, MapPath points, + CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(GeometryEngine), parameters: [polyline, points]); + return await InvokeAsync(nameof(ProjectionEngine), nameof(AddPath), + cancellationToken, polyline, points); } /// @@ -1453,21 +1814,24 @@ public async Task AddPath(Polyline polyline, MapPath points) /// The polyline to add the path to. Will return a new modified copy. /// /// - /// The polyline path to add as an array of s. + /// The polyline path to add as an array of s. /// /// /// Returns a new polyline with the added path. /// + /// The cancellation token to use for the operation. [SerializedMethod] - public async Task AddPath(Polyline polyline, Point[] points) + public async Task AddPath(Polyline polyline, Point[] points, + CancellationToken cancellationToken = default) { var mapPoints = new List(); - foreach (Point p in points) + + foreach (var p in points) { mapPoints.Add(new MapPoint(p.X ?? p.Longitude!.Value, p.Y ?? p.Latitude!.Value)); } - return await AddPath(polyline, new MapPath(mapPoints)); + return await AddPath(polyline, new MapPath(mapPoints), cancellationToken); } /// @@ -1485,10 +1849,13 @@ public async Task AddPath(Polyline polyline, Point[] points) /// /// Returns the point along the Polyline located in the given path and point indices. /// + /// The cancellation token to use for the operation. [SerializedMethod] - public async Task GetPoint(Polyline polyline, int pathIndex, int pointIndex) + public async Task GetPoint(Polyline polyline, int pathIndex, int pointIndex, + CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(GeometryEngine), parameters: [polyline, pathIndex, pointIndex]); + return await InvokeAsync(nameof(GeometryEngine), nameof(GetPoint), + cancellationToken, polyline, pathIndex, pointIndex); } /// @@ -1509,10 +1876,13 @@ public async Task GetPoint(Polyline polyline, int pathIndex, int pointInd /// /// Returns a new polyline with the inserted point. /// + /// The cancellation token to use for the operation. [SerializedMethod] - public async Task InsertPoint(Polyline polyline, int pathIndex, int pointIndex, Point point) + public async Task InsertPoint(Polyline polyline, int pathIndex, int pointIndex, Point point, + CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(GeometryEngine), parameters: [polyline, pathIndex, pointIndex, point]); + return await InvokeAsync(nameof(ProjectionEngine), nameof(InsertPoint), + cancellationToken, polyline, pathIndex, pointIndex, point); } /// @@ -1524,17 +1894,24 @@ public async Task InsertPoint(Polyline polyline, int pathIndex, int po /// /// The index of the path to remove. /// + /// The cancellation token to use for the operation. /// /// Returns an object with the modified polyline and the removed path. /// - public async Task<(Polyline PolyLine, Point[] Path)> RemovePath(Polyline polyline, int index) + [SerializedMethod] + public async Task<(Polyline PolyLine, Point[] Path)> RemovePath(Polyline polyline, int index, + CancellationToken cancellationToken = default) { - // DON'T ADD [SerializedMethod], doesn't work here - // TODO: Refactor API for V5 - GeometryRemovePathResult result = - await InvokeAsync(nameof(GeometryEngine), parameters: [polyline, index]); - - return ((Polyline)result.Geometry, result.Path); + var result = + await InvokeAsync(nameof(ProjectionEngine), nameof(RemovePath), + cancellationToken, polyline, index); + + if (result.Length < 3) // need at least two points for the path + { + throw new InvalidOperationException($"No path found at index {index} on polyline"); + } + + return ((Polyline)result[0], result.Skip(1).Cast().ToArray()); } /// @@ -1549,18 +1926,25 @@ public async Task InsertPoint(Polyline polyline, int pathIndex, int po /// /// The index of the point in the path to remove. /// + /// The cancellation token to use for the operation. /// /// Returns an object with the modified polyline and the removed point. /// - public async Task<(Polyline Polyline, Point Point)> RemovePoint(Polyline polyline, int pathIndex, int pointIndex) + [SerializedMethod] + public async Task<(Polyline Polyline, Point Point)> RemovePoint(Polyline polyline, int pathIndex, int pointIndex, + CancellationToken cancellationToken = default) { - // DON'T ADD [SerializedMethod], doesn't work here - // TODO: Refactor API for V5 - GeometryRemovePointResult result = - await InvokeAsync(nameof(GeometryEngine), - parameters: [polyline, pathIndex, pointIndex]); + var result = + await InvokeAsync(nameof(ProjectionEngine), nameof(RemovePoint), + cancellationToken, polyline, pathIndex, pointIndex); - return ((Polyline)result.Geometry, result.Point); + if (result.Length < 2) + { + throw new InvalidOperationException($"No point found at path index {pathIndex} and point index {pointIndex + } on polyline"); + } + + return ((Polyline)result[0], (Point)result[1]); } /// @@ -1581,14 +1965,18 @@ await InvokeAsync(nameof(GeometryEngine), /// /// Returns a new polyline with the updated point. /// + /// The cancellation token to use for the operation. [SerializedMethod] - public async Task SetPoint(Polyline polyline, int pathIndex, int pointIndex, Point point) + public async Task SetPoint(Polyline polyline, int pathIndex, int pointIndex, Point point, + CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(GeometryEngine), parameters: [polyline, pathIndex, pointIndex, point]); + return await InvokeAsync(nameof(GeometryEngine), nameof(SetPoint), + cancellationToken, polyline, pathIndex, pointIndex, point); } /// - /// Adds a ring to the Polygon. The ring can be one of the following: an array of numbers or an array of points. When added the index of the ring is incremented by one. + /// Adds a ring to the Polygon. The ring can be one of the following: an array of numbers or an array of points. When + /// added the index of the ring is incremented by one. /// /// /// The polygon to add the ring to. @@ -1599,14 +1987,17 @@ public async Task SetPoint(Polyline polyline, int pathIndex, int point /// /// Returns a new polygon with the added ring. /// + /// The cancellation token to use for the operation. [SerializedMethod] - public async Task AddRing(Polygon polygon, MapPath points) + public async Task AddRing(Polygon polygon, MapPath points, CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(GeometryEngine), parameters: [polygon, points]); + return await InvokeAsync(nameof(GeometryEngine), nameof(AddRing), + cancellationToken, polygon, points); } /// - /// Adds a ring to the Polygon. The ring can be one of the following: an array of numbers or an array of points. When added the index of the ring is incremented by one. + /// Adds a ring to the Polygon. The ring can be one of the following: an array of numbers or an array of points. When + /// added the index of the ring is incremented by one. /// /// /// The polygon to add the ring to. @@ -1617,20 +2008,24 @@ public async Task AddRing(Polygon polygon, MapPath points) /// /// Returns a new polygon with the added ring. /// + /// The cancellation token to use for the operation. [SerializedMethod] - public async Task AddRing(Polygon polygon, Point[] points) + public async Task AddRing(Polygon polygon, Point[] points, CancellationToken cancellationToken = default) { var mapPoints = new List(); - foreach (Point p in points) + + foreach (var p in points) { mapPoints.Add(new MapPoint(p.X ?? p.Longitude!.Value, p.Y ?? p.Latitude!.Value)); } - return await AddRing(polygon, new MapPath(mapPoints)); + return await AddRing(polygon, new MapPath(mapPoints), cancellationToken); } /// - /// Converts the given Extent to a Polygon instance. This is useful for scenarios in which you would like to display an area of interest, which is typically defined by an Extent or bounding box, as a polygon with a fill symbol in the view. Some geoprocessing tools require input geometries to be of a Polygon type and not an Extent. + /// Converts the given Extent to a Polygon instance. This is useful for scenarios in which you would like to display an + /// area of interest, which is typically defined by an Extent or bounding box, as a polygon with a fill symbol in the + /// view. Some geoprocessing tools require input geometries to be of a Polygon type and not an Extent. /// /// /// An extent object to convert to a polygon. @@ -1638,10 +2033,12 @@ public async Task AddRing(Polygon polygon, Point[] points) /// /// A polygon instance representing the given extent. /// + /// The cancellation token to use for the operation. [SerializedMethod] - public async Task PolygonFromExtent(Extent extent) + public async Task PolygonFromExtent(Extent extent, CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(GeometryEngine), parameters: [extent]); + return await InvokeAsync(nameof(GeometryEngine), nameof(PolygonFromExtent), + cancellationToken, extent); } /// @@ -1659,10 +2056,13 @@ public async Task PolygonFromExtent(Extent extent) /// /// Returns the point at the specified ring index and point index. /// + /// The cancellation token to use for the operation. [SerializedMethod] - public async Task GetPoint(Polygon polygon, int ringIndex, int pointIndex) + public async Task GetPoint(Polygon polygon, int ringIndex, int pointIndex, + CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(GeometryEngine), parameters: [polygon, ringIndex, pointIndex]); + return await InvokeAsync(nameof(GeometryEngine), nameof(GetPoint), + cancellationToken, polygon, ringIndex, pointIndex); } /// @@ -1683,10 +2083,13 @@ public async Task GetPoint(Polygon polygon, int ringIndex, int pointIndex /// /// Returns a new polygon with the inserted point. /// + /// The cancellation token to use for the operation. [SerializedMethod] - public async Task InsertPoint(Polygon polygon, int ringIndex, int pointIndex, Point point) + public async Task InsertPoint(Polygon polygon, int ringIndex, int pointIndex, Point point, + CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(GeometryEngine), parameters: [polygon, ringIndex, pointIndex, point]); + return await InvokeAsync(nameof(GeometryEngine), nameof(InsertPoint), + cancellationToken, polygon, ringIndex, pointIndex, point); } /// @@ -1696,15 +2099,18 @@ public async Task InsertPoint(Polygon polygon, int ringIndex, int point /// The polygon to check the ring on. /// /// - /// A polygon ring defined as a . The first and last coordinates/points in the ring must be the same. + /// A polygon ring defined as a . The first and last coordinates/points in the ring must be the + /// same. /// /// /// Returns true if the ring is clockwise and false for counterclockwise. /// + /// The cancellation token to use for the operation. [SerializedMethod] - public async Task IsClockwise(Polygon polygon, MapPath ring) + public async Task IsClockwise(Polygon polygon, MapPath ring, CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(GeometryEngine), parameters: [polygon, ring]); + return await InvokeAsync(nameof(GeometryEngine), nameof(IsClockwise), + cancellationToken, polygon, ring); } /// @@ -1714,21 +2120,24 @@ public async Task IsClockwise(Polygon polygon, MapPath ring) /// The polygon to check the ring on. /// /// - /// A polygon ring defined as an array of s. The first and last coordinates/points in the ring must be the same. + /// A polygon ring defined as an array of s. The first and last coordinates/points in the ring must + /// be the same. /// /// /// Returns true if the ring is clockwise and false for counterclockwise. /// + /// The cancellation token to use for the operation. [SerializedMethod] - public async Task IsClockwise(Polygon polygon, Point[] ring) + public async Task IsClockwise(Polygon polygon, Point[] ring, CancellationToken cancellationToken = default) { var mapPoints = new List(); - foreach (Point p in ring) + + foreach (var p in ring) { mapPoints.Add(new MapPoint(p.X!.Value, p.Y!.Value)); } - return await IsClockwise(polygon, new MapPath(mapPoints)); + return await IsClockwise(polygon, new MapPath(mapPoints), cancellationToken); } /// @@ -1743,18 +2152,25 @@ public async Task IsClockwise(Polygon polygon, Point[] ring) /// /// The index of the point to remove within the ring. /// + /// The cancellation token to use for the operation. /// /// Returns an object with the modified polygon and the removed point. /// - public async Task<(Polygon Polygon, Point Point)> RemovePoint(Polygon polygon, int ringIndex, int pointIndex) + [SerializedMethod] + public async Task<(Polygon Polygon, Point Point)> RemovePoint(Polygon polygon, int ringIndex, int pointIndex, + CancellationToken cancellationToken = default) { - // DON'T ADD [SerializedMethod], doesn't work here - // TODO: Refactor API for V5 - GeometryRemovePointResult result = - await InvokeAsync(nameof(GeometryEngine), - parameters: [polygon, ringIndex, pointIndex]); + var result = + await InvokeAsync(nameof(GeometryEngine), nameof(RemovePoint), + cancellationToken, polygon, ringIndex, pointIndex); - return ((Polygon)result.Geometry, result.Point); + if (result.Length < 2) + { + throw new InvalidOperationException($"No point found at ring index {ringIndex} and point index {pointIndex + } on polygon"); + } + + return ((Polygon)result[0], (Point)result[1]); } /// @@ -1766,17 +2182,24 @@ await InvokeAsync(nameof(GeometryEngine), /// /// The index of the ring to remove. /// + /// The cancellation token to use for the operation. /// /// Returns an object with the modified polygon and the removed ring. /// - public async Task<(Polygon Polygon, Point[] Ring)> RemoveRing(Polygon polygon, int index) + [SerializedMethod] + public async Task<(Polygon Polygon, Point[] Ring)> RemoveRing(Polygon polygon, int index, + CancellationToken cancellationToken = default) { - // DON'T ADD [SerializedMethod], doesn't work here - // TODO: Refactor API for V5 - GeometryRemovePathResult result = - await InvokeAsync(nameof(GeometryEngine), parameters: [polygon, index]); - - return ((Polygon)result.Geometry, result.Path); + var result = + await InvokeAsync(nameof(GeometryEngine), nameof(RemoveRing), + cancellationToken, polygon, index); + + if (result.Length < 3) // need at least two points for the ring + { + throw new InvalidOperationException($"No ring found at index {index} on polygon"); + } + + return ((Polygon)result[0], result.Skip(1).Cast().ToArray()); } /// @@ -1797,39 +2220,48 @@ await InvokeAsync(nameof(GeometryEngine), /// /// Returns a new polygon with the updated point. /// + /// The cancellation token to use for the operation. [SerializedMethod] - public async Task SetPoint(Polygon polygon, int ringIndex, int pointIndex, Point point) + public async Task SetPoint(Polygon polygon, int ringIndex, int pointIndex, Point point, + CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(GeometryEngine), parameters: [polygon, ringIndex, pointIndex, point]); + return await InvokeAsync(nameof(GeometryEngine), nameof(SetPoint), + cancellationToken, polygon, ringIndex, pointIndex, point); } /// /// Retrieves the center point of the extent in map units. /// + /// The extent to measure + /// The cancellation token to use for the operation. [SerializedMethod] - public async Task GetExtentCenter(Extent extent) + public async Task GetExtentCenter(Extent extent, CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(GeometryEngine), parameters: [extent]); + return await InvokeAsync(nameof(GeometryEngine), nameof(GetExtentCenter), + cancellationToken, extent); } /// /// Retrieves the height of the extent in map units (the distance between ymin and ymax). /// + /// The extent to measure + /// The cancellation token to use for the operation. [SerializedMethod] - public async Task GetExtentHeight(Extent extent) + public async Task GetExtentHeight(Extent extent, CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(GeometryEngine), parameters: [extent]); + return await InvokeAsync(nameof(ProjectionEngine), nameof(GetExtentHeight), + cancellationToken, extent); } /// /// Retrieves the width of the extent in map units (the distance between xmin and xmax). /// + /// The extent to measure + /// The cancellation token to use for the operation. [SerializedMethod] - public async Task GetExtentWidth(Extent extent) + public async Task GetExtentWidth(Extent extent, CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(GeometryEngine), parameters: [extent]); + return await InvokeAsync(nameof(ProjectionEngine), nameof(GetExtentWidth), + cancellationToken, extent); } -} - -internal record GeometryRemovePointResult(Geometry Geometry, Point Point); -internal record GeometryRemovePathResult(Geometry Geometry, Point[] Path); \ No newline at end of file +} \ No newline at end of file diff --git a/src/dymaptic.GeoBlazor.Core/Components/LocationService.cs b/src/dymaptic.GeoBlazor.Core/Components/LocationService.cs index 0f6e9a485..44d538278 100644 --- a/src/dymaptic.GeoBlazor.Core/Components/LocationService.cs +++ b/src/dymaptic.GeoBlazor.Core/Components/LocationService.cs @@ -1,6 +1,7 @@ // ReSharper disable MethodOverloadWithOptionalParameter namespace dymaptic.GeoBlazor.Core.Components; +[CodeGenerationIgnore] public partial class LocationService : LogicComponent { /// @@ -12,35 +13,57 @@ public LocationService(IAppValidator appValidator, IJSRuntime jsRuntime, JsModul { } - /// + /// protected override string ComponentName => nameof(LocationService); + // final implementation of all the SuggestLocations permutations + [SerializedMethod] + public async Task LocationToAddressImplementation(string url, Point location, + LocationType? locationType, SpatialReference? outSpatialReference, RequestOptions? requestOptions, + CancellationToken cancellationToken = default) + { + return await InvokeAsync(nameof(LocationService), nameof(LocationToAddress), + cancellationToken, url, location, locationType, outSpatialReference, requestOptions); + } + // Final implementation of all the permutations of AddressesToLocations + [SerializedMethod] private async Task> AddressesToLocationsImplementation(string url, object addresses, string? countryCode, List? categories, LocationType? locationType, SpatialReference? outSpatialReference, RequestOptions? requestOptions, - string? addressSearchStringParameterName) + string? addressSearchStringParameterName, CancellationToken cancellationToken = default) { - IJSStreamReference streamRef = await InvokeAsync("addressesToLocations", url, + return await InvokeAsync>(nameof(LocationService), + nameof(AddressesToLocations), cancellationToken, url, addresses, countryCode, categories, locationType, outSpatialReference, requestOptions, addressSearchStringParameterName); - - return await streamRef.ReadJsStreamReferenceAsJSON>() ?? []; } // final implementation of all the AddressToLocations permutations + [SerializedMethod] private async Task> AddressToLocationsImplementation(string url, object address, List? categories = null, string? countryCode = null, bool? forStorage = null, Point? location = null, LocationType? locationType = null, string? magicKey = null, int? maxLocations = null, List? outFields = null, SpatialReference? outSpatialReference = null, Extent? searchExtent = null, - RequestOptions? requestOptions = null, string? addressSearchStringParameterName = null) + RequestOptions? requestOptions = null, string? addressSearchStringParameterName = null, + CancellationToken cancellationToken = default) { - IJSStreamReference streamRef = await InvokeAsync("addressToLocations", url, address, + return await InvokeAsync>(nameof(LocationService), + nameof(AddressToLocations), cancellationToken, url, address, categories, countryCode, forStorage, location, locationType, magicKey, maxLocations, outFields, outSpatialReference, searchExtent, requestOptions, addressSearchStringParameterName); + } - return await streamRef.ReadJsStreamReferenceAsJSON>() ?? []; + // final implementation of all the SuggestLocations permutations + [SerializedMethod] + private async Task> SuggestLocationsImplementation(string url, Point location, string text, + List? categories, RequestOptions? requestOptions, + CancellationToken cancellationToken) + { + return await InvokeAsync>(nameof(LocationService), + nameof(SuggestLocations), cancellationToken, url, location, text, + categories, requestOptions); } private const string ESRIGeoLocationUrl = "https://geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer"; @@ -54,8 +77,6 @@ private async Task> AddressToLocationsImplementation(stri /// Note: If using as API token: the token must have "Geocode (Stored)" enabled to get results /// /// The input addresses in the format supported by the geocode service. - [CodeGenerationIgnore] - [SerializedMethod] public async Task> AddressesToLocations(List
addresses) { return await AddressesToLocations(ESRIGeoLocationUrl, addresses); @@ -68,9 +89,9 @@ public async Task> AddressesToLocations(List
add ///
/// The input addresses in the format supported by the geocode service. /// - /// Limits the results to only search in the country provided. For example US for United States or SE for Sweden. Only applies to the World Geocode Service. See the World Geocoding Service documentation for more information. + /// Limits the results to only search in the country provided. For example US for United States or SE for Sweden. Only + /// applies to the World Geocode Service. See the World Geocoding Service documentation for more information. /// - [SerializedMethod] public async Task> AddressesToLocations(List
addresses, string? countryCode) { return await AddressesToLocations(ESRIGeoLocationUrl, addresses, countryCode); @@ -83,13 +104,13 @@ public async Task> AddressesToLocations(List
add ///
/// The input addresses in the format supported by the geocode service. /// - /// Limits the results to only search in the country provided. For example US for United States or SE for Sweden. Only applies to the World Geocode Service. See the World Geocoding Service documentation for more information. + /// Limits the results to only search in the country provided. For example US for United States or SE for Sweden. Only + /// applies to the World Geocode Service. See the World Geocoding Service documentation for more information. /// /// /// Limit result to one or more categories. For example, "Populated Place" or "Scandinavian Food". /// Only applies to the World Geocode Service. See Category filtering (World Geocoding Service) for more information. /// - [SerializedMethod] public async Task> AddressesToLocations(List
addresses, string? countryCode, List? categories) { @@ -103,7 +124,8 @@ public async Task> AddressesToLocations(List
add ///
/// The input addresses in the format supported by the geocode service. /// - /// Limits the results to only search in the country provided. For example US for United States or SE for Sweden. Only applies to the World Geocode Service. See the World Geocoding Service documentation for more information. + /// Limits the results to only search in the country provided. For example US for United States or SE for Sweden. Only + /// applies to the World Geocode Service. See the World Geocoding Service documentation for more information. /// /// /// Limit result to one or more categories. For example, "Populated Place" or "Scandinavian Food". @@ -112,7 +134,6 @@ public async Task> AddressesToLocations(List
add /// /// Define the type of location, either "street" or "rooftop", of the point returned from the World Geocoding Service. /// - [SerializedMethod] public async Task> AddressesToLocations(List
addresses, string? countryCode, List? categories, LocationType? locationType) { @@ -126,7 +147,8 @@ public async Task> AddressesToLocations(List
add ///
/// The input addresses in the format supported by the geocode service. /// - /// Limits the results to only search in the country provided. For example US for United States or SE for Sweden. Only applies to the World Geocode Service. See the World Geocoding Service documentation for more information. + /// Limits the results to only search in the country provided. For example US for United States or SE for Sweden. Only + /// applies to the World Geocode Service. See the World Geocoding Service documentation for more information. /// /// /// Limit result to one or more categories. For example, "Populated Place" or "Scandinavian Food". @@ -136,9 +158,10 @@ public async Task> AddressesToLocations(List
add /// Define the type of location, either "street" or "rooftop", of the point returned from the World Geocoding Service. /// /// - /// The spatial reference of the output geometries. If not specified, the output geometries are in the spatial reference of the input geometries when performing a reverse geocode and in the default spatial reference returned by the service if finding locations by address. + /// The spatial reference of the output geometries. If not specified, the output geometries are in the spatial + /// reference of the input geometries when performing a reverse geocode and in the default spatial reference returned + /// by the service if finding locations by address. /// - [SerializedMethod] public async Task> AddressesToLocations(List
addresses, string? countryCode, List? categories, LocationType? locationType, SpatialReference? outSpatialReference) @@ -154,7 +177,8 @@ public async Task> AddressesToLocations(List
add ///
/// The input addresses in the format supported by the geocode service. /// - /// Limits the results to only search in the country provided. For example US for United States or SE for Sweden. Only applies to the World Geocode Service. See the World Geocoding Service documentation for more information. + /// Limits the results to only search in the country provided. For example US for United States or SE for Sweden. Only + /// applies to the World Geocode Service. See the World Geocoding Service documentation for more information. /// /// /// Limit result to one or more categories. For example, "Populated Place" or "Scandinavian Food". @@ -164,12 +188,13 @@ public async Task> AddressesToLocations(List
add /// Define the type of location, either "street" or "rooftop", of the point returned from the World Geocoding Service. /// /// - /// The spatial reference of the output geometries. If not specified, the output geometries are in the spatial reference of the input geometries when performing a reverse geocode and in the default spatial reference returned by the service if finding locations by address. + /// The spatial reference of the output geometries. If not specified, the output geometries are in the spatial + /// reference of the input geometries when performing a reverse geocode and in the default spatial reference returned + /// by the service if finding locations by address. /// /// /// Additional options to be used for the data request /// - [SerializedMethod] public async Task> AddressesToLocations(List
addresses, string? countryCode, List? categories, LocationType? locationType, SpatialReference? outSpatialReference, RequestOptions? requestOptions) @@ -196,7 +221,8 @@ public Task> AddressesToLocations(string url, ListURL to the ArcGIS Server REST resource that represents a locator service. /// The input addresses in the format supported by the geocode service. /// - /// Limits the results to only search in the country provided. For example US for United States or SE for Sweden. Only applies to the World Geocode Service. See the World Geocoding Service documentation for more information. + /// Limits the results to only search in the country provided. For example US for United States or SE for Sweden. Only + /// applies to the World Geocode Service. See the World Geocoding Service documentation for more information. /// public Task> AddressesToLocations(string url, List
addresses, string? countryCode) @@ -211,7 +237,8 @@ public Task> AddressesToLocations(string url, ListURL to the ArcGIS Server REST resource that represents a locator service. /// The input addresses in the format supported by the geocode service. /// - /// Limits the results to only search in the country provided. For example US for United States or SE for Sweden. Only applies to the World Geocode Service. See the World Geocoding Service documentation for more information. + /// Limits the results to only search in the country provided. For example US for United States or SE for Sweden. Only + /// applies to the World Geocode Service. See the World Geocoding Service documentation for more information. /// /// /// Limit result to one or more categories. For example, "Populated Place" or "Scandinavian Food". @@ -230,7 +257,8 @@ public Task> AddressesToLocations(string url, ListURL to the ArcGIS Server REST resource that represents a locator service. /// The input addresses in the format supported by the geocode service. /// - /// Limits the results to only search in the country provided. For example US for United States or SE for Sweden. Only applies to the World Geocode Service. See the World Geocoding Service documentation for more information. + /// Limits the results to only search in the country provided. For example US for United States or SE for Sweden. Only + /// applies to the World Geocode Service. See the World Geocoding Service documentation for more information. /// /// /// Limit result to one or more categories. For example, "Populated Place" or "Scandinavian Food". @@ -252,7 +280,8 @@ public Task> AddressesToLocations(string url, ListURL to the ArcGIS Server REST resource that represents a locator service. /// The input addresses in the format supported by the geocode service. /// - /// Limits the results to only search in the country provided. For example US for United States or SE for Sweden. Only applies to the World Geocode Service. See the World Geocoding Service documentation for more information. + /// Limits the results to only search in the country provided. For example US for United States or SE for Sweden. Only + /// applies to the World Geocode Service. See the World Geocoding Service documentation for more information. /// /// /// Limit result to one or more categories. For example, "Populated Place" or "Scandinavian Food". @@ -262,7 +291,9 @@ public Task> AddressesToLocations(string url, List /// - /// The spatial reference of the output geometries. If not specified, the output geometries are in the spatial reference of the input geometries when performing a reverse geocode and in the default spatial reference returned by the service if finding locations by address. + /// The spatial reference of the output geometries. If not specified, the output geometries are in the spatial + /// reference of the input geometries when performing a reverse geocode and in the default spatial reference returned + /// by the service if finding locations by address. /// public Task> AddressesToLocations(string url, List
addresses, string? countryCode, List? categories, LocationType? locationType, @@ -278,7 +309,8 @@ public Task> AddressesToLocations(string url, ListURL to the ArcGIS Server REST resource that represents a locator service. /// The input addresses in the format supported by the geocode service. /// - /// Limits the results to only search in the country provided. For example US for United States or SE for Sweden. Only applies to the World Geocode Service. See the World Geocoding Service documentation for more information. + /// Limits the results to only search in the country provided. For example US for United States or SE for Sweden. Only + /// applies to the World Geocode Service. See the World Geocoding Service documentation for more information. /// /// /// Limit result to one or more categories. For example, "Populated Place" or "Scandinavian Food". @@ -288,7 +320,9 @@ public Task> AddressesToLocations(string url, List /// - /// The spatial reference of the output geometries. If not specified, the output geometries are in the spatial reference of the input geometries when performing a reverse geocode and in the default spatial reference returned by the service if finding locations by address. + /// The spatial reference of the output geometries. If not specified, the output geometries are in the spatial + /// reference of the input geometries when performing a reverse geocode and in the default spatial reference returned + /// by the service if finding locations by address. /// /// /// Additional options to be used for the data request @@ -312,8 +346,6 @@ public Task> AddressesToLocations(string url, List /// The input addresses in the format supported by the geocode service. - [CodeGenerationIgnore] - [SerializedMethod] public async Task> AddressesToLocations(List addresses) { return await AddressesToLocations(ESRIGeoLocationUrl, addresses); @@ -326,9 +358,9 @@ public async Task> AddressesToLocations(List addr ///
/// The input addresses in the format supported by the geocode service. /// - /// Limits the results to only search in the country provided. For example US for United States or SE for Sweden. Only applies to the World Geocode Service. See the World Geocoding Service documentation for more information. + /// Limits the results to only search in the country provided. For example US for United States or SE for Sweden. Only + /// applies to the World Geocode Service. See the World Geocoding Service documentation for more information. /// - [SerializedMethod] public async Task> AddressesToLocations(List addresses, string? countryCode) { return await AddressesToLocations(ESRIGeoLocationUrl, addresses, countryCode); @@ -341,13 +373,13 @@ public async Task> AddressesToLocations(List addr ///
/// The input addresses in the format supported by the geocode service. /// - /// Limits the results to only search in the country provided. For example US for United States or SE for Sweden. Only applies to the World Geocode Service. See the World Geocoding Service documentation for more information. + /// Limits the results to only search in the country provided. For example US for United States or SE for Sweden. Only + /// applies to the World Geocode Service. See the World Geocoding Service documentation for more information. /// /// /// Limit result to one or more categories. For example, "Populated Place" or "Scandinavian Food". /// Only applies to the World Geocode Service. See Category filtering (World Geocoding Service) for more information. /// - [SerializedMethod] public async Task> AddressesToLocations(List addresses, string? countryCode, List? categories) { @@ -361,7 +393,8 @@ public async Task> AddressesToLocations(List addr ///
/// The input addresses in the format supported by the geocode service. /// - /// Limits the results to only search in the country provided. For example US for United States or SE for Sweden. Only applies to the World Geocode Service. See the World Geocoding Service documentation for more information. + /// Limits the results to only search in the country provided. For example US for United States or SE for Sweden. Only + /// applies to the World Geocode Service. See the World Geocoding Service documentation for more information. /// /// /// Limit result to one or more categories. For example, "Populated Place" or "Scandinavian Food". @@ -370,7 +403,6 @@ public async Task> AddressesToLocations(List addr /// /// Define the type of location, either "street" or "rooftop", of the point returned from the World Geocoding Service. /// - [SerializedMethod] public async Task> AddressesToLocations(List addresses, string? countryCode, List? categories, LocationType? locationType) { @@ -384,7 +416,8 @@ public async Task> AddressesToLocations(List addr ///
/// The input addresses in the format supported by the geocode service. /// - /// Limits the results to only search in the country provided. For example US for United States or SE for Sweden. Only applies to the World Geocode Service. See the World Geocoding Service documentation for more information. + /// Limits the results to only search in the country provided. For example US for United States or SE for Sweden. Only + /// applies to the World Geocode Service. See the World Geocoding Service documentation for more information. /// /// /// Limit result to one or more categories. For example, "Populated Place" or "Scandinavian Food". @@ -394,9 +427,10 @@ public async Task> AddressesToLocations(List addr /// Define the type of location, either "street" or "rooftop", of the point returned from the World Geocoding Service. /// /// - /// The spatial reference of the output geometries. If not specified, the output geometries are in the spatial reference of the input geometries when performing a reverse geocode and in the default spatial reference returned by the service if finding locations by address. + /// The spatial reference of the output geometries. If not specified, the output geometries are in the spatial + /// reference of the input geometries when performing a reverse geocode and in the default spatial reference returned + /// by the service if finding locations by address. /// - [SerializedMethod] public async Task> AddressesToLocations(List addresses, string? countryCode, List? categories, LocationType? locationType, SpatialReference? outSpatialReference) @@ -412,7 +446,8 @@ public async Task> AddressesToLocations(List addr ///
/// The input addresses in the format supported by the geocode service. /// - /// Limits the results to only search in the country provided. For example US for United States or SE for Sweden. Only applies to the World Geocode Service. See the World Geocoding Service documentation for more information. + /// Limits the results to only search in the country provided. For example US for United States or SE for Sweden. Only + /// applies to the World Geocode Service. See the World Geocoding Service documentation for more information. /// /// /// Limit result to one or more categories. For example, "Populated Place" or "Scandinavian Food". @@ -422,12 +457,13 @@ public async Task> AddressesToLocations(List addr /// Define the type of location, either "street" or "rooftop", of the point returned from the World Geocoding Service. /// /// - /// The spatial reference of the output geometries. If not specified, the output geometries are in the spatial reference of the input geometries when performing a reverse geocode and in the default spatial reference returned by the service if finding locations by address. + /// The spatial reference of the output geometries. If not specified, the output geometries are in the spatial + /// reference of the input geometries when performing a reverse geocode and in the default spatial reference returned + /// by the service if finding locations by address. /// /// /// Additional options to be used for the data request /// - [SerializedMethod] public async Task> AddressesToLocations(List addresses, string? countryCode, List? categories, LocationType? locationType, SpatialReference? outSpatialReference, RequestOptions? requestOptions) @@ -443,7 +479,8 @@ public async Task> AddressesToLocations(List addr ///
/// The input addresses in the format supported by the geocode service. /// - /// Limits the results to only search in the country provided. For example US for United States or SE for Sweden. Only applies to the World Geocode Service. See the World Geocoding Service documentation for more information. + /// Limits the results to only search in the country provided. For example US for United States or SE for Sweden. Only + /// applies to the World Geocode Service. See the World Geocoding Service documentation for more information. /// /// /// Limit result to one or more categories. For example, "Populated Place" or "Scandinavian Food". @@ -453,7 +490,9 @@ public async Task> AddressesToLocations(List addr /// Define the type of location, either "street" or "rooftop", of the point returned from the World Geocoding Service. /// /// - /// The spatial reference of the output geometries. If not specified, the output geometries are in the spatial reference of the input geometries when performing a reverse geocode and in the default spatial reference returned by the service if finding locations by address. + /// The spatial reference of the output geometries. If not specified, the output geometries are in the spatial + /// reference of the input geometries when performing a reverse geocode and in the default spatial reference returned + /// by the service if finding locations by address. /// /// /// Additional options to be used for the data request @@ -461,7 +500,6 @@ public async Task> AddressesToLocations(List addr /// /// The name of the single line address field for the ArcGIS Locator Service (for ArcGIS 10+), defaults to 'address'. /// - [SerializedMethod] public async Task> AddressesToLocations(List addresses, string? countryCode, List? categories, LocationType? locationType, SpatialReference? outSpatialReference, RequestOptions? requestOptions, string? addressSearchStringParameterName) @@ -488,7 +526,8 @@ public Task> AddressesToLocations(string url, ListURL to the ArcGIS Server REST resource that represents a locator service. /// The input addresses in the format supported by the geocode service. /// - /// Limits the results to only search in the country provided. For example US for United States or SE for Sweden. Only applies to the World Geocode Service. See the World Geocoding Service documentation for more information. + /// Limits the results to only search in the country provided. For example US for United States or SE for Sweden. Only + /// applies to the World Geocode Service. See the World Geocoding Service documentation for more information. /// public Task> AddressesToLocations(string url, List addresses, string? countryCode) @@ -503,7 +542,8 @@ public Task> AddressesToLocations(string url, ListURL to the ArcGIS Server REST resource that represents a locator service. /// The input addresses in the format supported by the geocode service. /// - /// Limits the results to only search in the country provided. For example US for United States or SE for Sweden. Only applies to the World Geocode Service. See the World Geocoding Service documentation for more information. + /// Limits the results to only search in the country provided. For example US for United States or SE for Sweden. Only + /// applies to the World Geocode Service. See the World Geocoding Service documentation for more information. /// /// /// Limit result to one or more categories. For example, "Populated Place" or "Scandinavian Food". @@ -522,7 +562,8 @@ public Task> AddressesToLocations(string url, ListURL to the ArcGIS Server REST resource that represents a locator service. /// The input addresses in the format supported by the geocode service. /// - /// Limits the results to only search in the country provided. For example US for United States or SE for Sweden. Only applies to the World Geocode Service. See the World Geocoding Service documentation for more information. + /// Limits the results to only search in the country provided. For example US for United States or SE for Sweden. Only + /// applies to the World Geocode Service. See the World Geocoding Service documentation for more information. /// /// /// Limit result to one or more categories. For example, "Populated Place" or "Scandinavian Food". @@ -544,7 +585,8 @@ public Task> AddressesToLocations(string url, ListURL to the ArcGIS Server REST resource that represents a locator service. /// The input addresses in the format supported by the geocode service. /// - /// Limits the results to only search in the country provided. For example US for United States or SE for Sweden. Only applies to the World Geocode Service. See the World Geocoding Service documentation for more information. + /// Limits the results to only search in the country provided. For example US for United States or SE for Sweden. Only + /// applies to the World Geocode Service. See the World Geocoding Service documentation for more information. /// /// /// Limit result to one or more categories. For example, "Populated Place" or "Scandinavian Food". @@ -554,7 +596,9 @@ public Task> AddressesToLocations(string url, List /// - /// The spatial reference of the output geometries. If not specified, the output geometries are in the spatial reference of the input geometries when performing a reverse geocode and in the default spatial reference returned by the service if finding locations by address. + /// The spatial reference of the output geometries. If not specified, the output geometries are in the spatial + /// reference of the input geometries when performing a reverse geocode and in the default spatial reference returned + /// by the service if finding locations by address. /// public Task> AddressesToLocations(string url, List addresses, string? countryCode, List? categories, LocationType? locationType, @@ -571,7 +615,8 @@ public Task> AddressesToLocations(string url, ListURL to the ArcGIS Server REST resource that represents a locator service. /// The input addresses in the format supported by the geocode service. /// - /// Limits the results to only search in the country provided. For example US for United States or SE for Sweden. Only applies to the World Geocode Service. See the World Geocoding Service documentation for more information. + /// Limits the results to only search in the country provided. For example US for United States or SE for Sweden. Only + /// applies to the World Geocode Service. See the World Geocoding Service documentation for more information. /// /// /// Limit result to one or more categories. For example, "Populated Place" or "Scandinavian Food". @@ -581,7 +626,9 @@ public Task> AddressesToLocations(string url, List /// - /// The spatial reference of the output geometries. If not specified, the output geometries are in the spatial reference of the input geometries when performing a reverse geocode and in the default spatial reference returned by the service if finding locations by address. + /// The spatial reference of the output geometries. If not specified, the output geometries are in the spatial + /// reference of the input geometries when performing a reverse geocode and in the default spatial reference returned + /// by the service if finding locations by address. /// /// /// Additional options to be used for the data request @@ -601,7 +648,8 @@ public Task> AddressesToLocations(string url, ListURL to the ArcGIS Server REST resource that represents a locator service. /// The input addresses in the format supported by the geocode service. /// - /// Limits the results to only search in the country provided. For example US for United States or SE for Sweden. Only applies to the World Geocode Service. See the World Geocoding Service documentation for more information. + /// Limits the results to only search in the country provided. For example US for United States or SE for Sweden. Only + /// applies to the World Geocode Service. See the World Geocoding Service documentation for more information. /// /// /// Limit result to one or more categories. For example, "Populated Place" or "Scandinavian Food". @@ -611,7 +659,9 @@ public Task> AddressesToLocations(string url, List /// - /// The spatial reference of the output geometries. If not specified, the output geometries are in the spatial reference of the input geometries when performing a reverse geocode and in the default spatial reference returned by the service if finding locations by address. + /// The spatial reference of the output geometries. If not specified, the output geometries are in the spatial + /// reference of the input geometries when performing a reverse geocode and in the default spatial reference returned + /// by the service if finding locations by address. /// /// /// Additional options to be used for the data request @@ -624,7 +674,7 @@ public Task> AddressesToLocations(string url, List> AddressesToLocations(string url, List - /// Sends a request to the ArcGIS REST geocode resource to find candidates for a single address specified in the address parameter. + /// Sends a request to the ArcGIS REST geocode resource to find candidates for a single address specified in the + /// address parameter. /// Uses the default ESRI geolocation service. ///
/// the various address fields accepted by the corresponding geocode service. - [CodeGenerationIgnore] public Task> AddressToLocations(Address address) { return AddressToLocations(ESRIGeoLocationUrl, address); } /// - /// Sends a request to the ArcGIS REST geocode resource to find candidates for a single address specified in the address parameter. + /// Sends a request to the ArcGIS REST geocode resource to find candidates for a single address specified in the + /// address parameter. /// Uses the default ESRI geolocation service. /// /// the various address fields accepted by the corresponding geocode service. @@ -653,14 +704,14 @@ public Task> AddressToLocations(Address address) /// Limit result to one or more categories. For example, "Populated Place" or "Scandinavian Food". /// Only applies to the World Geocode Service. See Category filtering (World Geocoding Service) for more information. /// - [SerializedMethod] public async Task> AddressToLocations(Address address, List? categories) { return await AddressToLocations(ESRIGeoLocationUrl, address, categories); } /// - /// Sends a request to the ArcGIS REST geocode resource to find candidates for a single address specified in the address parameter. + /// Sends a request to the ArcGIS REST geocode resource to find candidates for a single address specified in the + /// address parameter. /// Uses the default ESRI geolocation service. /// /// the various address fields accepted by the corresponding geocode service. @@ -672,7 +723,6 @@ public async Task> AddressToLocations(Address address, Li /// Limit result to a specific country. For example, "US" for United States or "SE" for Sweden. /// Only applies to the World Geocode Service. See Geocode coverage (World Geocoding Service) for more information. /// - [SerializedMethod] public async Task> AddressToLocations(Address address, List? categories, string? countryCode) { @@ -680,7 +730,8 @@ public async Task> AddressToLocations(Address address, Li } /// - /// Sends a request to the ArcGIS REST geocode resource to find candidates for a single address specified in the address parameter. + /// Sends a request to the ArcGIS REST geocode resource to find candidates for a single address specified in the + /// address parameter. /// Uses the default ESRI geolocation service. /// /// the various address fields accepted by the corresponding geocode service. @@ -693,7 +744,6 @@ public async Task> AddressToLocations(Address address, Li /// Only applies to the World Geocode Service. See Geocode coverage (World Geocoding Service) for more information. /// /// Allows the results of single geocode transactions to be persisted. - [SerializedMethod] public async Task> AddressToLocations(Address address, List? categories, string? countryCode, bool? forStorage) { @@ -701,7 +751,8 @@ public async Task> AddressToLocations(Address address, Li } /// - /// Sends a request to the ArcGIS REST geocode resource to find candidates for a single address specified in the address parameter. + /// Sends a request to the ArcGIS REST geocode resource to find candidates for a single address specified in the + /// address parameter. /// Uses the default ESRI geolocation service. /// /// the various address fields accepted by the corresponding geocode service. @@ -715,7 +766,6 @@ public async Task> AddressToLocations(Address address, Li /// /// Allows the results of single geocode transactions to be persisted. /// Used to weight returned results for a specified area. - [SerializedMethod] public async Task> AddressToLocations(Address address, List? categories, string? countryCode, bool? forStorage, Point? location) { @@ -723,7 +773,8 @@ public async Task> AddressToLocations(Address address, Li } /// - /// Sends a request to the ArcGIS REST geocode resource to find candidates for a single address specified in the address parameter. + /// Sends a request to the ArcGIS REST geocode resource to find candidates for a single address specified in the + /// address parameter. /// Uses the default ESRI geolocation service. /// /// the various address fields accepted by the corresponding geocode service. @@ -740,7 +791,6 @@ public async Task> AddressToLocations(Address address, Li /// /// Define the type of location, either "street" or "rooftop", of the point returned from the World Geocoding Service. /// - [SerializedMethod] public async Task> AddressToLocations(Address address, List? categories, string? countryCode, bool? forStorage, Point? location, LocationType? locationType) { @@ -749,7 +799,8 @@ public async Task> AddressToLocations(Address address, Li } /// - /// Sends a request to the ArcGIS REST geocode resource to find candidates for a single address specified in the address parameter. + /// Sends a request to the ArcGIS REST geocode resource to find candidates for a single address specified in the + /// address parameter. /// Uses the default ESRI geolocation service. /// /// the various address fields accepted by the corresponding geocode service. @@ -767,7 +818,6 @@ public async Task> AddressToLocations(Address address, Li /// Define the type of location, either "street" or "rooftop", of the point returned from the World Geocoding Service. /// /// A suggestLocations result ID (magicKey). Used to query for a specific results information. - [SerializedMethod] public async Task> AddressToLocations(Address address, List? categories, string? countryCode, bool? forStorage, Point? location, LocationType? locationType, string? magicKey) @@ -777,7 +827,8 @@ public async Task> AddressToLocations(Address address, Li } /// - /// Sends a request to the ArcGIS REST geocode resource to find candidates for a single address specified in the address parameter. + /// Sends a request to the ArcGIS REST geocode resource to find candidates for a single address specified in the + /// address parameter. /// Uses the default ESRI geolocation service. /// /// the various address fields accepted by the corresponding geocode service. @@ -796,7 +847,6 @@ public async Task> AddressToLocations(Address address, Li /// /// A suggestLocations result ID (magicKey). Used to query for a specific results information. /// Maximum results to return from the query. - [SerializedMethod] public async Task> AddressToLocations(Address address, List? categories, string? countryCode, bool? forStorage, Point? location, LocationType? locationType, string? magicKey, int? maxLocations) @@ -806,7 +856,8 @@ public async Task> AddressToLocations(Address address, Li } /// - /// Sends a request to the ArcGIS REST geocode resource to find candidates for a single address specified in the address parameter. + /// Sends a request to the ArcGIS REST geocode resource to find candidates for a single address specified in the + /// address parameter. /// Uses the default ESRI geolocation service. /// /// the various address fields accepted by the corresponding geocode service. @@ -826,9 +877,11 @@ public async Task> AddressToLocations(Address address, Li /// A suggestLocations result ID (magicKey). Used to query for a specific results information. /// Maximum results to return from the query. /// - /// The list of fields included in the returned result set. This list is a comma delimited list of field names. If you specify the shape field in the list of return fields, it is ignored. For non-intersection addresses you can specify the candidate fields as defined in the geocode service. For intersection addresses you can specify the intersection candidate fields. + /// The list of fields included in the returned result set. This list is a comma delimited list of field names. If you + /// specify the shape field in the list of return fields, it is ignored. For non-intersection addresses you can specify + /// the candidate fields as defined in the geocode service. For intersection addresses you can specify the intersection + /// candidate fields. /// - [SerializedMethod] public async Task> AddressToLocations(Address address, List? categories, string? countryCode, bool? forStorage, Point? location, LocationType? locationType, string? magicKey, int? maxLocations, List? outFields) @@ -838,7 +891,8 @@ public async Task> AddressToLocations(Address address, Li } /// - /// Sends a request to the ArcGIS REST geocode resource to find candidates for a single address specified in the address parameter. + /// Sends a request to the ArcGIS REST geocode resource to find candidates for a single address specified in the + /// address parameter. /// Uses the default ESRI geolocation service. /// /// the various address fields accepted by the corresponding geocode service. @@ -858,12 +912,16 @@ public async Task> AddressToLocations(Address address, Li /// A suggestLocations result ID (magicKey). Used to query for a specific results information. /// Maximum results to return from the query. /// - /// The list of fields included in the returned result set. This list is a comma delimited list of field names. If you specify the shape field in the list of return fields, it is ignored. For non-intersection addresses you can specify the candidate fields as defined in the geocode service. For intersection addresses you can specify the intersection candidate fields. + /// The list of fields included in the returned result set. This list is a comma delimited list of field names. If you + /// specify the shape field in the list of return fields, it is ignored. For non-intersection addresses you can specify + /// the candidate fields as defined in the geocode service. For intersection addresses you can specify the intersection + /// candidate fields. /// /// - /// The spatial reference of the output geometries. If not specified, the output geometries are in the spatial reference of the input geometries when performing a reverse geocode and in the default spatial reference returned by the service if finding locations by address. + /// The spatial reference of the output geometries. If not specified, the output geometries are in the spatial + /// reference of the input geometries when performing a reverse geocode and in the default spatial reference returned + /// by the service if finding locations by address. /// - [SerializedMethod] public async Task> AddressToLocations(Address address, List? categories, string? countryCode, bool? forStorage, Point? location, LocationType? locationType, string? magicKey, int? maxLocations, List? outFields, @@ -874,7 +932,8 @@ public async Task> AddressToLocations(Address address, Li } /// - /// Sends a request to the ArcGIS REST geocode resource to find candidates for a single address specified in the address parameter. + /// Sends a request to the ArcGIS REST geocode resource to find candidates for a single address specified in the + /// address parameter. /// Uses the default ESRI geolocation service. /// /// the various address fields accepted by the corresponding geocode service. @@ -894,15 +953,19 @@ public async Task> AddressToLocations(Address address, Li /// A suggestLocations result ID (magicKey). Used to query for a specific results information. /// Maximum results to return from the query. /// - /// The list of fields included in the returned result set. This list is a comma delimited list of field names. If you specify the shape field in the list of return fields, it is ignored. For non-intersection addresses you can specify the candidate fields as defined in the geocode service. For intersection addresses you can specify the intersection candidate fields. + /// The list of fields included in the returned result set. This list is a comma delimited list of field names. If you + /// specify the shape field in the list of return fields, it is ignored. For non-intersection addresses you can specify + /// the candidate fields as defined in the geocode service. For intersection addresses you can specify the intersection + /// candidate fields. /// /// - /// The spatial reference of the output geometries. If not specified, the output geometries are in the spatial reference of the input geometries when performing a reverse geocode and in the default spatial reference returned by the service if finding locations by address. + /// The spatial reference of the output geometries. If not specified, the output geometries are in the spatial + /// reference of the input geometries when performing a reverse geocode and in the default spatial reference returned + /// by the service if finding locations by address. /// /// /// Defines the extent within which the geocode server will search. Requires ArcGIS Server version 10.1 or greater. /// - [SerializedMethod] public async Task> AddressToLocations(Address address, List? categories, string? countryCode, bool? forStorage, Point? location, LocationType? locationType, string? magicKey, int? maxLocations, List? outFields, @@ -913,7 +976,8 @@ public async Task> AddressToLocations(Address address, Li } /// - /// Sends a request to the ArcGIS REST geocode resource to find candidates for a single address specified in the address parameter. + /// Sends a request to the ArcGIS REST geocode resource to find candidates for a single address specified in the + /// address parameter. /// Uses the default ESRI geolocation service. /// /// the various address fields accepted by the corresponding geocode service. @@ -933,18 +997,22 @@ public async Task> AddressToLocations(Address address, Li /// A suggestLocations result ID (magicKey). Used to query for a specific results information. /// Maximum results to return from the query. /// - /// The list of fields included in the returned result set. This list is a comma delimited list of field names. If you specify the shape field in the list of return fields, it is ignored. For non-intersection addresses you can specify the candidate fields as defined in the geocode service. For intersection addresses you can specify the intersection candidate fields. + /// The list of fields included in the returned result set. This list is a comma delimited list of field names. If you + /// specify the shape field in the list of return fields, it is ignored. For non-intersection addresses you can specify + /// the candidate fields as defined in the geocode service. For intersection addresses you can specify the intersection + /// candidate fields. /// /// - /// The spatial reference of the output geometries. If not specified, the output geometries are in the spatial reference of the input geometries when performing a reverse geocode and in the default spatial reference returned by the service if finding locations by address. + /// The spatial reference of the output geometries. If not specified, the output geometries are in the spatial + /// reference of the input geometries when performing a reverse geocode and in the default spatial reference returned + /// by the service if finding locations by address. /// /// /// Defines the extent within which the geocode server will search. Requires ArcGIS Server version 10.1 or greater. /// /// - /// Additional options to be used for the data request + /// Additional options to be used for the data request /// - [SerializedMethod] public async Task> AddressToLocations(Address address, List? categories = null, string? countryCode = null, bool? forStorage = null, Point? location = null, LocationType? locationType = null, string? magicKey = null, int? maxLocations = null, List? outFields = null, @@ -956,7 +1024,8 @@ public async Task> AddressToLocations(Address address, Li } /// - /// Sends a request to the ArcGIS REST geocode resource to find candidates for a single address specified in the address parameter. + /// Sends a request to the ArcGIS REST geocode resource to find candidates for a single address specified in the + /// address parameter. /// /// URL to the ArcGIS Server REST resource that represents a locator service. /// the various address fields accepted by the corresponding geocode service. @@ -967,7 +1036,8 @@ public Task> AddressToLocations(string url, Address addre } /// - /// Sends a request to the ArcGIS REST geocode resource to find candidates for a single address specified in the address parameter. + /// Sends a request to the ArcGIS REST geocode resource to find candidates for a single address specified in the + /// address parameter. /// /// URL to the ArcGIS Server REST resource that represents a locator service. /// the various address fields accepted by the corresponding geocode service. @@ -983,7 +1053,8 @@ public Task> AddressToLocations(string url, Address addre } /// - /// Sends a request to the ArcGIS REST geocode resource to find candidates for a single address specified in the address parameter. + /// Sends a request to the ArcGIS REST geocode resource to find candidates for a single address specified in the + /// address parameter. /// /// URL to the ArcGIS Server REST resource that represents a locator service. /// the various address fields accepted by the corresponding geocode service. @@ -1003,7 +1074,8 @@ public Task> AddressToLocations(string url, Address addre } /// - /// Sends a request to the ArcGIS REST geocode resource to find candidates for a single address specified in the address parameter. + /// Sends a request to the ArcGIS REST geocode resource to find candidates for a single address specified in the + /// address parameter. /// /// URL to the ArcGIS Server REST resource that represents a locator service. /// the various address fields accepted by the corresponding geocode service. @@ -1024,7 +1096,8 @@ public Task> AddressToLocations(string url, Address addre } /// - /// Sends a request to the ArcGIS REST geocode resource to find candidates for a single address specified in the address parameter. + /// Sends a request to the ArcGIS REST geocode resource to find candidates for a single address specified in the + /// address parameter. /// /// URL to the ArcGIS Server REST resource that represents a locator service. /// the various address fields accepted by the corresponding geocode service. @@ -1046,7 +1119,8 @@ public Task> AddressToLocations(string url, Address addre } /// - /// Sends a request to the ArcGIS REST geocode resource to find candidates for a single address specified in the address parameter. + /// Sends a request to the ArcGIS REST geocode resource to find candidates for a single address specified in the + /// address parameter. /// /// URL to the ArcGIS Server REST resource that represents a locator service. /// the various address fields accepted by the corresponding geocode service. @@ -1072,7 +1146,8 @@ public Task> AddressToLocations(string url, Address addre } /// - /// Sends a request to the ArcGIS REST geocode resource to find candidates for a single address specified in the address parameter. + /// Sends a request to the ArcGIS REST geocode resource to find candidates for a single address specified in the + /// address parameter. /// /// URL to the ArcGIS Server REST resource that represents a locator service. /// the various address fields accepted by the corresponding geocode service. @@ -1099,7 +1174,8 @@ public Task> AddressToLocations(string url, Address addre } /// - /// Sends a request to the ArcGIS REST geocode resource to find candidates for a single address specified in the address parameter. + /// Sends a request to the ArcGIS REST geocode resource to find candidates for a single address specified in the + /// address parameter. /// /// URL to the ArcGIS Server REST resource that represents a locator service. /// the various address fields accepted by the corresponding geocode service. @@ -1127,7 +1203,8 @@ public Task> AddressToLocations(string url, Address addre } /// - /// Sends a request to the ArcGIS REST geocode resource to find candidates for a single address specified in the address parameter. + /// Sends a request to the ArcGIS REST geocode resource to find candidates for a single address specified in the + /// address parameter. /// /// URL to the ArcGIS Server REST resource that represents a locator service. /// the various address fields accepted by the corresponding geocode service. @@ -1147,7 +1224,10 @@ public Task> AddressToLocations(string url, Address addre /// A suggestLocations result ID (magicKey). Used to query for a specific results information. /// Maximum results to return from the query. /// - /// The list of fields included in the returned result set. This list is a comma delimited list of field names. If you specify the shape field in the list of return fields, it is ignored. For non-intersection addresses you can specify the candidate fields as defined in the geocode service. For intersection addresses you can specify the intersection candidate fields. + /// The list of fields included in the returned result set. This list is a comma delimited list of field names. If you + /// specify the shape field in the list of return fields, it is ignored. For non-intersection addresses you can specify + /// the candidate fields as defined in the geocode service. For intersection addresses you can specify the intersection + /// candidate fields. /// public Task> AddressToLocations(string url, Address address, List? categories, string? countryCode, bool? forStorage, Point? location, @@ -1159,7 +1239,8 @@ public Task> AddressToLocations(string url, Address addre } /// - /// Sends a request to the ArcGIS REST geocode resource to find candidates for a single address specified in the address parameter. + /// Sends a request to the ArcGIS REST geocode resource to find candidates for a single address specified in the + /// address parameter. /// /// URL to the ArcGIS Server REST resource that represents a locator service. /// the various address fields accepted by the corresponding geocode service. @@ -1179,10 +1260,15 @@ public Task> AddressToLocations(string url, Address addre /// A suggestLocations result ID (magicKey). Used to query for a specific results information. /// Maximum results to return from the query. /// - /// The list of fields included in the returned result set. This list is a comma delimited list of field names. If you specify the shape field in the list of return fields, it is ignored. For non-intersection addresses you can specify the candidate fields as defined in the geocode service. For intersection addresses you can specify the intersection candidate fields. + /// The list of fields included in the returned result set. This list is a comma delimited list of field names. If you + /// specify the shape field in the list of return fields, it is ignored. For non-intersection addresses you can specify + /// the candidate fields as defined in the geocode service. For intersection addresses you can specify the intersection + /// candidate fields. /// /// - /// The spatial reference of the output geometries. If not specified, the output geometries are in the spatial reference of the input geometries when performing a reverse geocode and in the default spatial reference returned by the service if finding locations by address. + /// The spatial reference of the output geometries. If not specified, the output geometries are in the spatial + /// reference of the input geometries when performing a reverse geocode and in the default spatial reference returned + /// by the service if finding locations by address. /// public Task> AddressToLocations(string url, Address address, List? categories, string? countryCode, bool? forStorage, Point? location, @@ -1194,7 +1280,8 @@ public Task> AddressToLocations(string url, Address addre } /// - /// Sends a request to the ArcGIS REST geocode resource to find candidates for a single address specified in the address parameter. + /// Sends a request to the ArcGIS REST geocode resource to find candidates for a single address specified in the + /// address parameter. /// /// URL to the ArcGIS Server REST resource that represents a locator service. /// the various address fields accepted by the corresponding geocode service. @@ -1214,10 +1301,15 @@ public Task> AddressToLocations(string url, Address addre /// A suggestLocations result ID (magicKey). Used to query for a specific results information. /// Maximum results to return from the query. /// - /// The list of fields included in the returned result set. This list is a comma delimited list of field names. If you specify the shape field in the list of return fields, it is ignored. For non-intersection addresses you can specify the candidate fields as defined in the geocode service. For intersection addresses you can specify the intersection candidate fields. + /// The list of fields included in the returned result set. This list is a comma delimited list of field names. If you + /// specify the shape field in the list of return fields, it is ignored. For non-intersection addresses you can specify + /// the candidate fields as defined in the geocode service. For intersection addresses you can specify the intersection + /// candidate fields. /// /// - /// The spatial reference of the output geometries. If not specified, the output geometries are in the spatial reference of the input geometries when performing a reverse geocode and in the default spatial reference returned by the service if finding locations by address. + /// The spatial reference of the output geometries. If not specified, the output geometries are in the spatial + /// reference of the input geometries when performing a reverse geocode and in the default spatial reference returned + /// by the service if finding locations by address. /// /// /// Defines the extent within which the geocode server will search. Requires ArcGIS Server version 10.1 or greater. @@ -1233,7 +1325,8 @@ public Task> AddressToLocations(string url, Address addre } /// - /// Sends a request to the ArcGIS REST geocode resource to find candidates for a single address specified in the address parameter. + /// Sends a request to the ArcGIS REST geocode resource to find candidates for a single address specified in the + /// address parameter. /// /// URL to the ArcGIS Server REST resource that represents a locator service. /// the various address fields accepted by the corresponding geocode service. @@ -1253,20 +1346,23 @@ public Task> AddressToLocations(string url, Address addre /// A suggestLocations result ID (magicKey). Used to query for a specific results information. /// Maximum results to return from the query. /// - /// The list of fields included in the returned result set. This list is a comma delimited list of field names. If you specify the shape field in the list of return fields, it is ignored. For non-intersection addresses you can specify the candidate fields as defined in the geocode service. For intersection addresses you can specify the intersection candidate fields. + /// The list of fields included in the returned result set. This list is a comma delimited list of field names. If you + /// specify the shape field in the list of return fields, it is ignored. For non-intersection addresses you can specify + /// the candidate fields as defined in the geocode service. For intersection addresses you can specify the intersection + /// candidate fields. /// /// - /// The spatial reference of the output geometries. If not specified, the output geometries are in the spatial reference of the input geometries when performing a reverse geocode and in the default spatial reference returned by the service if finding locations by address. + /// The spatial reference of the output geometries. If not specified, the output geometries are in the spatial + /// reference of the input geometries when performing a reverse geocode and in the default spatial reference returned + /// by the service if finding locations by address. /// /// /// Defines the extent within which the geocode server will search. Requires ArcGIS Server version 10.1 or greater. /// /// - /// Additional options to be used for the data request + /// Additional options to be used for the data request /// - /// #pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters - [SerializedMethod] public async Task> AddressToLocations(string url, Address address, #pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters List? categories, string? countryCode, bool? forStorage, Point? location, @@ -1276,7 +1372,7 @@ public async Task> AddressToLocations(string url, Address { return await AddressToLocationsImplementation(url, address, categories, countryCode, forStorage, location, locationType, magicKey, maxLocations, - outFields, outSpatialReference, searchExtent, requestOptions, null); + outFields, outSpatialReference, searchExtent, requestOptions); } #endregion @@ -1285,18 +1381,19 @@ public async Task> AddressToLocations(string url, Address #region AddressToLocationsWithString /// - /// Sends a request to the ArcGIS REST geocode resource to find candidates for a single address specified in the address parameter. + /// Sends a request to the ArcGIS REST geocode resource to find candidates for a single address specified in the + /// address parameter. /// Uses the default ESRI geolocation service. /// /// the various address fields accepted by the corresponding geocode service. - [CodeGenerationIgnore] public Task> AddressToLocations(string address) { return AddressToLocations(ESRIGeoLocationUrl, address); } /// - /// Sends a request to the ArcGIS REST geocode resource to find candidates for a single address specified in the address parameter. + /// Sends a request to the ArcGIS REST geocode resource to find candidates for a single address specified in the + /// address parameter. /// Uses the default ESRI geolocation service. /// /// the various address fields accepted by the corresponding geocode service. @@ -1304,14 +1401,14 @@ public Task> AddressToLocations(string address) /// Limit result to one or more categories. For example, "Populated Place" or "Scandinavian Food". /// Only applies to the World Geocode Service. See Category filtering (World Geocoding Service) for more information. /// - [SerializedMethod] public async Task> AddressToLocations(string address, List? categories) { return await AddressToLocations(ESRIGeoLocationUrl, address, categories); } /// - /// Sends a request to the ArcGIS REST geocode resource to find candidates for a single address specified in the address parameter. + /// Sends a request to the ArcGIS REST geocode resource to find candidates for a single address specified in the + /// address parameter. /// Uses the default ESRI geolocation service. /// /// the various address fields accepted by the corresponding geocode service. @@ -1323,7 +1420,6 @@ public async Task> AddressToLocations(string address, Lis /// Limit result to a specific country. For example, "US" for United States or "SE" for Sweden. /// Only applies to the World Geocode Service. See Geocode coverage (World Geocoding Service) for more information. /// - [SerializedMethod] public async Task> AddressToLocations(string address, List? categories, string? countryCode) { @@ -1331,7 +1427,8 @@ public async Task> AddressToLocations(string address, Lis } /// - /// Sends a request to the ArcGIS REST geocode resource to find candidates for a single address specified in the address parameter. + /// Sends a request to the ArcGIS REST geocode resource to find candidates for a single address specified in the + /// address parameter. /// Uses the default ESRI geolocation service. /// /// the various address fields accepted by the corresponding geocode service. @@ -1344,7 +1441,6 @@ public async Task> AddressToLocations(string address, Lis /// Only applies to the World Geocode Service. See Geocode coverage (World Geocoding Service) for more information. /// /// Allows the results of single geocode transactions to be persisted. - [SerializedMethod] public async Task> AddressToLocations(string address, List? categories, string? countryCode, bool? forStorage) { @@ -1352,7 +1448,8 @@ public async Task> AddressToLocations(string address, Lis } /// - /// Sends a request to the ArcGIS REST geocode resource to find candidates for a single address specified in the address parameter. + /// Sends a request to the ArcGIS REST geocode resource to find candidates for a single address specified in the + /// address parameter. /// Uses the default ESRI geolocation service. /// /// the various address fields accepted by the corresponding geocode service. @@ -1366,7 +1463,6 @@ public async Task> AddressToLocations(string address, Lis /// /// Allows the results of single geocode transactions to be persisted. /// Used to weight returned results for a specified area. - [SerializedMethod] public async Task> AddressToLocations(string address, List? categories, string? countryCode, bool? forStorage, Point? location) { @@ -1374,7 +1470,8 @@ public async Task> AddressToLocations(string address, Lis } /// - /// Sends a request to the ArcGIS REST geocode resource to find candidates for a single address specified in the address parameter. + /// Sends a request to the ArcGIS REST geocode resource to find candidates for a single address specified in the + /// address parameter. /// Uses the default ESRI geolocation service. /// /// the various address fields accepted by the corresponding geocode service. @@ -1391,7 +1488,6 @@ public async Task> AddressToLocations(string address, Lis /// /// Define the type of location, either "street" or "rooftop", of the point returned from the World Geocoding Service. /// - [SerializedMethod] public async Task> AddressToLocations(string address, List? categories, string? countryCode, bool? forStorage, Point? location, LocationType? locationType) { @@ -1400,7 +1496,8 @@ public async Task> AddressToLocations(string address, Lis } /// - /// Sends a request to the ArcGIS REST geocode resource to find candidates for a single address specified in the address parameter. + /// Sends a request to the ArcGIS REST geocode resource to find candidates for a single address specified in the + /// address parameter. /// Uses the default ESRI geolocation service. /// /// the various address fields accepted by the corresponding geocode service. @@ -1418,7 +1515,6 @@ public async Task> AddressToLocations(string address, Lis /// Define the type of location, either "street" or "rooftop", of the point returned from the World Geocoding Service. /// /// A suggestLocations result ID (magicKey). Used to query for a specific results information. - [SerializedMethod] public async Task> AddressToLocations(string address, List? categories, string? countryCode, bool? forStorage, Point? location, LocationType? locationType, string? magicKey) @@ -1428,7 +1524,8 @@ public async Task> AddressToLocations(string address, Lis } /// - /// Sends a request to the ArcGIS REST geocode resource to find candidates for a single address specified in the address parameter. + /// Sends a request to the ArcGIS REST geocode resource to find candidates for a single address specified in the + /// address parameter. /// Uses the default ESRI geolocation service. /// /// the various address fields accepted by the corresponding geocode service. @@ -1447,7 +1544,6 @@ public async Task> AddressToLocations(string address, Lis /// /// A suggestLocations result ID (magicKey). Used to query for a specific results information. /// Maximum results to return from the query. - [SerializedMethod] public async Task> AddressToLocations(string address, List? categories, string? countryCode, bool? forStorage, Point? location, LocationType? locationType, string? magicKey, int? maxLocations) @@ -1457,7 +1553,8 @@ public async Task> AddressToLocations(string address, Lis } /// - /// Sends a request to the ArcGIS REST geocode resource to find candidates for a single address specified in the address parameter. + /// Sends a request to the ArcGIS REST geocode resource to find candidates for a single address specified in the + /// address parameter. /// Uses the default ESRI geolocation service. /// /// the various address fields accepted by the corresponding geocode service. @@ -1477,9 +1574,11 @@ public async Task> AddressToLocations(string address, Lis /// A suggestLocations result ID (magicKey). Used to query for a specific results information. /// Maximum results to return from the query. /// - /// The list of fields included in the returned result set. This list is a comma delimited list of field names. If you specify the shape field in the list of return fields, it is ignored. For non-intersection addresses you can specify the candidate fields as defined in the geocode service. For intersection addresses you can specify the intersection candidate fields. + /// The list of fields included in the returned result set. This list is a comma delimited list of field names. If you + /// specify the shape field in the list of return fields, it is ignored. For non-intersection addresses you can specify + /// the candidate fields as defined in the geocode service. For intersection addresses you can specify the intersection + /// candidate fields. /// - [SerializedMethod] public async Task> AddressToLocations(string address, List? categories, string? countryCode, bool? forStorage, Point? location, LocationType? locationType, string? magicKey, int? maxLocations, List? outFields) @@ -1489,7 +1588,8 @@ public async Task> AddressToLocations(string address, Lis } /// - /// Sends a request to the ArcGIS REST geocode resource to find candidates for a single address specified in the address parameter. + /// Sends a request to the ArcGIS REST geocode resource to find candidates for a single address specified in the + /// address parameter. /// Uses the default ESRI geolocation service. /// /// the various address fields accepted by the corresponding geocode service. @@ -1509,12 +1609,16 @@ public async Task> AddressToLocations(string address, Lis /// A suggestLocations result ID (magicKey). Used to query for a specific results information. /// Maximum results to return from the query. /// - /// The list of fields included in the returned result set. This list is a comma delimited list of field names. If you specify the shape field in the list of return fields, it is ignored. For non-intersection addresses you can specify the candidate fields as defined in the geocode service. For intersection addresses you can specify the intersection candidate fields. + /// The list of fields included in the returned result set. This list is a comma delimited list of field names. If you + /// specify the shape field in the list of return fields, it is ignored. For non-intersection addresses you can specify + /// the candidate fields as defined in the geocode service. For intersection addresses you can specify the intersection + /// candidate fields. /// /// - /// The spatial reference of the output geometries. If not specified, the output geometries are in the spatial reference of the input geometries when performing a reverse geocode and in the default spatial reference returned by the service if finding locations by address. + /// The spatial reference of the output geometries. If not specified, the output geometries are in the spatial + /// reference of the input geometries when performing a reverse geocode and in the default spatial reference returned + /// by the service if finding locations by address. /// - [SerializedMethod] public async Task> AddressToLocations(string address, List? categories, string? countryCode, bool? forStorage, Point? location, LocationType? locationType, string? magicKey, int? maxLocations, List? outFields, @@ -1525,7 +1629,8 @@ public async Task> AddressToLocations(string address, Lis } /// - /// Sends a request to the ArcGIS REST geocode resource to find candidates for a single address specified in the address parameter. + /// Sends a request to the ArcGIS REST geocode resource to find candidates for a single address specified in the + /// address parameter. /// Uses the default ESRI geolocation service. /// /// the various address fields accepted by the corresponding geocode service. @@ -1545,15 +1650,19 @@ public async Task> AddressToLocations(string address, Lis /// A suggestLocations result ID (magicKey). Used to query for a specific results information. /// Maximum results to return from the query. /// - /// The list of fields included in the returned result set. This list is a comma delimited list of field names. If you specify the shape field in the list of return fields, it is ignored. For non-intersection addresses you can specify the candidate fields as defined in the geocode service. For intersection addresses you can specify the intersection candidate fields. + /// The list of fields included in the returned result set. This list is a comma delimited list of field names. If you + /// specify the shape field in the list of return fields, it is ignored. For non-intersection addresses you can specify + /// the candidate fields as defined in the geocode service. For intersection addresses you can specify the intersection + /// candidate fields. /// /// - /// The spatial reference of the output geometries. If not specified, the output geometries are in the spatial reference of the input geometries when performing a reverse geocode and in the default spatial reference returned by the service if finding locations by address. + /// The spatial reference of the output geometries. If not specified, the output geometries are in the spatial + /// reference of the input geometries when performing a reverse geocode and in the default spatial reference returned + /// by the service if finding locations by address. /// /// /// Defines the extent within which the geocode server will search. Requires ArcGIS Server version 10.1 or greater. /// - [SerializedMethod] public async Task> AddressToLocations(string address, List? categories, string? countryCode, bool? forStorage, Point? location, LocationType? locationType, string? magicKey, int? maxLocations, List? outFields, @@ -1564,7 +1673,8 @@ public async Task> AddressToLocations(string address, Lis } /// - /// Sends a request to the ArcGIS REST geocode resource to find candidates for a single address specified in the address parameter. + /// Sends a request to the ArcGIS REST geocode resource to find candidates for a single address specified in the + /// address parameter. /// Uses the default ESRI geolocation service. /// /// the various address fields accepted by the corresponding geocode service. @@ -1584,16 +1694,21 @@ public async Task> AddressToLocations(string address, Lis /// A suggestLocations result ID (magicKey). Used to query for a specific results information. /// Maximum results to return from the query. /// - /// The list of fields included in the returned result set. This list is a comma delimited list of field names. If you specify the shape field in the list of return fields, it is ignored. For non-intersection addresses you can specify the candidate fields as defined in the geocode service. For intersection addresses you can specify the intersection candidate fields. + /// The list of fields included in the returned result set. This list is a comma delimited list of field names. If you + /// specify the shape field in the list of return fields, it is ignored. For non-intersection addresses you can specify + /// the candidate fields as defined in the geocode service. For intersection addresses you can specify the intersection + /// candidate fields. /// /// - /// The spatial reference of the output geometries. If not specified, the output geometries are in the spatial reference of the input geometries when performing a reverse geocode and in the default spatial reference returned by the service if finding locations by address. + /// The spatial reference of the output geometries. If not specified, the output geometries are in the spatial + /// reference of the input geometries when performing a reverse geocode and in the default spatial reference returned + /// by the service if finding locations by address. /// /// /// Defines the extent within which the geocode server will search. Requires ArcGIS Server version 10.1 or greater. /// /// - /// Additional options to be used for the data request + /// Additional options to be used for the data request /// /// /// The name of the single line address field for the ArcGIS Locator Service (for ArcGIS 10+), defaults to 'address'. @@ -1610,7 +1725,8 @@ public async Task> AddressToLocations(string address, Lis } /// - /// Sends a request to the ArcGIS REST geocode resource to find candidates for a single address specified in the address parameter. + /// Sends a request to the ArcGIS REST geocode resource to find candidates for a single address specified in the + /// address parameter. /// /// URL to the ArcGIS Server REST resource that represents a locator service. /// the various address fields accepted by the corresponding geocode service. @@ -1621,7 +1737,8 @@ public Task> AddressToLocations(string url, string addres } /// - /// Sends a request to the ArcGIS REST geocode resource to find candidates for a single address specified in the address parameter. + /// Sends a request to the ArcGIS REST geocode resource to find candidates for a single address specified in the + /// address parameter. /// /// URL to the ArcGIS Server REST resource that represents a locator service. /// the various address fields accepted by the corresponding geocode service. @@ -1637,7 +1754,8 @@ public Task> AddressToLocations(string url, string addres } /// - /// Sends a request to the ArcGIS REST geocode resource to find candidates for a single address specified in the address parameter. + /// Sends a request to the ArcGIS REST geocode resource to find candidates for a single address specified in the + /// address parameter. /// /// URL to the ArcGIS Server REST resource that represents a locator service. /// the various address fields accepted by the corresponding geocode service. @@ -1657,7 +1775,8 @@ public Task> AddressToLocations(string url, string addres } /// - /// Sends a request to the ArcGIS REST geocode resource to find candidates for a single address specified in the address parameter. + /// Sends a request to the ArcGIS REST geocode resource to find candidates for a single address specified in the + /// address parameter. /// /// URL to the ArcGIS Server REST resource that represents a locator service. /// the various address fields accepted by the corresponding geocode service. @@ -1678,7 +1797,8 @@ public Task> AddressToLocations(string url, string addres } /// - /// Sends a request to the ArcGIS REST geocode resource to find candidates for a single address specified in the address parameter. + /// Sends a request to the ArcGIS REST geocode resource to find candidates for a single address specified in the + /// address parameter. /// /// URL to the ArcGIS Server REST resource that represents a locator service. /// the various address fields accepted by the corresponding geocode service. @@ -1700,7 +1820,8 @@ public Task> AddressToLocations(string url, string addres } /// - /// Sends a request to the ArcGIS REST geocode resource to find candidates for a single address specified in the address parameter. + /// Sends a request to the ArcGIS REST geocode resource to find candidates for a single address specified in the + /// address parameter. /// /// URL to the ArcGIS Server REST resource that represents a locator service. /// the various address fields accepted by the corresponding geocode service. @@ -1726,7 +1847,8 @@ public Task> AddressToLocations(string url, string addres } /// - /// Sends a request to the ArcGIS REST geocode resource to find candidates for a single address specified in the address parameter. + /// Sends a request to the ArcGIS REST geocode resource to find candidates for a single address specified in the + /// address parameter. /// /// URL to the ArcGIS Server REST resource that represents a locator service. /// the various address fields accepted by the corresponding geocode service. @@ -1753,7 +1875,8 @@ public Task> AddressToLocations(string url, string addres } /// - /// Sends a request to the ArcGIS REST geocode resource to find candidates for a single address specified in the address parameter. + /// Sends a request to the ArcGIS REST geocode resource to find candidates for a single address specified in the + /// address parameter. /// /// URL to the ArcGIS Server REST resource that represents a locator service. /// the various address fields accepted by the corresponding geocode service. @@ -1781,7 +1904,8 @@ public Task> AddressToLocations(string url, string addres } /// - /// Sends a request to the ArcGIS REST geocode resource to find candidates for a single address specified in the address parameter. + /// Sends a request to the ArcGIS REST geocode resource to find candidates for a single address specified in the + /// address parameter. /// /// URL to the ArcGIS Server REST resource that represents a locator service. /// the various address fields accepted by the corresponding geocode service. @@ -1801,7 +1925,10 @@ public Task> AddressToLocations(string url, string addres /// A suggestLocations result ID (magicKey). Used to query for a specific results information. /// Maximum results to return from the query. /// - /// The list of fields included in the returned result set. This list is a comma delimited list of field names. If you specify the shape field in the list of return fields, it is ignored. For non-intersection addresses you can specify the candidate fields as defined in the geocode service. For intersection addresses you can specify the intersection candidate fields. + /// The list of fields included in the returned result set. This list is a comma delimited list of field names. If you + /// specify the shape field in the list of return fields, it is ignored. For non-intersection addresses you can specify + /// the candidate fields as defined in the geocode service. For intersection addresses you can specify the intersection + /// candidate fields. /// public Task> AddressToLocations(string url, string address, List? categories, string? countryCode, bool? forStorage, Point? location, @@ -1813,7 +1940,8 @@ public Task> AddressToLocations(string url, string addres } /// - /// Sends a request to the ArcGIS REST geocode resource to find candidates for a single address specified in the address parameter. + /// Sends a request to the ArcGIS REST geocode resource to find candidates for a single address specified in the + /// address parameter. /// /// URL to the ArcGIS Server REST resource that represents a locator service. /// the various address fields accepted by the corresponding geocode service. @@ -1833,10 +1961,15 @@ public Task> AddressToLocations(string url, string addres /// A suggestLocations result ID (magicKey). Used to query for a specific results information. /// Maximum results to return from the query. /// - /// The list of fields included in the returned result set. This list is a comma delimited list of field names. If you specify the shape field in the list of return fields, it is ignored. For non-intersection addresses you can specify the candidate fields as defined in the geocode service. For intersection addresses you can specify the intersection candidate fields. + /// The list of fields included in the returned result set. This list is a comma delimited list of field names. If you + /// specify the shape field in the list of return fields, it is ignored. For non-intersection addresses you can specify + /// the candidate fields as defined in the geocode service. For intersection addresses you can specify the intersection + /// candidate fields. /// /// - /// The spatial reference of the output geometries. If not specified, the output geometries are in the spatial reference of the input geometries when performing a reverse geocode and in the default spatial reference returned by the service if finding locations by address. + /// The spatial reference of the output geometries. If not specified, the output geometries are in the spatial + /// reference of the input geometries when performing a reverse geocode and in the default spatial reference returned + /// by the service if finding locations by address. /// public Task> AddressToLocations(string url, string address, List? categories, string? countryCode, bool? forStorage, Point? location, @@ -1848,7 +1981,8 @@ public Task> AddressToLocations(string url, string addres } /// - /// Sends a request to the ArcGIS REST geocode resource to find candidates for a single address specified in the address parameter. + /// Sends a request to the ArcGIS REST geocode resource to find candidates for a single address specified in the + /// address parameter. /// /// URL to the ArcGIS Server REST resource that represents a locator service. /// the various address fields accepted by the corresponding geocode service. @@ -1868,10 +2002,15 @@ public Task> AddressToLocations(string url, string addres /// A suggestLocations result ID (magicKey). Used to query for a specific results information. /// Maximum results to return from the query. /// - /// The list of fields included in the returned result set. This list is a comma delimited list of field names. If you specify the shape field in the list of return fields, it is ignored. For non-intersection addresses you can specify the candidate fields as defined in the geocode service. For intersection addresses you can specify the intersection candidate fields. + /// The list of fields included in the returned result set. This list is a comma delimited list of field names. If you + /// specify the shape field in the list of return fields, it is ignored. For non-intersection addresses you can specify + /// the candidate fields as defined in the geocode service. For intersection addresses you can specify the intersection + /// candidate fields. /// /// - /// The spatial reference of the output geometries. If not specified, the output geometries are in the spatial reference of the input geometries when performing a reverse geocode and in the default spatial reference returned by the service if finding locations by address. + /// The spatial reference of the output geometries. If not specified, the output geometries are in the spatial + /// reference of the input geometries when performing a reverse geocode and in the default spatial reference returned + /// by the service if finding locations by address. /// /// /// Defines the extent within which the geocode server will search. Requires ArcGIS Server version 10.1 or greater. @@ -1887,7 +2026,8 @@ public Task> AddressToLocations(string url, string addres } /// - /// Sends a request to the ArcGIS REST geocode resource to find candidates for a single address specified in the address parameter. + /// Sends a request to the ArcGIS REST geocode resource to find candidates for a single address specified in the + /// address parameter. /// /// URL to the ArcGIS Server REST resource that represents a locator service. /// the various address fields accepted by the corresponding geocode service. @@ -1907,16 +2047,21 @@ public Task> AddressToLocations(string url, string addres /// A suggestLocations result ID (magicKey). Used to query for a specific results information. /// Maximum results to return from the query. /// - /// The list of fields included in the returned result set. This list is a comma delimited list of field names. If you specify the shape field in the list of return fields, it is ignored. For non-intersection addresses you can specify the candidate fields as defined in the geocode service. For intersection addresses you can specify the intersection candidate fields. + /// The list of fields included in the returned result set. This list is a comma delimited list of field names. If you + /// specify the shape field in the list of return fields, it is ignored. For non-intersection addresses you can specify + /// the candidate fields as defined in the geocode service. For intersection addresses you can specify the intersection + /// candidate fields. /// /// - /// The spatial reference of the output geometries. If not specified, the output geometries are in the spatial reference of the input geometries when performing a reverse geocode and in the default spatial reference returned by the service if finding locations by address. + /// The spatial reference of the output geometries. If not specified, the output geometries are in the spatial + /// reference of the input geometries when performing a reverse geocode and in the default spatial reference returned + /// by the service if finding locations by address. /// /// /// Defines the extent within which the geocode server will search. Requires ArcGIS Server version 10.1 or greater. /// /// - /// Additional options to be used for the data request + /// Additional options to be used for the data request /// public Task> AddressToLocations(string url, string address, List? categories, string? countryCode, bool? forStorage, Point? location, @@ -1930,7 +2075,8 @@ public Task> AddressToLocations(string url, string addres } /// - /// Sends a request to the ArcGIS REST geocode resource to find candidates for a single address specified in the address parameter. + /// Sends a request to the ArcGIS REST geocode resource to find candidates for a single address specified in the + /// address parameter. /// /// URL to the ArcGIS Server REST resource that represents a locator service. /// the various address fields accepted by the corresponding geocode service. @@ -1950,16 +2096,21 @@ public Task> AddressToLocations(string url, string addres /// A suggestLocations result ID (magicKey). Used to query for a specific results information. /// Maximum results to return from the query. /// - /// The list of fields included in the returned result set. This list is a comma delimited list of field names. If you specify the shape field in the list of return fields, it is ignored. For non-intersection addresses you can specify the candidate fields as defined in the geocode service. For intersection addresses you can specify the intersection candidate fields. + /// The list of fields included in the returned result set. This list is a comma delimited list of field names. If you + /// specify the shape field in the list of return fields, it is ignored. For non-intersection addresses you can specify + /// the candidate fields as defined in the geocode service. For intersection addresses you can specify the intersection + /// candidate fields. /// /// - /// The spatial reference of the output geometries. If not specified, the output geometries are in the spatial reference of the input geometries when performing a reverse geocode and in the default spatial reference returned by the service if finding locations by address. + /// The spatial reference of the output geometries. If not specified, the output geometries are in the spatial + /// reference of the input geometries when performing a reverse geocode and in the default spatial reference returned + /// by the service if finding locations by address. /// /// /// Defines the extent within which the geocode server will search. Requires ArcGIS Server version 10.1 or greater. /// /// - /// Additional options to be used for the data request + /// Additional options to be used for the data request /// /// /// The name of the single line address field for the ArcGIS Locator Service (for ArcGIS 10+), defaults to 'address'. @@ -1985,13 +2136,14 @@ public Task> AddressToLocations(string url, string addres /// Uses the default ESRI geolocation service. ///
/// - /// The point at which to search for the closest address. The location should be in the same spatial reference as that of the geocode service. + /// The point at which to search for the closest address. The location should be in the same spatial reference as that + /// of the geocode service. /// - [CodeGenerationIgnore] - [SerializedMethod] - public Task LocationToAddress(Point location) + /// The cancellation token to use for the operation. + public Task LocationToAddress(Point location, CancellationToken cancellationToken = default) { - return LocationToAddress(ESRIGeoLocationUrl, location); + return LocationToAddressImplementation(ESRIGeoLocationUrl, location, + null, null, null, cancellationToken); } /// @@ -1999,14 +2151,18 @@ public Task LocationToAddress(Point location) /// Uses the default ESRI geolocation service. /// /// - /// The point at which to search for the closest address. The location should be in the same spatial reference as that of the geocode service. + /// The point at which to search for the closest address. The location should be in the same spatial reference as that + /// of the geocode service. /// /// /// Define the type of location, either "street" or "rooftop", of the point returned from the World Geocoding Service. /// - public Task LocationToAddress(Point location, LocationType? locationType) + /// The cancellation token to use for the operation. + public Task LocationToAddress(Point location, LocationType? locationType, + CancellationToken cancellationToken = default) { - return LocationToAddress(ESRIGeoLocationUrl, location, locationType); + return LocationToAddressImplementation(ESRIGeoLocationUrl, location, locationType, + null, null, cancellationToken); } /// @@ -2014,18 +2170,23 @@ public Task LocationToAddress(Point location, LocationType? lo /// Uses the default ESRI geolocation service. /// /// - /// The point at which to search for the closest address. The location should be in the same spatial reference as that of the geocode service. + /// The point at which to search for the closest address. The location should be in the same spatial reference as that + /// of the geocode service. /// /// /// Define the type of location, either "street" or "rooftop", of the point returned from the World Geocoding Service. /// /// - /// The spatial reference of the output geometries. If not specified, the output geometries are in the spatial reference of the input geometries when performing a reverse geocode and in the default spatial reference returned by the service if finding locations by address. + /// The spatial reference of the output geometries. If not specified, the output geometries are in the spatial + /// reference of the input geometries when performing a reverse geocode and in the default spatial reference returned + /// by the service if finding locations by address. /// + /// The cancellation token to use for the operation. public Task LocationToAddress(Point location, LocationType? locationType, - SpatialReference? outSpatialReference) + SpatialReference? outSpatialReference, CancellationToken cancellationToken = default) { - return LocationToAddress(ESRIGeoLocationUrl, location, locationType, outSpatialReference); + return LocationToAddressImplementation(ESRIGeoLocationUrl, location, locationType, outSpatialReference, + null, cancellationToken); } /// @@ -2033,21 +2194,27 @@ public Task LocationToAddress(Point location, LocationType? lo /// Uses the default ESRI geolocation service. /// /// - /// The point at which to search for the closest address. The location should be in the same spatial reference as that of the geocode service. + /// The point at which to search for the closest address. The location should be in the same spatial reference as that + /// of the geocode service. /// /// /// Define the type of location, either "street" or "rooftop", of the point returned from the World Geocoding Service. /// /// - /// The spatial reference of the output geometries. If not specified, the output geometries are in the spatial reference of the input geometries when performing a reverse geocode and in the default spatial reference returned by the service if finding locations by address. + /// The spatial reference of the output geometries. If not specified, the output geometries are in the spatial + /// reference of the input geometries when performing a reverse geocode and in the default spatial reference returned + /// by the service if finding locations by address. /// /// - /// Additional options to be used for the data request + /// Additional options to be used for the data request /// + /// The cancellation token to use for the operation. public Task LocationToAddress(Point location, LocationType? locationType, - SpatialReference? outSpatialReference, RequestOptions? requestOptions) + SpatialReference? outSpatialReference, RequestOptions? requestOptions, + CancellationToken cancellationToken = default) { - return LocationToAddress(ESRIGeoLocationUrl, location, locationType, outSpatialReference, requestOptions); + return LocationToAddressImplementation(ESRIGeoLocationUrl, location, locationType, + outSpatialReference, requestOptions, cancellationToken); } /// @@ -2055,11 +2222,15 @@ public Task LocationToAddress(Point location, LocationType? lo /// /// URL to the ArcGIS Server REST resource that represents a locator service. /// - /// The point at which to search for the closest address. The location should be in the same spatial reference as that of the geocode service. + /// The point at which to search for the closest address. The location should be in the same spatial reference as that + /// of the geocode service. /// - public Task LocationToAddress(string url, Point location) + /// The cancellation token to use for the operation. + public Task LocationToAddress(string url, Point location, + CancellationToken cancellationToken = default) { - return InvokeAsync(nameof(LocationService), parameters: [url, location, null, null, null]); + return LocationToAddressImplementation(url, location, null, null, + null, cancellationToken); } /// @@ -2067,14 +2238,18 @@ public Task LocationToAddress(string url, Point location) /// /// URL to the ArcGIS Server REST resource that represents a locator service. /// - /// The point at which to search for the closest address. The location should be in the same spatial reference as that of the geocode service. + /// The point at which to search for the closest address. The location should be in the same spatial reference as that + /// of the geocode service. /// /// /// Define the type of location, either "street" or "rooftop", of the point returned from the World Geocoding Service. /// - public Task LocationToAddress(string url, Point location, LocationType? locationType) + /// The cancellation token to use for the operation. + public Task LocationToAddress(string url, Point location, LocationType? locationType, + CancellationToken cancellationToken = default) { - return InvokeAsync(nameof(LocationService), parameters: [url, location, locationType, null, null]); + return LocationToAddressImplementation(url, location, locationType, null, + null, cancellationToken); } /// @@ -2082,18 +2257,23 @@ public Task LocationToAddress(string url, Point location, Loca /// /// URL to the ArcGIS Server REST resource that represents a locator service. /// - /// The point at which to search for the closest address. The location should be in the same spatial reference as that of the geocode service. + /// The point at which to search for the closest address. The location should be in the same spatial reference as that + /// of the geocode service. /// /// /// Define the type of location, either "street" or "rooftop", of the point returned from the World Geocoding Service. /// /// - /// The spatial reference of the output geometries. If not specified, the output geometries are in the spatial reference of the input geometries when performing a reverse geocode and in the default spatial reference returned by the service if finding locations by address. + /// The spatial reference of the output geometries. If not specified, the output geometries are in the spatial + /// reference of the input geometries when performing a reverse geocode and in the default spatial reference returned + /// by the service if finding locations by address. /// + /// The cancellation token to use for the operation. public Task LocationToAddress(string url, Point location, LocationType? locationType, - SpatialReference? outSpatialReference) + SpatialReference? outSpatialReference, CancellationToken cancellationToken = default) { - return LocationToAddress(url, location, locationType, outSpatialReference, null); + return LocationToAddressImplementation(url, location, locationType, outSpatialReference, null, + cancellationToken); } /// @@ -2101,23 +2281,27 @@ public Task LocationToAddress(string url, Point location, Loca /// /// URL to the ArcGIS Server REST resource that represents a locator service. /// - /// The point at which to search for the closest address. The location should be in the same spatial reference as that of the geocode service. + /// The point at which to search for the closest address. The location should be in the same spatial reference as that + /// of the geocode service. /// /// /// Define the type of location, either "street" or "rooftop", of the point returned from the World Geocoding Service. /// /// - /// The spatial reference of the output geometries. If not specified, the output geometries are in the spatial reference of the input geometries when performing a reverse geocode and in the default spatial reference returned by the service if finding locations by address. + /// The spatial reference of the output geometries. If not specified, the output geometries are in the spatial + /// reference of the input geometries when performing a reverse geocode and in the default spatial reference returned + /// by the service if finding locations by address. /// /// - /// Additional options to be used for the data request + /// Additional options to be used for the data request /// - [SerializedMethod] - public async Task LocationToAddress(string url, Point location, LocationType? locationType, - SpatialReference? outSpatialReference, RequestOptions? requestOptions) + /// The cancellation token to use for the operation. + public Task LocationToAddress(string url, Point location, LocationType? locationType, + SpatialReference? outSpatialReference, RequestOptions? requestOptions, + CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(LocationService), parameters: [url, location, locationType, outSpatialReference, - requestOptions]); + return LocationToAddressImplementation(url, location, locationType, outSpatialReference, requestOptions, + cancellationToken); } #endregion @@ -2130,16 +2314,18 @@ public async Task LocationToAddress(string url, Point location /// Uses the default ESRI geolocation service. ///
/// - /// Defines a normalized location point that is used to sort geocoding candidates based upon their proximity to the given location. + /// Defines a normalized location point that is used to sort geocoding candidates based upon their proximity to the + /// given location. /// /// /// The input text entered by a user which is used by the suggest operation to generate a list of possible matches. /// - [CodeGenerationIgnore] - [SerializedMethod] - public async Task> SuggestLocations(Point location, string text) + /// The cancellation token. + public Task> SuggestLocations(Point location, string text, + CancellationToken cancellationToken = default) { - return await SuggestLocations(ESRIGeoLocationUrl, location, text); + return SuggestLocationsImplementation(ESRIGeoLocationUrl, location, text, null, null, + cancellationToken); } /// @@ -2147,19 +2333,22 @@ public async Task> SuggestLocations(Point location, strin /// Uses the default ESRI geolocation service. /// /// - /// Defines a normalized location point that is used to sort geocoding candidates based upon their proximity to the given location. + /// Defines a normalized location point that is used to sort geocoding candidates based upon their proximity to the + /// given location. /// /// /// The input text entered by a user which is used by the suggest operation to generate a list of possible matches. /// /// - /// A place or address type which can be used to filter suggest results. The parameter supports input of single category values or multiple comma-separated values. + /// A place or address type which can be used to filter suggest results. The parameter supports input of single + /// category values or multiple comma-separated values. /// - [SerializedMethod] - public async Task> SuggestLocations(Point location, string text, - List? categories) + /// The cancellation token. + public Task> SuggestLocations(Point location, string text, + List? categories, CancellationToken cancellationToken = default) { - return await SuggestLocations(ESRIGeoLocationUrl, location, text, categories); + return SuggestLocationsImplementation(ESRIGeoLocationUrl, location, text, categories, + null, cancellationToken); } /// @@ -2167,22 +2356,26 @@ public async Task> SuggestLocations(Point location, strin /// Uses the default ESRI geolocation service. /// /// - /// Defines a normalized location point that is used to sort geocoding candidates based upon their proximity to the given location. + /// Defines a normalized location point that is used to sort geocoding candidates based upon their proximity to the + /// given location. /// /// /// The input text entered by a user which is used by the suggest operation to generate a list of possible matches. /// /// - /// A place or address type which can be used to filter suggest results. The parameter supports input of single category values or multiple comma-separated values. + /// A place or address type which can be used to filter suggest results. The parameter supports input of single + /// category values or multiple comma-separated values. /// /// - /// Additional options to be used for the data request + /// Additional options to be used for the data request /// - [SerializedMethod] - public async Task> SuggestLocations(Point location, string text, - List? categories, RequestOptions? requestOptions) + /// The cancellation token. + public Task> SuggestLocations(Point location, string text, + List? categories, RequestOptions? requestOptions, + CancellationToken cancellationToken = default) { - return await SuggestLocations(ESRIGeoLocationUrl, location, text, categories, requestOptions); + return SuggestLocationsImplementation(ESRIGeoLocationUrl, location, text, categories, requestOptions, + cancellationToken); } /// @@ -2190,15 +2383,18 @@ public async Task> SuggestLocations(Point location, strin /// /// URL to the ArcGIS Server REST resource that represents a locator service. /// - /// Defines a normalized location point that is used to sort geocoding candidates based upon their proximity to the given location. + /// Defines a normalized location point that is used to sort geocoding candidates based upon their proximity to the + /// given location. /// /// /// The input text entered by a user which is used by the suggest operation to generate a list of possible matches. /// - [SerializedMethod] - public async Task> SuggestLocations(string url, Point location, string text) + /// The cancellation token. + public Task> SuggestLocations(string url, Point location, string text, + CancellationToken cancellationToken = default) { - return await InvokeAsync>(nameof(LocationService), parameters: [url, location, text, null, null]); + return SuggestLocationsImplementation(url, location, text, null, null, + cancellationToken); } /// @@ -2206,19 +2402,21 @@ public async Task> SuggestLocations(string url, Point loc /// /// URL to the ArcGIS Server REST resource that represents a locator service. /// - /// Defines a normalized location point that is used to sort geocoding candidates based upon their proximity to the given location. + /// Defines a normalized location point that is used to sort geocoding candidates based upon their proximity to the + /// given location. /// /// /// The input text entered by a user which is used by the suggest operation to generate a list of possible matches. /// /// - /// A place or address type which can be used to filter suggest results. The parameter supports input of single category values or multiple comma-separated values. + /// A place or address type which can be used to filter suggest results. The parameter supports input of single + /// category values or multiple comma-separated values. /// - [SerializedMethod] - public async Task> SuggestLocations(string url, Point location, string text, - List? categories) + /// The cancellation token. + public Task> SuggestLocations(string url, Point location, string text, + List? categories, CancellationToken cancellationToken = default) { - return await InvokeAsync>(nameof(LocationService), parameters: [url, location, text, categories, null]); + return SuggestLocationsImplementation(url, location, text, categories, null, cancellationToken); } /// @@ -2226,23 +2424,25 @@ public async Task> SuggestLocations(string url, Point loc /// /// URL to the ArcGIS Server REST resource that represents a locator service. /// - /// Defines a normalized location point that is used to sort geocoding candidates based upon their proximity to the given location. + /// Defines a normalized location point that is used to sort geocoding candidates based upon their proximity to the + /// given location. /// /// /// The input text entered by a user which is used by the suggest operation to generate a list of possible matches. /// /// - /// A place or address type which can be used to filter suggest results. The parameter supports input of single category values or multiple comma-separated values. + /// A place or address type which can be used to filter suggest results. The parameter supports input of single + /// category values or multiple comma-separated values. /// /// - /// Additional options to be used for the data request + /// Additional options to be used for the data request /// - [SerializedMethod] - public async Task> SuggestLocations(string url, Point location, string text, - List? categories, RequestOptions? requestOptions) + /// The cancellation token. + public Task> SuggestLocations(string url, Point location, string text, + List? categories, RequestOptions? requestOptions, + CancellationToken cancellationToken = default) { - return await InvokeAsync>(nameof(LocationService), parameters: [url, location, text, categories, - requestOptions]); + return SuggestLocationsImplementation(url, location, text, categories, requestOptions, cancellationToken); } #endregion diff --git a/src/dymaptic.GeoBlazor.Core/Model/ProjectionEngine.cs b/src/dymaptic.GeoBlazor.Core/Model/ProjectionEngine.cs index 544a2f342..2015fea89 100644 --- a/src/dymaptic.GeoBlazor.Core/Model/ProjectionEngine.cs +++ b/src/dymaptic.GeoBlazor.Core/Model/ProjectionEngine.cs @@ -1,4 +1,4 @@ -namespace dymaptic.GeoBlazor.Core.Model; +namespace dymaptic.GeoBlazor.Core.Model; /// /// A client-side projection engine for converting geometries from one SpatialReference to another. When projecting geometries the starting spatial reference must be specified on the input geometry. You can specify a specific geographic (datum) transformation for the project operation, or accept the default transformation if one is needed. @@ -31,10 +31,11 @@ public ProjectionEngine(IAppValidator appValidator, IJSRuntime jsRuntime, JsModu /// /// A collection of projected geometries. /// - [SerializedMethod] - public async Task Project(Geometry[] geometries, SpatialReference spatialReference) + /// The cancellation token to use for the operation. + public Task Project(Geometry[] geometries, SpatialReference spatialReference, + CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(ProjectionEngine), parameters: [geometries, spatialReference, null]); + return Project(geometries, spatialReference, null, cancellationToken); } /// @@ -52,12 +53,13 @@ public ProjectionEngine(IAppValidator appValidator, IJSRuntime jsRuntime, JsModu /// /// A collection of projected geometries. /// + /// The cancellation token to use for the operation. [SerializedMethod] public async Task Project(Geometry[] geometries, SpatialReference spatialReference, - GeographicTransformation? geographicTransformation) + GeographicTransformation? geographicTransformation, CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(ProjectionEngine), parameters: [geometries, spatialReference, - geographicTransformation]); + return await InvokeAsync(nameof(ProjectionEngine), nameof(Project), + cancellationToken, geometries, spatialReference, geographicTransformation); } /// @@ -72,10 +74,11 @@ public ProjectionEngine(IAppValidator appValidator, IJSRuntime jsRuntime, JsModu /// /// A projected geometry. /// - [SerializedMethod] - public async Task Project(Geometry geometry, SpatialReference spatialReference) + /// The cancellation token to use for the operation. + public Task Project(Geometry geometry, SpatialReference spatialReference, + CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(ProjectionEngine), parameters: [geometry, spatialReference, null]); + return Project(geometry, spatialReference, null, cancellationToken); } /// @@ -93,12 +96,13 @@ public ProjectionEngine(IAppValidator appValidator, IJSRuntime jsRuntime, JsModu /// /// A projected geometry. /// + /// The cancellation token to use for the operation. [SerializedMethod] public async Task Project(Geometry geometry, SpatialReference spatialReference, - GeographicTransformation? geographicTransformation) + GeographicTransformation? geographicTransformation, CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(ProjectionEngine), parameters: [geometry, spatialReference, - geographicTransformation]); + return await InvokeAsync(nameof(ProjectionEngine), nameof(Project), + cancellationToken, geometry, spatialReference, geographicTransformation); } /// @@ -116,12 +120,14 @@ public ProjectionEngine(IAppValidator appValidator, IJSRuntime jsRuntime, JsModu /// /// A geographic transformation. /// + /// The cancellation token to use for the operation. [SerializedMethod] public async Task GetTransformation(SpatialReference inSpatialReference, - SpatialReference outSpatialReference, Extent extent) + SpatialReference outSpatialReference, Extent extent, CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(ProjectionEngine), parameters: [inSpatialReference, - outSpatialReference, extent]); + return await InvokeAsync(nameof(ProjectionEngine), nameof(GetTransformation), + cancellationToken, inSpatialReference, + outSpatialReference, extent); } /// @@ -139,11 +145,12 @@ public ProjectionEngine(IAppValidator appValidator, IJSRuntime jsRuntime, JsModu /// /// A collection of geographic transformation. /// + /// The cancellation token to use for the operation. [SerializedMethod] public async Task GetTransformations(SpatialReference inSpatialReference, - SpatialReference outSpatialReference, Extent extent) + SpatialReference outSpatialReference, Extent extent, CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(ProjectionEngine), - parameters: [inSpatialReference, outSpatialReference, extent]); + return await InvokeAsync(nameof(ProjectionEngine), + nameof(GetTransformations), cancellationToken, inSpatialReference, outSpatialReference, extent); } } \ No newline at end of file diff --git a/src/dymaptic.GeoBlazor.Core/Scripts/geometryEngine.ts b/src/dymaptic.GeoBlazor.Core/Scripts/geometryEngine.ts index 09048391c..03b79985c 100644 --- a/src/dymaptic.GeoBlazor.Core/Scripts/geometryEngine.ts +++ b/src/dymaptic.GeoBlazor.Core/Scripts/geometryEngine.ts @@ -3,27 +3,21 @@ import Polygon from "@arcgis/core/geometry/Polygon"; import Polyline from "@arcgis/core/geometry/Polyline"; import SpatialReference from "@arcgis/core/geometry/SpatialReference"; import Point from "@arcgis/core/geometry/Point"; -import { - DotNetExtent, - DotNetGeometry, - DotNetPoint, - DotNetPolygon, - DotNetPolyline -} from "./definitions"; +import {DotNetExtent, DotNetGeometry, DotNetPoint, DotNetPolygon, DotNetPolyline} from "./definitions"; import Extent from "@arcgis/core/geometry/Extent"; import {buildDotNetExtent, buildJsExtent} from "./extent"; import {buildDotNetPolygon, buildJsPolygon} from "./polygon"; import {buildDotNetGeometry, buildJsGeometry} from "./geometry"; import {buildDotNetPoint, buildJsPoint} from "./point"; import {buildDotNetPolyline, buildJsPathsOrRings, buildJsPolyline} from "./polyline"; +import Mesh from "@arcgis/core/geometry/Mesh"; +import Multipoint from "@arcgis/core/geometry/Multipoint"; +import {hasValue} from './geoBlazorCore'; +import BaseComponent from "./baseComponent"; import LinearUnits = __esri.LinearUnits; import SpatialReferenceInfo = __esri.SpatialReferenceInfo; import AreaUnits = __esri.AreaUnits; -import Mesh from "@arcgis/core/geometry/Mesh"; -import Multipoint from "@arcgis/core/geometry/Multipoint"; -import { hasValue } from './geoBlazorCore'; import GeometryUnion = __esri.GeometryUnion; -import BaseComponent from "./baseComponent"; export default class GeometryEngineWrapper extends BaseComponent { @@ -584,19 +578,19 @@ export default class GeometryEngineWrapper extends BaseComponent { let jsPolyline = buildJsPolyline(polyline) as Polyline; let path = jsPolyline.removePath(pathIndex); let newLine = buildDotNetPolyline(jsPolyline) as DotNetPolyline; - return { - geometry: newLine, - path: path?.map(p => buildDotNetPoint(p) as DotNetPoint) - } + let result: DotNetGeometry[] = []; + result.push(newLine); + result.push(...path?.map(p => buildDotNetPoint(p) as DotNetPoint) ?? []); + return result; } removePoint(geometry: DotNetGeometry, firstIndex: number, secondIndex: number): any | null { let jsGeometry = buildJsGeometry(geometry) as Geometry; let point = (jsGeometry as any).removePoint(firstIndex, secondIndex); - return { - geometry: buildDotNetGeometry(jsGeometry) as DotNetGeometry, - point: buildDotNetPoint(point) as DotNetPoint - }; + let result: DotNetGeometry[] = []; + result.push(buildDotNetGeometry(jsGeometry) as DotNetGeometry); + result.push(buildDotNetPoint(point) as DotNetPoint); + return result; } setPoint(geometry: DotNetGeometry, firstIndex: number, secondIndex: number, point: DotNetPoint) @@ -630,10 +624,10 @@ export default class GeometryEngineWrapper extends BaseComponent { removeRing(polygon: DotNetPolygon, index: number): any { let jsPolygon = buildJsPolygon(polygon) as Polygon; let ring = jsPolygon.removeRing(index); - return { - geometry: buildDotNetPolygon(jsPolygon) as DotNetPolygon, - path: ring?.map(p => buildDotNetPoint(p) as DotNetPoint) - }; + let result: DotNetGeometry[] = []; + result.push(buildDotNetPolygon(jsPolygon) as DotNetPolygon); + result.push(...ring?.map(p => buildDotNetPoint(p) as DotNetPoint) ?? []); + return result; } getExtentCenter(extent: DotNetExtent): DotNetPoint { From 532ab7e94a4a4bff738eca88eecae670a5d8b1a1 Mon Sep 17 00:00:00 2001 From: Tim Purdum Date: Fri, 23 Jan 2026 11:01:33 -0600 Subject: [PATCH 07/89] wip --- ...dymaptic.GeoBlazor.Core.Sample.Maui.csproj | 10 +- ....GeoBlazor.Core.Sample.OAuth.Client.csproj | 4 +- ...ymaptic.GeoBlazor.Core.Sample.OAuth.csproj | 2 +- ...maptic.GeoBlazor.Core.Sample.Shared.csproj | 6 +- ...zor.Core.Sample.TokenRefresh.Client.csproj | 4 +- ....GeoBlazor.Core.Sample.TokenRefresh.csproj | 2 +- ...dymaptic.GeoBlazor.Core.Sample.Wasm.csproj | 4 +- ...GeoBlazor.Core.Sample.WebApp.Client.csproj | 2 +- ...maptic.GeoBlazor.Core.Sample.WebApp.csproj | 2 +- .../SerializationGenerator.cs | 151 ++++-- .../ESBuildGenerator.cs | 1 - .../Components/Geometries/Point.cs | 90 +++- .../Components/Geometries/Point.gb.cs | 298 +++++------ .../Components/LocationService.cs | 2 +- .../Components/MapComponent.razor.cs | 5 +- .../{Components => Model}/GeometryEngine.cs | 289 +++++------ .../Model/LogicComponent.cs | 29 +- .../Model/ProjectionEngine.cs | 11 + ...arestPointResult.cs => ProximityResult.cs} | 10 +- .../Scripts/baseComponent.ts | 14 + .../Scripts/geometryEngine.ts | 256 +++++++--- .../Serialization/CoreSerializationData.cs | 11 + .../Serialization/JsSyncManager.cs | 206 ++++---- .../dymaptic.GeoBlazor.Core.csproj | 2 +- src/dymaptic.GeoBlazor.Core/esBuild.js | 7 +- ...eoBlazor.Core.SourceGenerator.Tests.csproj | 13 +- ...ptic.GeoBlazor.Core.Test.Automation.csproj | 14 +- .../Components/GeometryEngineTests.cs | 464 ++++++++++++++++- .../Components/ProjectionEngineTests.cs | 483 ++++++++++++++++++ ...c.GeoBlazor.Core.Test.Blazor.Shared.csproj | 10 +- .../dymaptic.GeoBlazor.Core.Test.Unit.csproj | 1 + ...c.GeoBlazor.Core.Test.WebApp.Client.csproj | 4 +- ...dymaptic.GeoBlazor.Core.Test.WebApp.csproj | 2 +- 33 files changed, 1780 insertions(+), 629 deletions(-) rename src/dymaptic.GeoBlazor.Core/{Components => Model}/GeometryEngine.cs (87%) rename src/dymaptic.GeoBlazor.Core/Results/{NearestPointResult.cs => ProximityResult.cs} (68%) create mode 100644 test/dymaptic.GeoBlazor.Core.Test.Blazor.Shared/Components/ProjectionEngineTests.cs diff --git a/samples/dymaptic.GeoBlazor.Core.Sample.Maui/dymaptic.GeoBlazor.Core.Sample.Maui.csproj b/samples/dymaptic.GeoBlazor.Core.Sample.Maui/dymaptic.GeoBlazor.Core.Sample.Maui.csproj index c2a930d9a..4e9891b73 100644 --- a/samples/dymaptic.GeoBlazor.Core.Sample.Maui/dymaptic.GeoBlazor.Core.Sample.Maui.csproj +++ b/samples/dymaptic.GeoBlazor.Core.Sample.Maui/dymaptic.GeoBlazor.Core.Sample.Maui.csproj @@ -52,11 +52,11 @@ - - - - - + + + + + diff --git a/samples/dymaptic.GeoBlazor.Core.Sample.OAuth/dymaptic.GeoBlazor.Core.Sample.OAuth.Client/dymaptic.GeoBlazor.Core.Sample.OAuth.Client.csproj b/samples/dymaptic.GeoBlazor.Core.Sample.OAuth/dymaptic.GeoBlazor.Core.Sample.OAuth.Client/dymaptic.GeoBlazor.Core.Sample.OAuth.Client.csproj index 212ce0fb7..8d1376c8b 100644 --- a/samples/dymaptic.GeoBlazor.Core.Sample.OAuth/dymaptic.GeoBlazor.Core.Sample.OAuth.Client/dymaptic.GeoBlazor.Core.Sample.OAuth.Client.csproj +++ b/samples/dymaptic.GeoBlazor.Core.Sample.OAuth/dymaptic.GeoBlazor.Core.Sample.OAuth.Client/dymaptic.GeoBlazor.Core.Sample.OAuth.Client.csproj @@ -7,8 +7,8 @@ - - + + diff --git a/samples/dymaptic.GeoBlazor.Core.Sample.OAuth/dymaptic.GeoBlazor.Core.Sample.OAuth/dymaptic.GeoBlazor.Core.Sample.OAuth.csproj b/samples/dymaptic.GeoBlazor.Core.Sample.OAuth/dymaptic.GeoBlazor.Core.Sample.OAuth/dymaptic.GeoBlazor.Core.Sample.OAuth.csproj index e99eb002d..bbc1cb3f3 100644 --- a/samples/dymaptic.GeoBlazor.Core.Sample.OAuth/dymaptic.GeoBlazor.Core.Sample.OAuth/dymaptic.GeoBlazor.Core.Sample.OAuth.csproj +++ b/samples/dymaptic.GeoBlazor.Core.Sample.OAuth/dymaptic.GeoBlazor.Core.Sample.OAuth/dymaptic.GeoBlazor.Core.Sample.OAuth.csproj @@ -8,7 +8,7 @@ - + diff --git a/samples/dymaptic.GeoBlazor.Core.Sample.Shared/dymaptic.GeoBlazor.Core.Sample.Shared.csproj b/samples/dymaptic.GeoBlazor.Core.Sample.Shared/dymaptic.GeoBlazor.Core.Sample.Shared.csproj index 83447a3cc..79c1b6a25 100644 --- a/samples/dymaptic.GeoBlazor.Core.Sample.Shared/dymaptic.GeoBlazor.Core.Sample.Shared.csproj +++ b/samples/dymaptic.GeoBlazor.Core.Sample.Shared/dymaptic.GeoBlazor.Core.Sample.Shared.csproj @@ -12,9 +12,9 @@ - - - + + + diff --git a/samples/dymaptic.GeoBlazor.Core.Sample.TokenRefresh/dymaptic.GeoBlazor.Core.Sample.TokenRefresh.Client/dymaptic.GeoBlazor.Core.Sample.TokenRefresh.Client.csproj b/samples/dymaptic.GeoBlazor.Core.Sample.TokenRefresh/dymaptic.GeoBlazor.Core.Sample.TokenRefresh.Client/dymaptic.GeoBlazor.Core.Sample.TokenRefresh.Client.csproj index 81c1ffe44..92b619fa4 100644 --- a/samples/dymaptic.GeoBlazor.Core.Sample.TokenRefresh/dymaptic.GeoBlazor.Core.Sample.TokenRefresh.Client/dymaptic.GeoBlazor.Core.Sample.TokenRefresh.Client.csproj +++ b/samples/dymaptic.GeoBlazor.Core.Sample.TokenRefresh/dymaptic.GeoBlazor.Core.Sample.TokenRefresh.Client/dymaptic.GeoBlazor.Core.Sample.TokenRefresh.Client.csproj @@ -7,8 +7,8 @@ - - + + diff --git a/samples/dymaptic.GeoBlazor.Core.Sample.TokenRefresh/dymaptic.GeoBlazor.Core.Sample.TokenRefresh/dymaptic.GeoBlazor.Core.Sample.TokenRefresh.csproj b/samples/dymaptic.GeoBlazor.Core.Sample.TokenRefresh/dymaptic.GeoBlazor.Core.Sample.TokenRefresh/dymaptic.GeoBlazor.Core.Sample.TokenRefresh.csproj index 07dfbd614..a44cc4a65 100644 --- a/samples/dymaptic.GeoBlazor.Core.Sample.TokenRefresh/dymaptic.GeoBlazor.Core.Sample.TokenRefresh/dymaptic.GeoBlazor.Core.Sample.TokenRefresh.csproj +++ b/samples/dymaptic.GeoBlazor.Core.Sample.TokenRefresh/dymaptic.GeoBlazor.Core.Sample.TokenRefresh/dymaptic.GeoBlazor.Core.Sample.TokenRefresh.csproj @@ -8,7 +8,7 @@ - + diff --git a/samples/dymaptic.GeoBlazor.Core.Sample.Wasm/dymaptic.GeoBlazor.Core.Sample.Wasm.csproj b/samples/dymaptic.GeoBlazor.Core.Sample.Wasm/dymaptic.GeoBlazor.Core.Sample.Wasm.csproj index 60b8c4c69..565eec732 100644 --- a/samples/dymaptic.GeoBlazor.Core.Sample.Wasm/dymaptic.GeoBlazor.Core.Sample.Wasm.csproj +++ b/samples/dymaptic.GeoBlazor.Core.Sample.Wasm/dymaptic.GeoBlazor.Core.Sample.Wasm.csproj @@ -5,8 +5,8 @@ - - + + diff --git a/samples/dymaptic.GeoBlazor.Core.Sample.WebApp/dymaptic.GeoBlazor.Core.Sample.WebApp.Client/dymaptic.GeoBlazor.Core.Sample.WebApp.Client.csproj b/samples/dymaptic.GeoBlazor.Core.Sample.WebApp/dymaptic.GeoBlazor.Core.Sample.WebApp.Client/dymaptic.GeoBlazor.Core.Sample.WebApp.Client.csproj index 5fb04f61b..7a531c895 100644 --- a/samples/dymaptic.GeoBlazor.Core.Sample.WebApp/dymaptic.GeoBlazor.Core.Sample.WebApp.Client/dymaptic.GeoBlazor.Core.Sample.WebApp.Client.csproj +++ b/samples/dymaptic.GeoBlazor.Core.Sample.WebApp/dymaptic.GeoBlazor.Core.Sample.WebApp.Client/dymaptic.GeoBlazor.Core.Sample.WebApp.Client.csproj @@ -7,7 +7,7 @@ - + diff --git a/samples/dymaptic.GeoBlazor.Core.Sample.WebApp/dymaptic.GeoBlazor.Core.Sample.WebApp/dymaptic.GeoBlazor.Core.Sample.WebApp.csproj b/samples/dymaptic.GeoBlazor.Core.Sample.WebApp/dymaptic.GeoBlazor.Core.Sample.WebApp/dymaptic.GeoBlazor.Core.Sample.WebApp.csproj index e1ac12030..deb1536e3 100644 --- a/samples/dymaptic.GeoBlazor.Core.Sample.WebApp/dymaptic.GeoBlazor.Core.Sample.WebApp/dymaptic.GeoBlazor.Core.Sample.WebApp.csproj +++ b/samples/dymaptic.GeoBlazor.Core.Sample.WebApp/dymaptic.GeoBlazor.Core.Sample.WebApp/dymaptic.GeoBlazor.Core.Sample.WebApp.csproj @@ -8,7 +8,7 @@ - + diff --git a/src/dymaptic.GeoBlazor.Core.SourceGenerator.Shared/SerializationGenerator.cs b/src/dymaptic.GeoBlazor.Core.SourceGenerator.Shared/SerializationGenerator.cs index cdd1c77fd..ae457e128 100644 --- a/src/dymaptic.GeoBlazor.Core.SourceGenerator.Shared/SerializationGenerator.cs +++ b/src/dymaptic.GeoBlazor.Core.SourceGenerator.Shared/SerializationGenerator.cs @@ -1,4 +1,5 @@ using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using System.Collections.Immutable; using System.Text; @@ -121,8 +122,8 @@ private static string GenerateExtensionMethods(Dictionary /// Convenience method to deserialize an to a specific type via protobuf. /// - public static async Task ReadJsStreamReferenceAsProtobuf(this IJSStreamReference jsStreamReference, - Type returnType, long maxAllowedSize = 1_000_000_000) + public static partial async Task ReadJsStreamReferenceAsProtobuf(this IJSStreamReference jsStreamReference, + Type returnType, long maxAllowedSize) { await using Stream stream = await jsStreamReference.OpenReadStreamAsync(maxAllowedSize); using MemoryStream memoryStream = new(); @@ -140,8 +141,8 @@ private static string GenerateExtensionMethods(Dictionary /// Convenience method to deserialize an to a specific coolection type via protobuf. /// - public static async Task ReadJsStreamReferenceAsProtobufCollection(this IJSStreamReference jsStreamReference, - Type returnType, long maxAllowedSize = 1_000_000_000) + public static partial async Task ReadJsStreamReferenceAsProtobufCollection(this IJSStreamReference jsStreamReference, + Type returnType, long maxAllowedSize) { await using Stream stream = await jsStreamReference.OpenReadStreamAsync(maxAllowedSize); using MemoryStream memoryStream = new(); @@ -181,7 +182,7 @@ private static string GenerateExtensionMethods(Dictionary /// Convenience method to generate a Protobuf serialized parameter. /// - public static object ToProtobufParameter(this object value, Type serializableType, bool isServer) + public static partial object ToProtobufParameter(this object value, Type serializableType, bool isServer) { MemoryStream memoryStream = new(); switch (serializableType.Name) @@ -193,7 +194,7 @@ public static object ToProtobufParameter(this object value, Type serializableTyp /// /// Convenience method to generate a Protobuf serialized collection parameter. /// - public static object ToProtobufCollectionParameter(this IList items, Type serializableType, bool isServer) + public static partial object ToProtobufCollectionParameter(this IList items, Type serializableType, bool isServer) { MemoryStream memoryStream = new(); string typeName = $"{serializableType.Name}Collection"; @@ -390,16 +391,25 @@ private static string GenerateSerializableMethodRecords( """); } - foreach (var classGroup in serializedMethodsCollection - .GroupBy(m => m.ClassName)) + List> classGroups = serializedMethodsCollection + .GroupBy(m => m.ClassName) + .ToList(); + + for (int i = 0; i < classGroups.Count; i++) { + IGrouping classGroup = classGroups[i]; + outputBuilder.AppendLine($$""" ["{{classGroup.Key}}"] = [ """); - foreach (var methodRecord in classGroup) + List methodRecords = classGroup.ToList(); + + for (int j = 0; j < methodRecords.Count; j++) { + SerializableMethodRecord methodRecord = methodRecords[j]; + if (methodRecord.Parameters.Values.Contains("T") || methodRecord.Parameters.Values.Contains("T?") || (methodRecord.ReturnType == "T") @@ -417,58 +427,58 @@ private static string GenerateSerializableMethodRecords( [ """); - foreach (var param in methodRecord.Parameters) + List parameters = methodRecord.Parameters.Values.ToList(); + + for (int k = 0; k < parameters.Count; k++) { - bool isNullable = param.Value.EndsWith("?"); - string value = isNullable ? param.Value.TrimEnd('?') : param.Value; - string isNullableText = isNullable ? "true" : "false"; - string? collectionType = null; + string param = parameters[k]; + outputBuilder.Append(GenerateSerializableParameterRecord(param, 24)); - if (value.EndsWith("[]")) + if (k < parameters.Count - 1) { - collectionType = value.Replace("[]", ""); + outputBuilder.AppendLine(","); } - else if (value.Contains("<") && value.Contains(">")) + else { - int genericStart = value.IndexOf("<", StringComparison.OrdinalIgnoreCase); - collectionType = value.Substring(genericStart + 1, value.Length - genericStart - 2); + outputBuilder.AppendLine(); } - - string collectionText = collectionType is null - ? "null" - : $"typeof({collectionType})"; - - outputBuilder.AppendLine($" new SerializableParameterRecord(typeof({value - }), {isNullableText}, {collectionText}),"); } if (methodRecord.ReturnType != null) { - string returnValue = methodRecord.ReturnType.TrimEnd('?'); - - bool isCollectionReturn = returnValue.EndsWith("[]") || - (returnValue.Contains("<") && returnValue.Contains(">")); - - string singleType = isCollectionReturn - ? returnValue.Contains("<") && returnValue.Contains(">") - ? $"typeof({returnValue.Substring( - returnValue.IndexOf("<", StringComparison.OrdinalIgnoreCase) + 1, - returnValue.Length - returnValue.IndexOf("<", StringComparison.OrdinalIgnoreCase) - 2) - })" - : $"typeof({returnValue.Replace("[]", "")})" - : "null"; - string isNullable = methodRecord.ReturnType.EndsWith("?") ? "true" : "false"; - - outputBuilder.AppendLine($" ], new SerializableParameterRecord(typeof({ - returnValue}), {isNullable}, {singleType})),"); + outputBuilder.AppendLine(" ],"); + outputBuilder.Append(GenerateSerializableParameterRecord(methodRecord.ReturnType, 20)); + + if (j < methodRecords.Count - 1) + { + outputBuilder.AppendLine("),"); + } + else + { + outputBuilder.AppendLine(")"); + } } else { - outputBuilder.AppendLine(" ])),"); + if (j < methodRecords.Count - 1) + { + outputBuilder.AppendLine(" ]),"); + } + else + { + outputBuilder.AppendLine(" ]"); + } } } - outputBuilder.AppendLine(" ],"); + if (i < classGroups.Count - 1) + { + outputBuilder.AppendLine(" ],"); + } + else + { + outputBuilder.AppendLine(" ]"); + } } outputBuilder.AppendLine(" };"); @@ -476,6 +486,42 @@ private static string GenerateSerializableMethodRecords( return outputBuilder.ToString(); } + private static string GenerateSerializableParameterRecord(string value, int indent) + { + bool isNullable = value.EndsWith("?"); + string trimmedValue = value.Replace("?", ""); // replace all ?, even in the inner single type + string isNullableText = isNullable ? "true" : "false"; + string? singleType = null; + + if (trimmedValue.EndsWith("[]")) // check against the trimmed version without ? + { + // use untrimmed value again to restore ? in the single types + int arrayStart = value.IndexOf("[", StringComparison.OrdinalIgnoreCase); + singleType = value.Substring(0, arrayStart); + } + else if (value.Contains("<") && trimmedValue.Contains(">")) + { + // use param.Value again to restore ? in the single types + int genericStart = value.IndexOf("<", StringComparison.OrdinalIgnoreCase); + singleType = value.Substring(genericStart + 1, trimmedValue.Length - genericStart - 2); + } + + bool singleTypeIsNullable = singleType?.EndsWith("?") == true; + string singleTypeIsNullableText = singleTypeIsNullable ? "true" : "false"; + singleType = singleType?.Replace("?", ""); + + string collectionText = singleType is null + ? "null" + : $"typeof({singleType})"; + + string padding = new string(' ', indent); + + return $""" + {padding}new SerializableParameterRecord(typeof({trimmedValue}), {isNullableText}, + {padding} {collectionText}, {singleTypeIsNullableText}) + """; + } + private static List ToSerializableMethodRecords(this BaseTypeDeclarationSyntax typeSyntax) { List methods = typeSyntax @@ -498,10 +544,21 @@ private static List ToSerializableMethodRecords(this B returnType = returnType.Substring(bracketIndex + 1, returnType.Length - bracketIndex - 2); } + // For static extension methods, skip the 'this' parameter + var parameters = method.Modifiers + .Any(m => m.IsKind(SyntaxKind.StaticKeyword)) + ? method.ParameterList.Parameters + .Where(p => !p.Modifiers + .Any(m => m.IsKind(SyntaxKind.ThisKeyword))) + .ToDictionary(p => p.Identifier.Text, + p => p.Type!.ToString()) + : method.ParameterList.Parameters + .ToDictionary(p => p.Identifier.Text, + p => p.Type!.ToString()); + SerializableMethodRecord record = new(typeSyntax.Identifier.Text, method.Identifier.Text, - method.ParameterList.Parameters.ToDictionary(p => p.Identifier.Text, - p => p.Type!.ToString()), + parameters, returnType); methodRecords.Add(record); diff --git a/src/dymaptic.GeoBlazor.Core.SourceGenerator/ESBuildGenerator.cs b/src/dymaptic.GeoBlazor.Core.SourceGenerator/ESBuildGenerator.cs index ea8489806..ca09f2737 100644 --- a/src/dymaptic.GeoBlazor.Core.SourceGenerator/ESBuildGenerator.cs +++ b/src/dymaptic.GeoBlazor.Core.SourceGenerator/ESBuildGenerator.cs @@ -163,7 +163,6 @@ private void LaunchESBuild(SourceProductionContext context) bool proBuildSuccess = false; // gets the ESBuild.cs script - // Only show dialog on full compilation builds, not design-time builds string[] coreArgs = [ "ESBuild.dll", diff --git a/src/dymaptic.GeoBlazor.Core/Components/Geometries/Point.cs b/src/dymaptic.GeoBlazor.Core/Components/Geometries/Point.cs index 3d5870811..3dd143115 100644 --- a/src/dymaptic.GeoBlazor.Core/Components/Geometries/Point.cs +++ b/src/dymaptic.GeoBlazor.Core/Components/Geometries/Point.cs @@ -3,6 +3,94 @@ namespace dymaptic.GeoBlazor.Core.Components.Geometries; [ProtobufSerializable] public partial class Point : Geometry { + /// + /// Parameterless constructor for use as a Razor Component. + /// + [ActivatorUtilitiesConstructor] + [CodeGenerationIgnore] + public Point() + { + } + + /// + /// Constructor for use in C# code. Use named parameters (e.g., item1: value1, item2: value2) to set properties in any order. + /// + /// + /// The longitude of the point. + /// ArcGIS Maps SDK for JavaScript + /// + /// + /// The latitude of the point. + /// ArcGIS Maps SDK for JavaScript + /// + /// + /// The x-coordinate (easting) of the point in map units. + /// default 0 + /// ArcGIS Maps SDK for JavaScript + /// + /// + /// The y-coordinate (northing) of the point in map units. + /// default 0 + /// ArcGIS Maps SDK for JavaScript + /// + /// + /// The z-coordinate (or elevation) of the point in map units. + /// default undefined + /// ArcGIS Maps SDK for JavaScript + /// + /// + /// The spatial reference of the geometry. + /// default SpatialReference.WGS84 // wkid: 4326 + /// ArcGIS Maps SDK for JavaScript + /// + /// + /// Indicates if the geometry has M values. + /// ArcGIS Maps SDK for JavaScript + /// + /// + /// Indicates if the geometry has z-values (elevation). + /// ArcGIS Maps SDK for JavaScript + /// + /// + /// The m-coordinate of the point in map units. + /// default undefined + /// ArcGIS Maps SDK for JavaScript + /// + [CodeGenerationIgnore] + public Point(double? longitude = null, + double? latitude = null, + double? x = null, + double? y = null, + double? z = null, + SpatialReference? spatialReference = null, + bool? hasM = null, + bool? hasZ = null, + double? m = null) + { + AllowRender = false; + + if (x is null && longitude is null || y is null && latitude is null) + { + throw new ArgumentException("Points must have X and Y coordinates or longitude and latitude."); + } + + if (x is null && y is null && spatialReference is { Wkid: not 4326 }) + { + } + +#pragma warning disable BL0005 + Longitude = longitude; + Latitude = latitude; + X = x; + Y = y; + Z = z; + SpatialReference = spatialReference; + HasM = hasM; + HasZ = hasZ; + M = m; +#pragma warning restore BL0005 + } + /// /// The latitude of the point. /// @@ -66,7 +154,7 @@ public Point Clone() /// public override GeometrySerializationRecord ToProtobuf() { - return new GeometrySerializationRecord(Id.ToString(), Type.ToString().ToKebabCase(), + return new GeometrySerializationRecord(Id.ToString(), Type.ToString().ToKebabCase(), Extent?.ToProtobuf(), SpatialReference?.ToProtobuf()) { diff --git a/src/dymaptic.GeoBlazor.Core/Components/Geometries/Point.gb.cs b/src/dymaptic.GeoBlazor.Core/Components/Geometries/Point.gb.cs index 1c3ffd2e5..accd60cd0 100644 --- a/src/dymaptic.GeoBlazor.Core/Components/Geometries/Point.gb.cs +++ b/src/dymaptic.GeoBlazor.Core/Components/Geometries/Point.gb.cs @@ -2,7 +2,6 @@ namespace dymaptic.GeoBlazor.Core.Components.Geometries; - /// /// GeoBlazor Docs /// A location defined by X, Y, and Z coordinates. @@ -10,85 +9,6 @@ namespace dymaptic.GeoBlazor.Core.Components.Geometries; /// public partial class Point { - - /// - /// Parameterless constructor for use as a Razor Component. - /// - [ActivatorUtilitiesConstructor] - public Point() - { - } - - /// - /// Constructor for use in C# code. Use named parameters (e.g., item1: value1, item2: value2) to set properties in any order. - /// - /// - /// The longitude of the point. - /// ArcGIS Maps SDK for JavaScript - /// - /// - /// The latitude of the point. - /// ArcGIS Maps SDK for JavaScript - /// - /// - /// The x-coordinate (easting) of the point in map units. - /// default 0 - /// ArcGIS Maps SDK for JavaScript - /// - /// - /// The y-coordinate (northing) of the point in map units. - /// default 0 - /// ArcGIS Maps SDK for JavaScript - /// - /// - /// The z-coordinate (or elevation) of the point in map units. - /// default undefined - /// ArcGIS Maps SDK for JavaScript - /// - /// - /// The spatial reference of the geometry. - /// default SpatialReference.WGS84 // wkid: 4326 - /// ArcGIS Maps SDK for JavaScript - /// - /// - /// Indicates if the geometry has M values. - /// ArcGIS Maps SDK for JavaScript - /// - /// - /// Indicates if the geometry has z-values (elevation). - /// ArcGIS Maps SDK for JavaScript - /// - /// - /// The m-coordinate of the point in map units. - /// default undefined - /// ArcGIS Maps SDK for JavaScript - /// - public Point( - double? longitude = null, - double? latitude = null, - double? x = null, - double? y = null, - double? z = null, - SpatialReference? spatialReference = null, - bool? hasM = null, - bool? hasZ = null, - double? m = null) - { - AllowRender = false; -#pragma warning disable BL0005 - Longitude = longitude; - Latitude = latitude; - X = x; - Y = y; - Z = z; - SpatialReference = spatialReference; - HasM = hasM; - HasZ = hasZ; - M = m; -#pragma warning restore BL0005 - } - - #region Property Getters /// @@ -100,8 +20,8 @@ public Point( { return Latitude; } - - try + + try { JsComponentReference ??= await CoreJsModule.InvokeAsync( "getJsComponent", CancellationTokenSource.Token, Id); @@ -110,26 +30,28 @@ public Point( { // this is expected if the component is not yet built } - + if (JsComponentReference is null) { return Latitude; } // get the property value - JsNullableDoubleWrapper? result = await CoreJsModule!.InvokeAsync("getNullableValueTypedProperty", + JsNullableDoubleWrapper? result = await CoreJsModule!.InvokeAsync( + "getNullableValueTypedProperty", CancellationTokenSource.Token, JsComponentReference, "latitude"); + if (result is { Value: not null }) { #pragma warning disable BL0005 - Latitude = result.Value.Value; + Latitude = result.Value.Value; #pragma warning restore BL0005 - ModifiedParameters[nameof(Latitude)] = Latitude; + ModifiedParameters[nameof(Latitude)] = Latitude; } - + return Latitude; } - + /// /// Asynchronously retrieve the current value of the Longitude property. /// @@ -139,8 +61,8 @@ public Point( { return Longitude; } - - try + + try { JsComponentReference ??= await CoreJsModule.InvokeAsync( "getJsComponent", CancellationTokenSource.Token, Id); @@ -149,26 +71,28 @@ public Point( { // this is expected if the component is not yet built } - + if (JsComponentReference is null) { return Longitude; } // get the property value - JsNullableDoubleWrapper? result = await CoreJsModule!.InvokeAsync("getNullableValueTypedProperty", + JsNullableDoubleWrapper? result = await CoreJsModule!.InvokeAsync( + "getNullableValueTypedProperty", CancellationTokenSource.Token, JsComponentReference, "longitude"); + if (result is { Value: not null }) { #pragma warning disable BL0005 - Longitude = result.Value.Value; + Longitude = result.Value.Value; #pragma warning restore BL0005 - ModifiedParameters[nameof(Longitude)] = Longitude; + ModifiedParameters[nameof(Longitude)] = Longitude; } - + return Longitude; } - + /// /// Asynchronously retrieve the current value of the M property. /// @@ -178,8 +102,8 @@ public Point( { return M; } - - try + + try { JsComponentReference ??= await CoreJsModule.InvokeAsync( "getJsComponent", CancellationTokenSource.Token, Id); @@ -188,26 +112,28 @@ public Point( { // this is expected if the component is not yet built } - + if (JsComponentReference is null) { return M; } // get the property value - JsNullableDoubleWrapper? result = await CoreJsModule!.InvokeAsync("getNullableValueTypedProperty", + JsNullableDoubleWrapper? result = await CoreJsModule!.InvokeAsync( + "getNullableValueTypedProperty", CancellationTokenSource.Token, JsComponentReference, "m"); + if (result is { Value: not null }) { #pragma warning disable BL0005 - M = result.Value.Value; + M = result.Value.Value; #pragma warning restore BL0005 - ModifiedParameters[nameof(M)] = M; + ModifiedParameters[nameof(M)] = M; } - + return M; } - + /// /// Asynchronously retrieve the current value of the X property. /// @@ -217,8 +143,8 @@ public Point( { return X; } - - try + + try { JsComponentReference ??= await CoreJsModule.InvokeAsync( "getJsComponent", CancellationTokenSource.Token, Id); @@ -227,26 +153,28 @@ public Point( { // this is expected if the component is not yet built } - + if (JsComponentReference is null) { return X; } // get the property value - JsNullableDoubleWrapper? result = await CoreJsModule!.InvokeAsync("getNullableValueTypedProperty", + JsNullableDoubleWrapper? result = await CoreJsModule!.InvokeAsync( + "getNullableValueTypedProperty", CancellationTokenSource.Token, JsComponentReference, "x"); + if (result is { Value: not null }) { #pragma warning disable BL0005 - X = result.Value.Value; + X = result.Value.Value; #pragma warning restore BL0005 - ModifiedParameters[nameof(X)] = X; + ModifiedParameters[nameof(X)] = X; } - + return X; } - + /// /// Asynchronously retrieve the current value of the Y property. /// @@ -256,8 +184,8 @@ public Point( { return Y; } - - try + + try { JsComponentReference ??= await CoreJsModule.InvokeAsync( "getJsComponent", CancellationTokenSource.Token, Id); @@ -266,26 +194,28 @@ public Point( { // this is expected if the component is not yet built } - + if (JsComponentReference is null) { return Y; } // get the property value - JsNullableDoubleWrapper? result = await CoreJsModule!.InvokeAsync("getNullableValueTypedProperty", + JsNullableDoubleWrapper? result = await CoreJsModule!.InvokeAsync( + "getNullableValueTypedProperty", CancellationTokenSource.Token, JsComponentReference, "y"); + if (result is { Value: not null }) { #pragma warning disable BL0005 - Y = result.Value.Value; + Y = result.Value.Value; #pragma warning restore BL0005 - ModifiedParameters[nameof(Y)] = Y; + ModifiedParameters[nameof(Y)] = Y; } - + return Y; } - + /// /// Asynchronously retrieve the current value of the Z property. /// @@ -295,8 +225,8 @@ public Point( { return Z; } - - try + + try { JsComponentReference ??= await CoreJsModule.InvokeAsync( "getJsComponent", CancellationTokenSource.Token, Id); @@ -305,28 +235,31 @@ public Point( { // this is expected if the component is not yet built } - + if (JsComponentReference is null) { return Z; } // get the property value - JsNullableDoubleWrapper? result = await CoreJsModule!.InvokeAsync("getNullableValueTypedProperty", + JsNullableDoubleWrapper? result = await CoreJsModule!.InvokeAsync( + "getNullableValueTypedProperty", CancellationTokenSource.Token, JsComponentReference, "z"); + if (result is { Value: not null }) { #pragma warning disable BL0005 - Z = result.Value.Value; + Z = result.Value.Value; #pragma warning restore BL0005 - ModifiedParameters[nameof(Z)] = Z; + ModifiedParameters[nameof(Z)] = Z; } - + return Z; } - + #endregion + #region Property Setters /// @@ -341,13 +274,13 @@ public async Task SetLatitude(double? value) Latitude = value; #pragma warning restore BL0005 ModifiedParameters[nameof(Latitude)] = value; - + if (CoreJsModule is null) { return; } - - try + + try { JsComponentReference ??= await CoreJsModule.InvokeAsync( "getJsComponent", CancellationTokenSource.Token, Id); @@ -356,16 +289,16 @@ public async Task SetLatitude(double? value) { // this is expected if the component is not yet built } - + if (JsComponentReference is null) { return; } - + await CoreJsModule.InvokeVoidAsync("setProperty", CancellationTokenSource.Token, JsComponentReference, "latitude", value); } - + /// /// Asynchronously set the value of the Longitude property after render. /// @@ -378,13 +311,13 @@ public async Task SetLongitude(double? value) Longitude = value; #pragma warning restore BL0005 ModifiedParameters[nameof(Longitude)] = value; - + if (CoreJsModule is null) { return; } - - try + + try { JsComponentReference ??= await CoreJsModule.InvokeAsync( "getJsComponent", CancellationTokenSource.Token, Id); @@ -393,16 +326,16 @@ public async Task SetLongitude(double? value) { // this is expected if the component is not yet built } - + if (JsComponentReference is null) { return; } - + await CoreJsModule.InvokeVoidAsync("setProperty", CancellationTokenSource.Token, JsComponentReference, "longitude", value); } - + /// /// Asynchronously set the value of the M property after render. /// @@ -415,13 +348,13 @@ public async Task SetM(double? value) M = value; #pragma warning restore BL0005 ModifiedParameters[nameof(M)] = value; - + if (CoreJsModule is null) { return; } - - try + + try { JsComponentReference ??= await CoreJsModule.InvokeAsync( "getJsComponent", CancellationTokenSource.Token, Id); @@ -430,16 +363,16 @@ public async Task SetM(double? value) { // this is expected if the component is not yet built } - + if (JsComponentReference is null) { return; } - + await CoreJsModule.InvokeVoidAsync("setProperty", CancellationTokenSource.Token, JsComponentReference, "m", value); } - + /// /// Asynchronously set the value of the X property after render. /// @@ -452,13 +385,13 @@ public async Task SetX(double? value) X = value; #pragma warning restore BL0005 ModifiedParameters[nameof(X)] = value; - + if (CoreJsModule is null) { return; } - - try + + try { JsComponentReference ??= await CoreJsModule.InvokeAsync( "getJsComponent", CancellationTokenSource.Token, Id); @@ -467,16 +400,16 @@ public async Task SetX(double? value) { // this is expected if the component is not yet built } - + if (JsComponentReference is null) { return; } - + await CoreJsModule.InvokeVoidAsync("setProperty", CancellationTokenSource.Token, JsComponentReference, "x", value); } - + /// /// Asynchronously set the value of the Y property after render. /// @@ -489,13 +422,13 @@ public async Task SetY(double? value) Y = value; #pragma warning restore BL0005 ModifiedParameters[nameof(Y)] = value; - + if (CoreJsModule is null) { return; } - - try + + try { JsComponentReference ??= await CoreJsModule.InvokeAsync( "getJsComponent", CancellationTokenSource.Token, Id); @@ -504,16 +437,16 @@ public async Task SetY(double? value) { // this is expected if the component is not yet built } - + if (JsComponentReference is null) { return; } - + await CoreJsModule.InvokeVoidAsync("setProperty", CancellationTokenSource.Token, JsComponentReference, "y", value); } - + /// /// Asynchronously set the value of the Z property after render. /// @@ -526,13 +459,13 @@ public async Task SetZ(double? value) Z = value; #pragma warning restore BL0005 ModifiedParameters[nameof(Z)] = value; - + if (CoreJsModule is null) { return; } - - try + + try { JsComponentReference ??= await CoreJsModule.InvokeAsync( "getJsComponent", CancellationTokenSource.Token, Id); @@ -541,18 +474,19 @@ public async Task SetZ(double? value) { // this is expected if the component is not yet built } - + if (JsComponentReference is null) { return; } - + await CoreJsModule.InvokeVoidAsync("setProperty", CancellationTokenSource.Token, JsComponentReference, "z", value); } - + #endregion + #region Public Methods /// @@ -570,7 +504,7 @@ await CoreJsModule.InvokeVoidAsync("setProperty", CancellationTokenSource.Token, { return null; } - + try { JsComponentReference ??= await CoreJsModule.InvokeAsync( @@ -580,18 +514,17 @@ await CoreJsModule.InvokeVoidAsync("setProperty", CancellationTokenSource.Token, { // this is expected if the component is not yet built } - + if (JsComponentReference is null) { return null; } - - return await JsComponentReference!.InvokeAsync( - "copy", + + return await JsComponentReference!.InvokeAsync("copy", CancellationTokenSource.Token, other); } - + /// /// GeoBlazor Docs /// Computes the Euclidean distance between this Point and a given Point. @@ -607,7 +540,7 @@ await CoreJsModule.InvokeVoidAsync("setProperty", CancellationTokenSource.Token, { return null; } - + try { JsComponentReference ??= await CoreJsModule.InvokeAsync( @@ -617,18 +550,17 @@ await CoreJsModule.InvokeVoidAsync("setProperty", CancellationTokenSource.Token, { // this is expected if the component is not yet built } - + if (JsComponentReference is null) { return null; } - - return await JsComponentReference!.InvokeAsync( - "distance", + + return await JsComponentReference!.InvokeAsync("distance", CancellationTokenSource.Token, other); } - + /// /// GeoBlazor Docs /// Modifies the point geometry in-place by shifting the X-coordinate to within @@ -642,7 +574,7 @@ await CoreJsModule.InvokeVoidAsync("setProperty", CancellationTokenSource.Token, { return null; } - + try { JsComponentReference ??= await CoreJsModule.InvokeAsync( @@ -652,17 +584,15 @@ await CoreJsModule.InvokeVoidAsync("setProperty", CancellationTokenSource.Token, { // this is expected if the component is not yet built } - + if (JsComponentReference is null) { return null; } - - return await JsComponentReference!.InvokeAsync( - "normalize", + + return await JsComponentReference!.InvokeAsync("normalize", CancellationTokenSource.Token); } - -#endregion -} +#endregion +} \ No newline at end of file diff --git a/src/dymaptic.GeoBlazor.Core/Components/LocationService.cs b/src/dymaptic.GeoBlazor.Core/Components/LocationService.cs index 44d538278..1b01dcf00 100644 --- a/src/dymaptic.GeoBlazor.Core/Components/LocationService.cs +++ b/src/dymaptic.GeoBlazor.Core/Components/LocationService.cs @@ -18,7 +18,7 @@ public LocationService(IAppValidator appValidator, IJSRuntime jsRuntime, JsModul // final implementation of all the SuggestLocations permutations [SerializedMethod] - public async Task LocationToAddressImplementation(string url, Point location, + private async Task LocationToAddressImplementation(string url, Point location, LocationType? locationType, SpatialReference? outSpatialReference, RequestOptions? requestOptions, CancellationToken cancellationToken = default) { diff --git a/src/dymaptic.GeoBlazor.Core/Components/MapComponent.razor.cs b/src/dymaptic.GeoBlazor.Core/Components/MapComponent.razor.cs index e3dbaf9e6..34732f1c8 100644 --- a/src/dymaptic.GeoBlazor.Core/Components/MapComponent.razor.cs +++ b/src/dymaptic.GeoBlazor.Core/Components/MapComponent.razor.cs @@ -185,7 +185,7 @@ public Guid? ViewId /// /// Boolean flag to identify if GeoBlazor is running in Blazor Hybrid (MAUI) mode /// - protected internal bool IsMaui => JsRuntime!.GetType().Name.Contains("WebView"); + protected internal bool IsMaui => JsRuntime?.GetType().Name.Contains("WebView") ?? false; /// /// Implements the `IAsyncDisposable` pattern. @@ -1138,7 +1138,8 @@ protected internal void UpdateGeoBlazorReferences(IJSObjectReference coreJsModul foreach (PropertyInfo prop in _props) { - if (_circularMapComponents.Contains(prop.Name) + if (prop.SetMethod is null + || _circularMapComponents.Contains(prop.Name) || _circularMapComponents.Contains(prop.PropertyType.Name) || (prop.PropertyType.IsGenericType && _circularMapComponents.Any(c => prop.PropertyType diff --git a/src/dymaptic.GeoBlazor.Core/Components/GeometryEngine.cs b/src/dymaptic.GeoBlazor.Core/Model/GeometryEngine.cs similarity index 87% rename from src/dymaptic.GeoBlazor.Core/Components/GeometryEngine.cs rename to src/dymaptic.GeoBlazor.Core/Model/GeometryEngine.cs index 9a5ae33ae..6257766a2 100644 --- a/src/dymaptic.GeoBlazor.Core/Components/GeometryEngine.cs +++ b/src/dymaptic.GeoBlazor.Core/Model/GeometryEngine.cs @@ -1,5 +1,5 @@ // ReSharper disable RedundantEnumerableCastCall -namespace dymaptic.GeoBlazor.Core.Components; +namespace dymaptic.GeoBlazor.Core.Model; /// /// A client-side geometry engine for testing, measuring, and analyzing the spatial relationship between two or more 2D @@ -126,7 +126,7 @@ public Task Buffer(IEnumerable geometries, IEnumerable Buffer(IEnumerable geometries, IEnumerable distances, GeometryEngineLinearUnit? unit, bool? unionResults, CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(ProjectionEngine), nameof(distances), + return await InvokeAsync(nameof(ProjectionEngine), nameof(Buffer), cancellationToken, geometries, distances, unit, unionResults); } @@ -150,7 +150,7 @@ public async Task Buffer(IEnumerable geometries, IEnumerabl /// The resulting buffer. /// /// The cancellation token to use for the operation. - public Task Buffer(Geometry geometry, double distance, CancellationToken cancellationToken = default) + public Task Buffer(Geometry geometry, double distance, CancellationToken cancellationToken = default) { return Buffer(geometry, distance, null, cancellationToken); } @@ -179,7 +179,7 @@ public Task Buffer(Geometry geometry, double distance, CancellationToke /// /// The cancellation token to use for the operation. [SerializedMethod] - public async Task Buffer(Geometry geometry, double distance, GeometryEngineLinearUnit? unit, + public async Task Buffer(Geometry geometry, double distance, GeometryEngineLinearUnit? unit, CancellationToken cancellationToken = default) { return await InvokeAsync(nameof(ProjectionEngine), nameof(Buffer), @@ -245,10 +245,10 @@ public async Task Contains(Geometry containerGeometry, Geometry insideGeom /// /// The cancellation token to use for the operation. [SerializedMethod] - public async Task ConvexHull(IEnumerable geometries, bool? merge = null, + public async Task ConvexHull(IEnumerable geometries, bool? merge = null, CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(ProjectionEngine), nameof(ConvexHull), + return await InvokeAsync(nameof(ProjectionEngine), nameof(ConvexHull), cancellationToken, geometries, merge); } @@ -266,9 +266,9 @@ public async Task ConvexHull(IEnumerable geometries, bool? /// /// The cancellation token to use for the operation. [SerializedMethod] - public async Task ConvexHull(Geometry geometry, CancellationToken cancellationToken = default) + public async Task ConvexHull(Geometry geometry, CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(ProjectionEngine), nameof(ConvexHull), + return await InvokeAsync(nameof(ProjectionEngine), nameof(ConvexHull), cancellationToken, geometry); } @@ -358,10 +358,10 @@ public async Task Densify(Geometry geometry, double maxSegmentLength, /// /// The cancellation token to use for the operation. [SerializedMethod] - public async Task Difference(IEnumerable geometries, Geometry subtractor, + public async Task Difference(IEnumerable geometries, Geometry subtractor, CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(ProjectionEngine), nameof(geometries), + return await InvokeAsync(nameof(ProjectionEngine), nameof(Difference), cancellationToken, geometries, subtractor); } @@ -380,10 +380,10 @@ public async Task Difference(IEnumerable geometries, Geome /// /// The cancellation token to use for the operation. [SerializedMethod] - public async Task Difference(Geometry geometry, Geometry subtractor, + public async Task Difference(Geometry geometry, Geometry subtractor, CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(ProjectionEngine), nameof(Difference), + return await InvokeAsync(nameof(ProjectionEngine), nameof(Difference), cancellationToken, geometry, subtractor); } @@ -469,11 +469,12 @@ public async Task AreEqual(Geometry geometry1, Geometry geometry2, /// /// The cancellation token to use for the operation. [SerializedMethod] + [Obsolete] public async Task ExtendedSpatialReferenceInfo(SpatialReference spatialReference, CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(ProjectionEngine), nameof(ExtendedSpatialReferenceInfo), - cancellationToken, spatialReference); + return await InvokeAsync(nameof(ProjectionEngine), + nameof(ExtendedSpatialReferenceInfo), cancellationToken, spatialReference); } /// @@ -539,10 +540,10 @@ public async Task FlipVertical(Geometry geometry, Point? flipOrigin = /// /// The cancellation token to use for the operation. [SerializedMethod] - public async Task Generalize(Geometry geometry, double maxDeviation, bool? removeDegenerateParts = null, + public async Task Generalize(Geometry geometry, double maxDeviation, bool? removeDegenerateParts = null, GeometryEngineLinearUnit? maxDeviationUnit = null, CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(ProjectionEngine), nameof(Generalize), + return await InvokeAsync(nameof(ProjectionEngine), nameof(Generalize), cancellationToken, geometry, maxDeviation, removeDegenerateParts, maxDeviationUnit); } @@ -683,7 +684,7 @@ public Task GeodesicBuffer(IEnumerable geometries, IEnumera public async Task GeodesicBuffer(IEnumerable geometries, IEnumerable distances, GeometryEngineLinearUnit? unit, bool? unionResults, CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(ProjectionEngine), nameof(distances), + return await InvokeAsync(nameof(ProjectionEngine), nameof(GeodesicBuffer), cancellationToken, geometries, distances, unit, unionResults); } @@ -710,7 +711,7 @@ public async Task GeodesicBuffer(IEnumerable geometries, IE /// The resulting buffers /// /// The cancellation token to use for the operation. - public Task GeodesicBuffer(Geometry geometry, double distance, + public Task GeodesicBuffer(Geometry geometry, double distance, CancellationToken cancellationToken = default) { return GeodesicBuffer(geometry, distance, null, cancellationToken); @@ -743,10 +744,10 @@ public Task GeodesicBuffer(Geometry geometry, double distance, /// /// The cancellation token to use for the operation. [SerializedMethod] - public async Task GeodesicBuffer(Geometry geometry, double distance, GeometryEngineLinearUnit? unit, + public async Task GeodesicBuffer(Geometry geometry, double distance, GeometryEngineLinearUnit? unit, CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(ProjectionEngine), nameof(GeodesicBuffer), + return await InvokeAsync(nameof(ProjectionEngine), nameof(GeodesicBuffer), cancellationToken, geometry, distance, unit); } @@ -765,7 +766,7 @@ public async Task GeodesicBuffer(Geometry geometry, double distance, Ge /// Returns the densified geometry. /// /// The cancellation token to use for the operation. - public Task GeodesicDensify(Geometry geometry, double maxSegmentLength, + public Task GeodesicDensify(Geometry geometry, double maxSegmentLength, CancellationToken cancellationToken = default) { return GeodesicDensify(geometry, maxSegmentLength, null, cancellationToken); @@ -790,7 +791,7 @@ public Task GeodesicDensify(Geometry geometry, double maxSegmentLength /// /// The cancellation token to use for the operation. [SerializedMethod] - public async Task GeodesicDensify(Geometry geometry, double maxSegmentLength, + public async Task GeodesicDensify(Geometry geometry, double maxSegmentLength, GeometryEngineLinearUnit? maxSegmentLengthUnit, CancellationToken cancellationToken = default) { return await InvokeAsync(nameof(ProjectionEngine), nameof(GeodesicDensify), @@ -844,7 +845,7 @@ public async Task GeodesicLength(Geometry geometry, GeometryEngineLinear public async Task Intersect(IEnumerable geometries1, Geometry geometry2, CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(ProjectionEngine), nameof(geometries1), + return await InvokeAsync(nameof(ProjectionEngine), nameof(Intersect), cancellationToken, geometries1, geometry2); } @@ -864,10 +865,10 @@ public async Task Intersect(IEnumerable geometries1, Geome /// /// The cancellation token to use for the operation. [SerializedMethod] - public async Task Intersect(Geometry geometry1, Geometry geometry2, + public async Task Intersect(Geometry geometry1, Geometry geometry2, CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(ProjectionEngine), nameof(Intersect), + return await InvokeAsync(nameof(ProjectionEngine), nameof(Intersect), cancellationToken, geometry1, geometry2); } @@ -924,10 +925,10 @@ public async Task IsSimple(Geometry geometry, CancellationToken cancellati /// /// The cancellation token to use for the operation. [SerializedMethod] - public async Task NearestCoordinate(Geometry geometry, Point inputPoint, + public async Task NearestCoordinate(Geometry geometry, Point inputPoint, CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(ProjectionEngine), nameof(NearestCoordinate), + return await InvokeAsync(nameof(ProjectionEngine), nameof(NearestCoordinate), cancellationToken, geometry, inputPoint); } @@ -945,10 +946,10 @@ public async Task NearestCoordinate(Geometry geometry, Point /// /// The cancellation token to use for the operation. [SerializedMethod] - public async Task NearestVertex(Geometry geometry, Point inputPoint, + public async Task NearestVertex(Geometry geometry, Point inputPoint, CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(ProjectionEngine), nameof(NearestVertex), + return await InvokeAsync(nameof(ProjectionEngine), nameof(NearestVertex), cancellationToken, geometry, inputPoint); } @@ -973,32 +974,28 @@ public async Task NearestVertex(Geometry geometry, Point inp /// /// The cancellation token to use for the operation. [SerializedMethod] - public async Task NearestVertices(Geometry geometry, Point inputPoint, double searchRadius, + public async Task NearestVertices(Geometry geometry, Point inputPoint, double searchRadius, int maxVertexCountToReturn, CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(ProjectionEngine), nameof(NearestVertices), + return await InvokeAsync(nameof(ProjectionEngine), nameof(NearestVertices), cancellationToken, geometry, inputPoint, searchRadius, maxVertexCountToReturn); } /// /// The offset operation creates a geometry that is a constant planar distance from an input polyline or polygon. It is - /// similar to buffering, but produces a one-sided result. + /// similar to buffering, but produces a one-sided result. Point and multipoint geometries are not supported. /// /// /// The geometries to offset. /// /// - /// The planar distance to offset from the input geometry. If offsetDistance > 0, then the offset geometry is - /// constructed to the right of the oriented input geometry, if offsetDistance = 0, then there is no change in the - /// geometries, otherwise it is constructed to the left. For a simple polygon, the orientation of outer rings is - /// clockwise and for inner rings it is counter clockwise. So the "right side" of a simple polygon is always its - /// inside. + /// The distance to offset the input geometry. Unless the unit option is set, the default is the geometry's spatial reference unit. /// /// /// The offset geometries. /// /// The cancellation token to use for the operation. - public Task Offset(IEnumerable geometries, double offsetDistance, + public Task Offset(IEnumerable geometries, double offsetDistance, CancellationToken cancellationToken = default) { return Offset(geometries, offsetDistance, null, null, null, null, @@ -1007,26 +1004,22 @@ public Task Offset(IEnumerable geometries, double offsetDi /// /// The offset operation creates a geometry that is a constant planar distance from an input polyline or polygon. It is - /// similar to buffering, but produces a one-sided result. + /// similar to buffering, but produces a one-sided result. Point and multipoint geometries are not supported. /// /// /// The geometries to offset. /// /// - /// The planar distance to offset from the input geometry. If offsetDistance > 0, then the offset geometry is - /// constructed to the right of the oriented input geometry, if offsetDistance = 0, then there is no change in the - /// geometries, otherwise it is constructed to the left. For a simple polygon, the orientation of outer rings is - /// clockwise and for inner rings it is counter clockwise. So the "right side" of a simple polygon is always its - /// inside. + /// The distance to offset the input geometry. Unless the unit option is set, the default is the geometry's spatial reference unit. /// /// - /// Measurement unit of the offset distance. Defaults to the units of the input geometries. + /// The length unit of the offset distance. The default is the input geometries spatial reference unit. An error will be thrown if this is set for Geographic Coordinate Systems. /// /// /// The offset geometries. /// /// The cancellation token to use for the operation. - public Task Offset(IEnumerable geometries, double offsetDistance, + public Task Offset(IEnumerable geometries, double offsetDistance, GeometryEngineLinearUnit? offsetUnit, CancellationToken cancellationToken = default) { return Offset(geometries, offsetDistance, offsetUnit, null, null, null, @@ -1035,29 +1028,30 @@ public Task Offset(IEnumerable geometries, double offsetDi /// /// The offset operation creates a geometry that is a constant planar distance from an input polyline or polygon. It is - /// similar to buffering, but produces a one-sided result. + /// similar to buffering, but produces a one-sided result. Point and multipoint geometries are not supported. /// /// /// The geometries to offset. /// /// - /// The planar distance to offset from the input geometry. If offsetDistance > 0, then the offset geometry is - /// constructed to the right of the oriented input geometry, if offsetDistance = 0, then there is no change in the - /// geometries, otherwise it is constructed to the left. For a simple polygon, the orientation of outer rings is - /// clockwise and for inner rings it is counter clockwise. So the "right side" of a simple polygon is always its - /// inside. + /// The distance to offset the input geometry. Unless the unit option is set, the default is the geometry's spatial reference unit. /// /// - /// Measurement unit of the offset distance. Defaults to the units of the input geometries. + /// The length unit of the offset distance. The default is the input geometries spatial reference unit. An error will be thrown if this is set for Geographic Coordinate Systems. /// /// - /// The + /// The . Defines the join type of the offset geometry. Inner corners are always mitered. + /// Default value: "round" + /// - Round - a circular arc that is tangent to the ends of both offset line segments. + /// - Miter - the offset line segments are extended to their intersection point forming a sharp angle, unless that extension exceeds the miterLimit, in which case the result is a bevel. + /// - Bevel - the offset line segments are not extended; their endpoints are joined by a straight line. + /// - Square - same as miter for minor arcs greater than 90 degrees. For all other minor arcs, the offset line segments are extended by an extra distance before their endpoints are joined. /// /// /// The offset geometries. /// /// The cancellation token to use for the operation. - public Task Offset(IEnumerable geometries, double offsetDistance, + public Task Offset(IEnumerable geometries, double offsetDistance, GeometryEngineLinearUnit? offsetUnit, JoinType? joinType, CancellationToken cancellationToken = default) { return Offset(geometries, offsetDistance, offsetUnit, joinType, null, null, @@ -1066,100 +1060,95 @@ public Task Offset(IEnumerable geometries, double offsetDi /// /// The offset operation creates a geometry that is a constant planar distance from an input polyline or polygon. It is - /// similar to buffering, but produces a one-sided result. + /// similar to buffering, but produces a one-sided result. Point and multipoint geometries are not supported. /// /// /// The geometries to offset. /// /// - /// The planar distance to offset from the input geometry. If offsetDistance > 0, then the offset geometry is - /// constructed to the right of the oriented input geometry, if offsetDistance = 0, then there is no change in the - /// geometries, otherwise it is constructed to the left. For a simple polygon, the orientation of outer rings is - /// clockwise and for inner rings it is counter clockwise. So the "right side" of a simple polygon is always its - /// inside. + /// The distance to offset the input geometry. Unless the unit option is set, the default is the geometry's spatial reference unit. /// /// - /// Measurement unit of the offset distance. Defaults to the units of the input geometries. + /// The length unit of the offset distance. The default is the input geometries spatial reference unit. An error will be thrown if this is set for Geographic Coordinate Systems. /// /// - /// The + /// The . Defines the join type of the offset geometry. Inner corners are always mitered. + /// Default value: "round" + /// - Round - a circular arc that is tangent to the ends of both offset line segments. + /// - Miter - the offset line segments are extended to their intersection point forming a sharp angle, unless that extension exceeds the miterLimit, in which case the result is a bevel. + /// - Bevel - the offset line segments are not extended; their endpoints are joined by a straight line. + /// - Square - same as miter for minor arcs greater than 90 degrees. For all other minor arcs, the offset line segments are extended by an extra distance before their endpoints are joined. /// - /// - /// Applicable when joinType = 'miter'; bevelRatio is multiplied by the offset distance and the result determines how - /// far a mitered offset intersection can be located before it is beveled. + /// + /// Applicable only to mitered joins. Controls the appearance of angled lines to avoid excessively long and pointy corners. The miterLimit is multiplied by the offset distance and the result is the maximum mitered offset: joins which would result in a larger mitered offset will be beveled instead. /// /// /// The offset geometries. /// /// The cancellation token to use for the operation. - public Task Offset(IEnumerable geometries, double offsetDistance, - GeometryEngineLinearUnit? offsetUnit, JoinType? joinType, double? bevelRatio, + public Task Offset(IEnumerable geometries, double offsetDistance, + GeometryEngineLinearUnit? offsetUnit, JoinType? joinType, double? miterLimit, CancellationToken cancellationToken = default) { - return Offset(geometries, offsetDistance, offsetUnit, joinType, bevelRatio, null, + return Offset(geometries, offsetDistance, offsetUnit, joinType, miterLimit, null, cancellationToken); } /// /// The offset operation creates a geometry that is a constant planar distance from an input polyline or polygon. It is - /// similar to buffering, but produces a one-sided result. + /// similar to buffering, but produces a one-sided result. Point and multipoint geometries are not supported. /// /// /// The geometries to offset. /// /// - /// The planar distance to offset from the input geometry. If offsetDistance > 0, then the offset geometry is - /// constructed to the right of the oriented input geometry, if offsetDistance = 0, then there is no change in the - /// geometries, otherwise it is constructed to the left. For a simple polygon, the orientation of outer rings is - /// clockwise and for inner rings it is counter clockwise. So the "right side" of a simple polygon is always its - /// inside. + /// The distance to offset the input geometry. Unless the unit option is set, the default is the geometry's spatial reference unit. /// /// - /// Measurement unit of the offset distance. Defaults to the units of the input geometries. + /// The length unit of the offset distance. The default is the input geometries spatial reference unit. An error will be thrown if this is set for Geographic Coordinate Systems. /// /// - /// The + /// The . Defines the join type of the offset geometry. Inner corners are always mitered. + /// Default value: "round" + /// - Round - a circular arc that is tangent to the ends of both offset line segments. + /// - Miter - the offset line segments are extended to their intersection point forming a sharp angle, unless that extension exceeds the miterLimit, in which case the result is a bevel. + /// - Bevel - the offset line segments are not extended; their endpoints are joined by a straight line. + /// - Square - same as miter for minor arcs greater than 90 degrees. For all other minor arcs, the offset line segments are extended by an extra distance before their endpoints are joined. /// - /// - /// Applicable when joinType = 'miter'; bevelRatio is multiplied by the offset distance and the result determines how - /// far a mitered offset intersection can be located before it is beveled. + /// + /// Applicable only to mitered joins. Controls the appearance of angled lines to avoid excessively long and pointy corners. The miterLimit is multiplied by the offset distance and the result is the maximum mitered offset: joins which would result in a larger mitered offset will be beveled instead. /// /// - /// Applicable when joinType = 'round'; flattenError determines the maximum distance of the resulting segments compared - /// to the true circular arc. The algorithm never produces more than around 180 vertices for each round join. + /// The maximum distance of the resulting segments compared to the true circular arc (used only when joins is round). The algorithm never produces more than around 180 vertices for each round join. /// /// /// The offset geometries. /// /// The cancellation token to use for the operation. [SerializedMethod] - public async Task Offset(IEnumerable geometries, double offsetDistance, - GeometryEngineLinearUnit? offsetUnit, JoinType? joinType, double? bevelRatio, + public async Task Offset(IEnumerable geometries, double offsetDistance, + GeometryEngineLinearUnit? offsetUnit, JoinType? joinType, double? miterLimit, double? flattenError, CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(ProjectionEngine), nameof(Offset), - cancellationToken, geometries, offsetDistance, offsetUnit, joinType, bevelRatio, flattenError); + return await InvokeAsync(nameof(ProjectionEngine), nameof(Offset), + cancellationToken, geometries, offsetDistance, offsetUnit, joinType, miterLimit, flattenError); } /// /// The offset operation creates a geometry that is a constant planar distance from an input polyline or polygon. It is - /// similar to buffering, but produces a one-sided result. + /// similar to buffering, but produces a one-sided result. Point and multipoint geometries are not supported. /// /// /// The geometry to offset. /// /// - /// The planar distance to offset from the input geometry. If offsetDistance > 0, then the offset geometry is - /// constructed to the right of the oriented input geometry, if offsetDistance = 0, then there is no change in the - /// geometries, otherwise it is constructed to the left. For a simple polygon, the orientation of outer rings is - /// clockwise and for inner rings it is counter clockwise. So the "right side" of a simple polygon is always its - /// inside. + /// The distance to offset the input geometry. Unless the unit option is set, the default is the geometry's spatial reference unit. /// /// /// The offset geometry. /// /// The cancellation token to use for the operation. - public Task Offset(Geometry geometry, double offsetDistance, + public Task Offset(Geometry geometry, double offsetDistance, CancellationToken cancellationToken = default) { return Offset(geometry, offsetDistance, null, null, null, null, @@ -1168,26 +1157,22 @@ public Task Offset(Geometry geometry, double offsetDistance, /// /// The offset operation creates a geometry that is a constant planar distance from an input polyline or polygon. It is - /// similar to buffering, but produces a one-sided result. + /// similar to buffering, but produces a one-sided result. Point and multipoint geometries are not supported. /// /// /// The geometry to offset. /// /// - /// The planar distance to offset from the input geometry. If offsetDistance > 0, then the offset geometry is - /// constructed to the right of the oriented input geometry, if offsetDistance = 0, then there is no change in the - /// geometries, otherwise it is constructed to the left. For a simple polygon, the orientation of outer rings is - /// clockwise and for inner rings it is counter clockwise. So the "right side" of a simple polygon is always its - /// inside. + /// The distance to offset the input geometry. Unless the unit option is set, the default is the geometry's spatial reference unit. /// /// - /// Measurement unit of the offset distance. Defaults to the units of the input geometries. + /// The length unit of the offset distance. The default is the input geometries spatial reference unit. An error will be thrown if this is set for Geographic Coordinate Systems. /// /// /// The offset geometry. /// /// The cancellation token to use for the operation. - public Task Offset(Geometry geometry, double offsetDistance, + public Task Offset(Geometry geometry, double offsetDistance, GeometryEngineLinearUnit? offsetUnit, CancellationToken cancellationToken = default) { return Offset(geometry, offsetDistance, offsetUnit, null, null, null, @@ -1196,29 +1181,30 @@ public Task Offset(Geometry geometry, double offsetDistance, /// /// The offset operation creates a geometry that is a constant planar distance from an input polyline or polygon. It is - /// similar to buffering, but produces a one-sided result. + /// similar to buffering, but produces a one-sided result. Point and multipoint geometries are not supported. /// /// /// The geometry to offset. /// /// - /// The planar distance to offset from the input geometry. If offsetDistance > 0, then the offset geometry is - /// constructed to the right of the oriented input geometry, if offsetDistance = 0, then there is no change in the - /// geometries, otherwise it is constructed to the left. For a simple polygon, the orientation of outer rings is - /// clockwise and for inner rings it is counter clockwise. So the "right side" of a simple polygon is always its - /// inside. + /// The distance to offset the input geometry. Unless the unit option is set, the default is the geometry's spatial reference unit. /// /// - /// Measurement unit of the offset distance. Defaults to the units of the input geometries. + /// The length unit of the offset distance. The default is the input geometries spatial reference unit. An error will be thrown if this is set for Geographic Coordinate Systems. /// /// - /// The + /// The . Defines the join type of the offset geometry. Inner corners are always mitered. + /// Default value: "round" + /// - Round - a circular arc that is tangent to the ends of both offset line segments. + /// - Miter - the offset line segments are extended to their intersection point forming a sharp angle, unless that extension exceeds the miterLimit, in which case the result is a bevel. + /// - Bevel - the offset line segments are not extended; their endpoints are joined by a straight line. + /// - Square - same as miter for minor arcs greater than 90 degrees. For all other minor arcs, the offset line segments are extended by an extra distance before their endpoints are joined. /// /// /// The offset geometry. /// /// The cancellation token to use for the operation. - public Task Offset(Geometry geometry, double offsetDistance, + public Task Offset(Geometry geometry, double offsetDistance, GeometryEngineLinearUnit? offsetUnit, JoinType? joinType, CancellationToken cancellationToken = default) { return Offset(geometry, offsetDistance, offsetUnit, joinType, null, null, @@ -1227,79 +1213,78 @@ public Task Offset(Geometry geometry, double offsetDistance, /// /// The offset operation creates a geometry that is a constant planar distance from an input polyline or polygon. It is - /// similar to buffering, but produces a one-sided result. + /// similar to buffering, but produces a one-sided result. Point and multipoint geometries are not supported. /// /// /// The geometry to offset. /// /// - /// The planar distance to offset from the input geometry. If offsetDistance > 0, then the offset geometry is - /// constructed to the right of the oriented input geometry, if offsetDistance = 0, then there is no change in the - /// geometries, otherwise it is constructed to the left. For a simple polygon, the orientation of outer rings is - /// clockwise and for inner rings it is counter clockwise. So the "right side" of a simple polygon is always its - /// inside. + /// The distance to offset the input geometry. Unless the unit option is set, the default is the geometry's spatial reference unit. /// /// - /// Measurement unit of the offset distance. Defaults to the units of the input geometries. + /// The length unit of the offset distance. The default is the input geometries spatial reference unit. An error will be thrown if this is set for Geographic Coordinate Systems. /// /// - /// The + /// The . Defines the join type of the offset geometry. Inner corners are always mitered. + /// Default value: "round" + /// - Round - a circular arc that is tangent to the ends of both offset line segments. + /// - Miter - the offset line segments are extended to their intersection point forming a sharp angle, unless that extension exceeds the miterLimit, in which case the result is a bevel. + /// - Bevel - the offset line segments are not extended; their endpoints are joined by a straight line. + /// - Square - same as miter for minor arcs greater than 90 degrees. For all other minor arcs, the offset line segments are extended by an extra distance before their endpoints are joined. /// - /// - /// Applicable when joinType = 'miter'; bevelRatio is multiplied by the offset distance and the result determines how - /// far a mitered offset intersection can be located before it is beveled. + /// + /// Applicable only to mitered joins. Controls the appearance of angled lines to avoid excessively long and pointy corners. The miterLimit is multiplied by the offset distance and the result is the maximum mitered offset: joins which would result in a larger mitered offset will be beveled instead. /// /// /// The offset geometry. /// /// The cancellation token to use for the operation. - public Task Offset(Geometry geometry, double offsetDistance, - GeometryEngineLinearUnit? offsetUnit, JoinType? joinType, double? bevelRatio, + public Task Offset(Geometry geometry, double offsetDistance, + GeometryEngineLinearUnit? offsetUnit, JoinType? joinType, double? miterLimit, CancellationToken cancellationToken = default) { - return Offset(geometry, offsetDistance, offsetUnit, joinType, bevelRatio, null, + return Offset(geometry, offsetDistance, offsetUnit, joinType, miterLimit, null, cancellationToken); } /// /// The offset operation creates a geometry that is a constant planar distance from an input polyline or polygon. It is - /// similar to buffering, but produces a one-sided result. + /// similar to buffering, but produces a one-sided result. Point and multipoint geometries are not supported. /// /// /// The geometry to offset. /// /// - /// The planar distance to offset from the input geometry. If offsetDistance > 0, then the offset geometry is - /// constructed to the right of the oriented input geometry, if offsetDistance = 0, then there is no change in the - /// geometries, otherwise it is constructed to the left. For a simple polygon, the orientation of outer rings is - /// clockwise and for inner rings it is counter clockwise. So the "right side" of a simple polygon is always its - /// inside. + /// The distance to offset the input geometry. Unless the unit option is set, the default is the geometry's spatial reference unit. /// /// - /// Measurement unit of the offset distance. Defaults to the units of the input geometries. + /// The length unit of the offset distance. The default is the input geometries spatial reference unit. An error will be thrown if this is set for Geographic Coordinate Systems. /// /// - /// The + /// The . Defines the join type of the offset geometry. Inner corners are always mitered. + /// Default value: "round" + /// - Round - a circular arc that is tangent to the ends of both offset line segments. + /// - Miter - the offset line segments are extended to their intersection point forming a sharp angle, unless that extension exceeds the miterLimit, in which case the result is a bevel. + /// - Bevel - the offset line segments are not extended; their endpoints are joined by a straight line. + /// - Square - same as miter for minor arcs greater than 90 degrees. For all other minor arcs, the offset line segments are extended by an extra distance before their endpoints are joined. /// - /// - /// Applicable when joinType = 'miter'; bevelRatio is multiplied by the offset distance and the result determines how - /// far a mitered offset intersection can be located before it is beveled. + /// + /// Applicable only to mitered joins. Controls the appearance of angled lines to avoid excessively long and pointy corners. The miterLimit is multiplied by the offset distance and the result is the maximum mitered offset: joins which would result in a larger mitered offset will be beveled instead. /// /// - /// Applicable when joinType = 'round'; flattenError determines the maximum distance of the resulting segments compared - /// to the true circular arc. The algorithm never produces more than around 180 vertices for each round join. + /// The maximum distance of the resulting segments compared to the true circular arc (used only when joins is round). The algorithm never produces more than around 180 vertices for each round join. /// /// /// The offset geometry. /// /// The cancellation token to use for the operation. [SerializedMethod] - public async Task Offset(Geometry geometry, double offsetDistance, - GeometryEngineLinearUnit? offsetUnit, JoinType? joinType, double? bevelRatio, + public async Task Offset(Geometry geometry, double offsetDistance, + GeometryEngineLinearUnit? offsetUnit, JoinType? joinType, double? miterLimit, double? flattenError, CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(ProjectionEngine), nameof(Offset), - cancellationToken, geometry, offsetDistance, offsetUnit, joinType, bevelRatio, flattenError); + return await InvokeAsync(nameof(ProjectionEngine), nameof(Offset), + cancellationToken, geometry, offsetDistance, offsetUnit, joinType, miterLimit, flattenError); } /// @@ -1448,8 +1433,7 @@ public async Task Relate(Geometry geometry1, Geometry geometry2, string re } /// - /// Rotates a geometry counterclockwise by the specified number of degrees. Rotation is around the centroid, or a given - /// rotation point. + /// Rotates a geometry counterclockwise by the specified number of degrees. Rotation is around the given rotation point, or the centroid if not specified. /// /// /// The geometry to rotate. @@ -1465,7 +1449,7 @@ public async Task Relate(Geometry geometry1, Geometry geometry2, string re /// /// The cancellation token to use for the operation. [SerializedMethod] - public async Task Rotate(Geometry geometry, double angle, Point rotationOrigin, + public async Task Rotate(Geometry geometry, double angle, Point? rotationOrigin, CancellationToken cancellationToken = default) { return await InvokeAsync(nameof(ProjectionEngine), nameof(Rotate), @@ -1485,9 +1469,9 @@ public async Task Rotate(Geometry geometry, double angle, Point rotati /// /// The cancellation token to use for the operation. [SerializedMethod] - public async Task Simplify(Geometry geometry, CancellationToken cancellationToken = default) + public async Task Simplify(Geometry geometry, CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(ProjectionEngine), nameof(Simplify), + return await InvokeAsync(nameof(ProjectionEngine), nameof(Simplify), cancellationToken, geometry); } @@ -1565,7 +1549,7 @@ public async Task Touches(Geometry geometry1, Geometry geometry2, /// /// The union of the geometries /// - public Task Union(params Geometry[] geometries) + public Task Union(params Geometry[] geometries) { return Union(geometries, CancellationToken.None); } @@ -1581,9 +1565,9 @@ public Task Union(params Geometry[] geometries) /// /// The cancellation token to use for the operation. [SerializedMethod] - public async Task Union(IEnumerable geometries, CancellationToken cancellationToken = default) + public async Task Union(IEnumerable geometries, CancellationToken cancellationToken = default) { - return await InvokeAsync(nameof(ProjectionEngine), nameof(Union), + return await InvokeAsync(nameof(ProjectionEngine), nameof(Union), cancellationToken, geometries.Cast()); } @@ -2134,7 +2118,12 @@ public async Task IsClockwise(Polygon polygon, Point[] ring, CancellationT foreach (var p in ring) { - mapPoints.Add(new MapPoint(p.X!.Value, p.Y!.Value)); + if (p.X is null && p.Longitude is null || p.Y is null && p.Latitude is null) + { + throw new ArgumentException("Points must have X and Y coordinates"); + } + + mapPoints.Add(new MapPoint(p.X ?? p.Longitude!.Value, p.Y ?? p.Latitude!.Value)); } return await IsClockwise(polygon, new MapPath(mapPoints), cancellationToken); diff --git a/src/dymaptic.GeoBlazor.Core/Model/LogicComponent.cs b/src/dymaptic.GeoBlazor.Core/Model/LogicComponent.cs index e8229fe55..534f9d696 100644 --- a/src/dymaptic.GeoBlazor.Core/Model/LogicComponent.cs +++ b/src/dymaptic.GeoBlazor.Core/Model/LogicComponent.cs @@ -6,8 +6,11 @@ namespace dymaptic.GeoBlazor.Core.Model; /// /// A base class for non-map components, such as GeometryEngine, Projection, etc. /// -public abstract class LogicComponent(IAppValidator appValidator, IJSRuntime jsRuntime, - JsModuleManager jsModuleManager, AuthenticationManager authenticationManager) +public abstract class LogicComponent( + IAppValidator appValidator, + IJSRuntime jsRuntime, + JsModuleManager jsModuleManager, + AuthenticationManager authenticationManager) { /// /// The name of the logic component. @@ -23,14 +26,18 @@ public abstract class LogicComponent(IAppValidator appValidator, IJSRuntime jsRu /// A .NET Object reference to this class for use in JavaScript. /// [JsonConverter(typeof(DotNetObjectReferenceJsonConverter))] - protected DotNetObjectReference DotNetComponentReference => - DotNetObjectReference.Create(this); + protected DotNetObjectReference DotNetComponentReference => DotNetObjectReference.Create(this); /// /// The project library which houses this particular logic component. /// protected virtual string Library => "Core"; + /// + /// Boolean flag to identify if GeoBlazor is running in Blazor Server mode + /// + protected bool IsServer => jsRuntime.GetType().Name.Contains("Remote"); + /// /// A JavaScript invokable method that returns a JS Error and converts it to an Exception. /// @@ -44,6 +51,7 @@ public abstract class LogicComponent(IAppValidator appValidator, IJSRuntime jsRu public void OnJavascriptError(JavascriptError error) { var exception = new JavascriptException(error); + throw exception; } @@ -78,7 +86,7 @@ public virtual async Task Initialize(CancellationToken cancellationToken = defau /// /// The collection of parameters to pass to the JS call. /// - internal virtual async Task InvokeVoidAsync(string className, [CallerMemberName] string method = "", + protected internal virtual async Task InvokeVoidAsync(string className, [CallerMemberName] string method = "", params object?[] parameters) { await Initialize(); @@ -101,18 +109,13 @@ internal virtual async Task InvokeVoidAsync(string className, [CallerMemberName] /// /// The collection of parameters to pass to the JS call. /// - internal virtual async Task InvokeAsync(string className, [CallerMemberName]string method = "", + protected internal virtual async Task InvokeAsync(string className, [CallerMemberName] string method = "", CancellationToken cancellationToken = default, params object?[] parameters) { await Initialize(cancellationToken); - + return await Component!.InvokeJsMethod(IsServer, method, className, cancellationToken, parameters); } - + private bool _validated; - - /// - /// Boolean flag to identify if GeoBlazor is running in Blazor Server mode - /// - protected bool IsServer => jsRuntime.GetType().Name.Contains("Remote"); } \ No newline at end of file diff --git a/src/dymaptic.GeoBlazor.Core/Model/ProjectionEngine.cs b/src/dymaptic.GeoBlazor.Core/Model/ProjectionEngine.cs index 2015fea89..740333191 100644 --- a/src/dymaptic.GeoBlazor.Core/Model/ProjectionEngine.cs +++ b/src/dymaptic.GeoBlazor.Core/Model/ProjectionEngine.cs @@ -35,6 +35,11 @@ public ProjectionEngine(IAppValidator appValidator, IJSRuntime jsRuntime, JsModu public Task Project(Geometry[] geometries, SpatialReference spatialReference, CancellationToken cancellationToken = default) { + if (geometries.Length == 0) + { + return Task.FromResult([]); + } + return Project(geometries, spatialReference, null, cancellationToken); } @@ -78,6 +83,12 @@ public ProjectionEngine(IAppValidator appValidator, IJSRuntime jsRuntime, JsModu public Task Project(Geometry geometry, SpatialReference spatialReference, CancellationToken cancellationToken = default) { + if (spatialReference.Equals(geometry.SpatialReference)) + { + // no conversion, just return the same geometry + return Task.FromResult(geometry); + } + return Project(geometry, spatialReference, null, cancellationToken); } diff --git a/src/dymaptic.GeoBlazor.Core/Results/NearestPointResult.cs b/src/dymaptic.GeoBlazor.Core/Results/ProximityResult.cs similarity index 68% rename from src/dymaptic.GeoBlazor.Core/Results/NearestPointResult.cs rename to src/dymaptic.GeoBlazor.Core/Results/ProximityResult.cs index fc9046829..9c4237fa0 100644 --- a/src/dymaptic.GeoBlazor.Core/Results/NearestPointResult.cs +++ b/src/dymaptic.GeoBlazor.Core/Results/ProximityResult.cs @@ -4,7 +4,7 @@ namespace dymaptic.GeoBlazor.Core.Results; /// Object returned from the nearestCoordinate(), nearestVertex(), and nearestVertices() methods of . /// [CodeGenerationIgnore] -public record NearestPointResult +public record ProximityResult { /// /// A vertex within the specified distance of the search. @@ -25,4 +25,12 @@ public record NearestPointResult /// Indicates if it is an empty geometry. /// public bool IsEmpty { get; set; } + + /// + /// Indicates if the nearest coordinate is on the right side or left side of the input point. + /// + /// + /// Note: This property will only be present when calculateLeftRightSide is set to true when calling getNearestCoordinate(). + /// + public bool IsRightSide { get; set; } } \ No newline at end of file diff --git a/src/dymaptic.GeoBlazor.Core/Scripts/baseComponent.ts b/src/dymaptic.GeoBlazor.Core/Scripts/baseComponent.ts index 4f67987e9..590db283b 100644 --- a/src/dymaptic.GeoBlazor.Core/Scripts/baseComponent.ts +++ b/src/dymaptic.GeoBlazor.Core/Scripts/baseComponent.ts @@ -84,6 +84,11 @@ export default class BaseComponent implements IPropertyWrapper { return paramValue; } + if (paramType == 'abortSignal') { + // abortSignal is a JSObjectReference as well + return paramValue; + } + if (Array.isArray(paramValue)) { let arrayValues: any[] = []; for (let i = 0; i < paramValue.length; i++) { @@ -294,6 +299,15 @@ export default class BaseComponent implements IPropertyWrapper { } } + createAbortControllerAndSignal() { + const controller = new AbortController(); + const signal = controller.signal; + return { + abortControllerRef: DotNet.createJSObjectReference(controller), + abortSignalRef: DotNet.createJSObjectReference(signal) + } + } + simpleDotNetTypes = ['int32', 'int64', 'long', 'decimal', 'double', 'single', 'float', 'int', 'bool', 'ulong', 'uint', 'ushort', 'byte', 'sbyte', 'char', 'string', 'datetime', 'dateonly', 'timeonly', 'guid', 'datetimeoffset', 'timespan']; diff --git a/src/dymaptic.GeoBlazor.Core/Scripts/geometryEngine.ts b/src/dymaptic.GeoBlazor.Core/Scripts/geometryEngine.ts index 03b79985c..f27dccb9f 100644 --- a/src/dymaptic.GeoBlazor.Core/Scripts/geometryEngine.ts +++ b/src/dymaptic.GeoBlazor.Core/Scripts/geometryEngine.ts @@ -18,6 +18,7 @@ import LinearUnits = __esri.LinearUnits; import SpatialReferenceInfo = __esri.SpatialReferenceInfo; import AreaUnits = __esri.AreaUnits; import GeometryUnion = __esri.GeometryUnion; +import {AreaUnit, LengthUnit} from "@arcgis/core/core/units"; export default class GeometryEngineWrapper extends BaseComponent { @@ -94,16 +95,36 @@ export default class GeometryEngineWrapper extends BaseComponent { return jsCut.map(g => buildDotNetGeometry(g) as DotNetGeometry); } - async densify(geometry: DotNetGeometry, maxSegmentLength: number, maxSegmentLengthUnit: LinearUnits | null) + async densify(geometries: DotNetGeometry | Array, maxSegmentLength: number, + maxSegmentLengthUnit: LengthUnit | null) : Promise { let densifyOperator = await import('@arcgis/core/geometry/operators/densifyOperator'); - let jsGeometry = buildJsGeometry(geometry) as GeometryUnion; - let jsDensified: Geometry; let options: any = {}; + let hasOptions = false; if (hasValue(maxSegmentLengthUnit)) { options.unit = maxSegmentLengthUnit; + hasOptions = true; + } + + if (geometries instanceof Array) { + let jsGeometries: GeometryUnion[] = geometries.map(buildJsGeometry) as GeometryUnion[]; + let jsDensified: Geometry[]; + if (hasOptions) { + jsDensified = densifyOperator.executeMany(jsGeometries, maxSegmentLength, options); + } else { + jsDensified = densifyOperator.executeMany(jsGeometries, maxSegmentLength); + } + + return jsDensified.map(g => buildDotNetGeometry(g) as DotNetGeometry); + } + + let jsGeometry = buildJsGeometry(geometries) as GeometryUnion; + let jsDensified: Geometry; + if (hasOptions) { + jsDensified = densifyOperator.execute(jsGeometry, maxSegmentLength, options); + } else { + jsDensified = densifyOperator.execute(jsGeometry, maxSegmentLength); } - jsDensified = densifyOperator.execute(jsGeometry, maxSegmentLength, options); return buildDotNetGeometry(jsDensified); } @@ -154,73 +175,102 @@ export default class GeometryEngineWrapper extends BaseComponent { return geometryEngine.extendedSpatialReferenceInfo(spatialReference); } - async flipHorizontal(geometry: DotNetGeometry, flipOrigin: DotNetPoint | null): Promise { - let { default: Transformation } = await import('@arcgis/core/geometry/operators/support/Transformation'); - let transformation = new Transformation(); - let affineTransformOperator = await import('@arcgis/core/geometry/operators/affineTransformOperator'); - let jsGeometry = buildJsGeometry(geometry) as Geometry; - let y0 = jsGeometry.extent?.ymin ?? 0; - let y1 = jsGeometry.extent?.ymax ?? 0; - if (hasValue(flipOrigin?.y)) { - // without a flip origin, the flip point is halfway between the min and max y values - let center = y1 - y0; - // with a set y origin point, we need to calculate the distance from the min and max y values - let flipY = flipOrigin!.y; - if (flipY < center) { - y0 = y1 - (flipY * 2); - } else if (flipY > center) { - y1 = y0 + (flipY * 2); - } - } - transformation.flipY(y0, y1); - let jsFlip = affineTransformOperator.execute(jsGeometry as any, transformation); + async flipHorizontal(geometries: DotNetGeometry | DotNetGeometry[], flipOrigin: DotNetPoint | null): Promise { + return await this.flipImplementation(geometries, flipOrigin, 'x'); + } - return buildDotNetGeometry(jsFlip); + async flipVertical(geometries: DotNetGeometry | DotNetGeometry[], flipOrigin: DotNetPoint | null): Promise { + return await this.flipImplementation(geometries, flipOrigin, 'y'); } - async flipVertical(geometry: DotNetGeometry, flipOrigin: DotNetPoint | null): Promise { + async flipImplementation(geometries: DotNetGeometry | DotNetGeometry[], flipOrigin: DotNetPoint | null, + axis: 'x' | 'y'): Promise { let { default: Transformation } = await import('@arcgis/core/geometry/operators/support/Transformation'); let transformation = new Transformation(); let affineTransformOperator = await import('@arcgis/core/geometry/operators/affineTransformOperator'); - let jsGeometry = buildJsGeometry(geometry) as Geometry; - let x0 = jsGeometry.extent?.xmin ?? 0; - let x1 = jsGeometry.extent?.xmax ?? 0; - if (hasValue(flipOrigin?.x)) { - // without a flip origin, the flip point is halfway between the min and max x values - let center = x1 - x0; - // with a set x origin point, we need to calculate the distance from the min and max x values - let flipX = flipOrigin!.x; - if (flipX < center) { - x0 = x1 - (flipX * 2); - } else if (flipX > center) { - x1 = x0 + (flipX * 2); + + let jsGeometries: GeometryUnion[] = []; + if (geometries instanceof Array) { + jsGeometries = geometries.map(buildJsGeometry) as GeometryUnion[]; + } else { + jsGeometries.push(buildJsGeometry(geometries) as GeometryUnion); + } + + let extent: Extent = jsGeometries[0].extent as Extent; + for (let i = 0; i < jsGeometries.length; i++) { + let geom = jsGeometries[i]; + if (i > 0) { + extent = extent.union(geom.extent as Extent) as Extent; + } + } + let n0 = axis == 'x' ? extent.xmin : extent.ymin; + let n1 = axis == 'x' ? extent.xmax : extent.ymax; + if (hasValue(flipOrigin) && hasValue(flipOrigin![axis])) { + // without a flip origin, the flip point is halfway between the min and max values + let center = n1 - n0 + // with a set origin point, we need to calculate the distance from the min and max values + let flipN = flipOrigin![axis]; + if (flipN < center) { + n0 = n1 - (flipN * 2); + } else if (flipN > center) { + n1 = n0 + (flipN * 2); } } - transformation.flipX(x0, x1); - let jsFlip = affineTransformOperator.execute(jsGeometry as any, transformation); + if (axis == 'x') { + transformation.flipX(n0, n1); + } else { + transformation.flipY(n0, n1); + } + if (jsGeometries.length == 1) { + let jsFlip = affineTransformOperator.execute(jsGeometries[0] as any, transformation); + return buildDotNetGeometry(jsFlip); + } - return buildDotNetGeometry(jsFlip); + let jsFlip = affineTransformOperator.executeMany(jsGeometries, transformation); + return jsFlip.map(g => buildDotNetGeometry(g) as DotNetGeometry); } - async generalize(geometry: DotNetGeometry, maxDeviation: number, removeDegenerateParts: boolean | null, - maxDeviationUnit: LinearUnits | null): Promise { + async generalize(geometries: DotNetGeometry | DotNetGeometry[], maxDeviation: number, + removeDegenerateParts: boolean | null, + maxDeviationUnit: LengthUnit | null): Promise { let generalizeOperator = await import('@arcgis/core/geometry/operators/generalizeOperator'); - let jsGeometry = buildJsGeometry(geometry) as Geometry; - let jsGeneralize: GeometryUnion; + + let jsGeneralize: GeometryUnion | GeometryUnion[]; let options: any = {}; + let hasOptions = false; if (hasValue(removeDegenerateParts)) { options.removeDegenerateParts = removeDegenerateParts; + hasOptions = true; } if (hasValue(maxDeviationUnit)) { options.unit = maxDeviationUnit; + hasOptions = true; + } + + if (geometries instanceof Array) { + let jsGeometries = geometries.map(buildJsGeometry) as GeometryUnion[]; + + if (hasOptions) { + jsGeneralize = generalizeOperator.executeMany(jsGeometries, maxDeviation, options) as GeometryUnion[]; + } else { + jsGeneralize = generalizeOperator.executeMany(jsGeometries, maxDeviation, options) as GeometryUnion[]; + } + + return jsGeneralize.map(g => buildDotNetGeometry(g) as DotNetGeometry); + } + + let jsGeometry = buildJsGeometry(geometries) as GeometryUnion; + + if (hasOptions) { + jsGeneralize = generalizeOperator.execute(jsGeometry as GeometryUnion, maxDeviation, options) as GeometryUnion; + } else { + jsGeneralize = generalizeOperator.execute(jsGeometry as GeometryUnion, maxDeviation) as GeometryUnion; } - - jsGeneralize = generalizeOperator.execute(jsGeometry as GeometryUnion, maxDeviation, options) as GeometryUnion; return buildDotNetGeometry(jsGeneralize); } - async geodesicArea(geometry: DotNetPolygon, unit: AreaUnits | null): Promise { + async geodesicArea(geometry: DotNetPolygon, unit: AreaUnit | null): Promise { let geodeticAreaOperator = await import('@arcgis/core/geometry/operators/geodeticAreaOperator'); if (!geodeticAreaOperator.isLoaded()) { await geodeticAreaOperator.load(); @@ -236,7 +286,7 @@ export default class GeometryEngineWrapper extends BaseComponent { } async geodesicBuffer(geometries: Array | DotNetGeometry, distances: Array | number, - unit: LinearUnits | null, unionResults: boolean | null): Promise { + unit: LengthUnit | null, unionResults: boolean | null): Promise { let geodesicBufferOperator = await import('@arcgis/core/geometry/operators/geodesicBufferOperator'); if (!geodesicBufferOperator.isLoaded()) { await geodesicBufferOperator.load(); @@ -264,18 +314,40 @@ export default class GeometryEngineWrapper extends BaseComponent { return buildDotNetPolygon(jsBuffer); } - async geodesicDensify(geometry: DotNetGeometry, maxSegmentLength: number, - maxSegmentLengthUnit: LinearUnits | null): Promise { + async geodesicDensify(geometries: DotNetGeometry | DotNetGeometry[], maxSegmentLength: number, + maxSegmentLengthUnit: LengthUnit | null): Promise { let geodeticDensifyOperator = await import('@arcgis/core/geometry/operators/geodeticDensifyOperator'); if (!geodeticDensifyOperator.isLoaded()) { await geodeticDensifyOperator.load(); } - let jsDensify: GeometryUnion; + let options: any = {}; + let hasOptions = false; if (hasValue(maxSegmentLengthUnit)) { options.unit = maxSegmentLengthUnit; + hasOptions = true; + } + + let jsDensify: GeometryUnion | GeometryUnion[]; + + if (geometries instanceof Array) { + let jsGeometries = geometries.map(buildJsGeometry) as GeometryUnion[]; + if (hasOptions) { + jsDensify = geodeticDensifyOperator.executeMany(jsGeometries, maxSegmentLength, options) as GeometryUnion[]; + } else { + jsDensify = geodeticDensifyOperator.executeMany(jsGeometries, maxSegmentLength) as GeometryUnion[]; + } + + return jsDensify.map(buildDotNetGeometry); + } + + let jsGeometry = buildJsGeometry(geometries) as GeometryUnion; + + if (hasOptions) { + jsDensify = geodeticDensifyOperator.execute(jsGeometry, maxSegmentLength, options) as GeometryUnion; + } else { + jsDensify = geodeticDensifyOperator.execute(jsGeometry, maxSegmentLength) as GeometryUnion; } - jsDensify = geodeticDensifyOperator.execute(buildJsGeometry(geometry) as GeometryUnion, maxSegmentLength, options) as GeometryUnion; return buildDotNetGeometry(jsDensify); @@ -366,39 +438,57 @@ export default class GeometryEngineWrapper extends BaseComponent { } async offset(geometries: Array | DotNetGeometry, offsetDistance: number, - offsetUnit: LinearUnits | null | undefined, joinType: any | null | undefined, - bevelRatio: number | null | undefined, flattenError: number | null | undefined): Promise { + offsetUnit: LengthUnit | null | undefined, joinType: any | null | undefined, + miterLimit: number | null | undefined, flattenError: number | null | undefined): Promise { let offsetOperator = await import('@arcgis/core/geometry/operators/offsetOperator'); let jsGeometries: GeometryUnion | Array let options: any = {}; + let hasOptions = false; if (hasValue(offsetUnit)) { options.unit = offsetUnit; + hasOptions = true; } if (hasValue(joinType)) { options.joins = joinType; + hasOptions = true; } if (hasValue(flattenError)) { options.flattenError = flattenError; + hasOptions = true; + } + + if (hasValue(miterLimit)) { + options.miterLimit = miterLimit; + hasOptions = true; } + + let jsOffset: GeometryUnion | Array; if (Array.isArray(geometries)) { jsGeometries = []; geometries.forEach(g => (jsGeometries as Array).push(buildJsGeometry(g) as GeometryUnion)); - let jsOffset = offsetOperator.executeMany(jsGeometries as GeometryUnion[], offsetDistance, options); + if (hasOptions) { + jsOffset = offsetOperator.executeMany(jsGeometries as GeometryUnion[], offsetDistance, options); + } else { + jsOffset = offsetOperator.executeMany(jsGeometries as GeometryUnion[], offsetDistance); + } return jsOffset.map(g => buildDotNetGeometry(g) as DotNetGeometry); } jsGeometries = buildJsGeometry(geometries as DotNetGeometry) as GeometryUnion; - let jsOffset = offsetOperator.execute(jsGeometries as GeometryUnion, offsetDistance, options) as GeometryUnion; + if (hasOptions) { + jsOffset = offsetOperator.execute(jsGeometries as GeometryUnion, offsetDistance, options) as GeometryUnion; + } else { + jsOffset = offsetOperator.execute(jsGeometries as GeometryUnion, offsetDistance) as GeometryUnion; + } return buildDotNetGeometry(jsOffset); } async overlaps(geometry1: DotNetGeometry, geometry2: DotNetGeometry): Promise { let overlapsOperator = await import('@arcgis/core/geometry/operators/overlapsOperator'); return overlapsOperator.execute(buildJsGeometry(geometry1) as GeometryUnion, buildJsGeometry(geometry2) as GeometryUnion); - } - async planarArea(geometry: DotNetPolygon, unit: AreaUnits | null): Promise { + async planarArea(geometry: DotNetPolygon, unit: AreaUnit | null): Promise { let areaOperator = await import('@arcgis/core/geometry/operators/areaOperator'); if (unit === null) { return areaOperator.execute(buildJsPolygon(geometry) as Polygon); @@ -406,7 +496,7 @@ export default class GeometryEngineWrapper extends BaseComponent { return areaOperator.execute(buildJsPolygon(geometry) as Polygon, {unit: unit as any}); } - async planarLength(geometry: DotNetGeometry, unit: LinearUnits | null): Promise { + async planarLength(geometry: DotNetGeometry, unit: LengthUnit | null): Promise { let lengthOperator = await import('@arcgis/core/geometry/operators/lengthOperator'); if (unit === null) { return lengthOperator.execute(buildJsGeometry(geometry) as GeometryUnion); @@ -421,23 +511,55 @@ export default class GeometryEngineWrapper extends BaseComponent { } - async rotate(geometry: DotNetGeometry, angle: number, rotationOrigin: DotNetPoint): Promise { + async rotate(geometries: DotNetGeometry | DotNetGeometry[], angle: number, rotationOrigin: DotNetPoint): Promise { let { default: Transformation } = await import('@arcgis/core/geometry/operators/support/Transformation'); let transformation = new Transformation(); let affineTransformOperator = await import('@arcgis/core/geometry/operators/affineTransformOperator'); - let jsRotationOrigin = buildJsPoint(rotationOrigin) as Point; + + let jsGeometries: Array = []; + if (geometries instanceof Array) { + jsGeometries = geometries.map(buildJsGeometry) as Array; + } else { + jsGeometries.push(buildJsGeometry(geometries) as GeometryUnion); + } + + let jsRotationOrigin: Point; + + if (!hasValue(rotationOrigin)) { + let geom: GeometryUnion = jsGeometries[0] as GeometryUnion; + + if (jsGeometries.length > 1) { + let unionOperator = await import('@arcgis/core/geometry/operators/unionOperator'); + geom = unionOperator.executeMany(jsGeometries) as GeometryUnion; + } + let centroidOperator = await import('@arcgis/core/geometry/operators/centroidOperator'); + jsRotationOrigin = centroidOperator.execute(geom); + } else { + jsRotationOrigin = buildJsPoint(rotationOrigin) as Point; + } + transformation.rotate(angle, jsRotationOrigin.x, jsRotationOrigin.y) - let jsRotated = affineTransformOperator.execute(buildJsGeometry(geometry) as GeometryUnion, - transformation); + + let jsRotated: GeometryUnion | Array; + if (jsGeometries.length > 1) { + jsRotated = affineTransformOperator.executeMany(jsGeometries, transformation) as Array; + return jsRotated.map(g => buildDotNetGeometry(g) as DotNetGeometry); + } + jsRotated = affineTransformOperator.execute(jsGeometries[0] as GeometryUnion, transformation); return buildDotNetGeometry(jsRotated); } - async simplify(geometry: DotNetGeometry): Promise { + async simplify(geometries: DotNetGeometry | DotNetGeometry[]): Promise { let simplifyOperator = await import('@arcgis/core/geometry/operators/simplifyOperator'); - let jsSimplified = simplifyOperator.execute(buildJsGeometry(geometry) as GeometryUnion); - return buildDotNetGeometry(jsSimplified); + if (geometries instanceof Array) { + let jsGeometries = geometries.map(buildJsGeometry) as Array; + let jsSimplified = simplifyOperator.executeMany(jsGeometries) as GeometryUnion[]; + return jsSimplified.map(g => buildDotNetGeometry(g) as DotNetGeometry); + } + let jsSimplified = simplifyOperator.execute(buildJsGeometry(geometries) as GeometryUnion); + return buildDotNetGeometry(jsSimplified); } async symmetricDifference(leftGeometry: Array | DotNetGeometry, rightGeometry: DotNetGeometry) diff --git a/src/dymaptic.GeoBlazor.Core/Serialization/CoreSerializationData.cs b/src/dymaptic.GeoBlazor.Core/Serialization/CoreSerializationData.cs index 8651a13d3..ceb179c72 100644 --- a/src/dymaptic.GeoBlazor.Core/Serialization/CoreSerializationData.cs +++ b/src/dymaptic.GeoBlazor.Core/Serialization/CoreSerializationData.cs @@ -13,4 +13,15 @@ internal static partial class CoreSerializationData public static partial Dictionary ProtoContractTypes { get; } public static partial Dictionary ProtoCollectionTypes { get; } + + public static partial object ToProtobufParameter(this object value, Type serializableType, bool isServer); + + public static partial object ToProtobufCollectionParameter(this IList items, Type serializableType, bool isServer); + + public static partial Task ReadJsStreamReferenceAsProtobuf(this IJSStreamReference jsStreamReference, + Type returnType, long maxAllowedSize = 1_000_000_000); + + public static partial Task ReadJsStreamReferenceAsProtobufCollection( + this IJSStreamReference jsStreamReference, + Type returnType, long maxAllowedSize = 1_000_000_000); } \ No newline at end of file diff --git a/src/dymaptic.GeoBlazor.Core/Serialization/JsSyncManager.cs b/src/dymaptic.GeoBlazor.Core/Serialization/JsSyncManager.cs index e275e41b0..1fe9098aa 100644 --- a/src/dymaptic.GeoBlazor.Core/Serialization/JsSyncManager.cs +++ b/src/dymaptic.GeoBlazor.Core/Serialization/JsSyncManager.cs @@ -37,7 +37,8 @@ public static void Initialize() RuntimeTypeModel.Default.CompileInPlace(); _serializableMethods = SerializableMethods - .ToDictionary(g => g.Key, g => g.Value.ToList()); + .ToDictionary(g => g.Key, + g => g.Value.ToList()); } /// @@ -66,7 +67,7 @@ public static async Task InvokeVoidJsMethod(this IJSObjectReference js, bool isS CancellationToken cancellationToken = default, params object?[] parameters) { SerializableMethodRecord methodRecord = GetMethodRecord(method, className, true, parameters); - List parameterList = GenerateSerializedParameters(methodRecord, parameters, isServer); + List parameterList = await GenerateSerializedParameters(methodRecord, parameters, isServer, js); await js.InvokeVoidAsync("invokeVoidSerializedMethod", cancellationToken, [methodRecord.MethodName, isServer, ..parameterList]); @@ -100,8 +101,7 @@ public static async Task InvokeJsMethod(this IJSObjectReference js, bool i { SerializableMethodRecord methodRecord = GetMethodRecord(method, className, false, parameters); - List parameterList = GenerateSerializedParameters(methodRecord, parameters, isServer); - + List parameterList = await GenerateSerializedParameters(methodRecord, parameters, isServer, js); Type? returnType = methodRecord.ReturnValue?.Type; bool returnTypeIsProtobuf = returnType is not null && ProtoContractTypes.ContainsKey(returnType); @@ -199,27 +199,14 @@ private static SerializableMethodRecord GetMethodRecord(string method, string matchedMethods = []; - foreach (MethodInfo methodInfo in methodInfos) + foreach (MethodInfo methodInfo in methodInfos.Where(m => m.ReturnType.IsGenericType)) { List methodParams = []; var paramInfos = methodInfo.GetParameters(); foreach (ParameterInfo paramInfo in paramInfos) { - NullabilityInfo nullabilityInfo = nullabilityContext.Create(paramInfo); - bool isNullable = nullabilityInfo.ReadState == NullabilityState.Nullable; - - Type paramType = paramInfo.ParameterType.IsGenericType && - paramInfo.ParameterType.GetGenericTypeDefinition() == typeof(Nullable<>) - ? Nullable.GetUnderlyingType(paramInfo.ParameterType)! - : paramInfo.ParameterType; - - Type? collectionType = paramType.IsArray - ? paramType.GetElementType() - : paramType is { IsGenericType: true, GenericTypeArguments.Length: 1 } - ? paramType.GenericTypeArguments[0] - : null; - methodParams.Add(new SerializableParameterRecord(paramType, isNullable, collectionType)); + methodParams.Add(CreateParameterRecord(paramInfo.ParameterType, paramInfo)); } SerializableParameterRecord? returnRecord = null; @@ -229,53 +216,19 @@ private static SerializableMethodRecord GetMethodRecord(string method, string ParameterInfo returnParamInfo = methodInfo.ReturnParameter; Type returnType = returnParamInfo.ParameterType; - if (returnType.Name.StartsWith("Task") || returnType.Name.StartsWith("ValueTask")) - { - returnType = returnType.IsGenericType - ? returnType.GenericTypeArguments[0] - : typeof(void); - } - - bool isNullable = false; - - if (returnType.IsGenericType && - methodInfo.ReturnType.GetGenericTypeDefinition() == typeof(Nullable<>)) - { - isNullable = true; - returnType = Nullable.GetUnderlyingType(returnType)!; - } - - bool returnIsCollection = returnType.IsArray || - returnType is { IsGenericType: true, GenericTypeArguments.Length: 1 }; - - Type? singleType = null; + returnRecord = CreateParameterRecord(returnType, returnParamInfo); - if (returnIsCollection) + if (returnRecord.Type.IsGenericType) { - singleType = returnType.IsArray - ? returnType.GetElementType() - : returnType.GenericTypeArguments[0]; + // skip methods that return generic types, they cannot be handled + continue; } - - bool isGenericParameter = returnType.IsGenericParameter; - - if (isGenericParameter) - { - returnType = returnType.GenericTypeArguments[0]; - } - - returnRecord = new SerializableParameterRecord(returnType, isNullable, singleType); } SerializableMethodRecord methodRecord = new(method.ToLowerFirstChar(), methodParams.ToArray(), returnRecord); matchedMethods.Add(methodRecord); - - if (methodRecord.ReturnValue?.Type.IsGenericType != true) - { - // only add non-generic return typed methods to the dictionary - classMethods.Add(methodRecord); - } + classMethods.Add(methodRecord); } } @@ -332,8 +285,8 @@ private static SerializableMethodRecord GetMethodRecord(string method, string }); } - private static List GenerateSerializedParameters(SerializableMethodRecord methodRecord, - object?[] parameters, bool isServer) + private static async Task> GenerateSerializedParameters(SerializableMethodRecord methodRecord, + object?[] parameters, bool isServer, IJSObjectReference js) { List serializedParameters = []; @@ -341,7 +294,7 @@ private static SerializableMethodRecord GetMethodRecord(string method, string { object? parameterValue = parameters[i]; SerializableParameterRecord parameterRecord = methodRecord.Parameters[i]; - serializedParameters.AddRange(ProcessParameter(parameterValue, parameterRecord, isServer)); + serializedParameters.AddRange(await ProcessParameter(parameterValue, parameterRecord, isServer, js)); } return serializedParameters; @@ -359,8 +312,12 @@ private static SerializableMethodRecord GetMethodRecord(string method, string /// /// Boolean flag to identify if GeoBlazor is running in Blazor Server mode /// - private static object?[] ProcessParameter(object? parameterValue, SerializableParameterRecord parameterRecord, - bool isServer) + /// + /// The current class instance JS object reference + /// + private static async Task ProcessParameter(object? parameterValue, + SerializableParameterRecord parameterRecord, + bool isServer, IJSObjectReference js) { if (parameterValue is null) { @@ -369,6 +326,21 @@ private static SerializableMethodRecord GetMethodRecord(string method, string Type paramType = parameterRecord.Type; + if (paramType == typeof(CancellationToken)) + { + if (!abortManagers.TryGetValue(js, out AbortManager? abortManager)) + { + abortManager = new AbortManager(js); + abortManagers[js] = abortManager; + } + + object? value = parameterValue is CancellationToken token + ? await abortManager.CreateAbortSignal(token) + : null; + + return ["abortSignal", value]; + } + if (simpleTypes.Contains(paramType) || paramType.IsPrimitive) { return [GetKey(paramType), parameterValue]; @@ -393,11 +365,11 @@ private static SerializableMethodRecord GetMethodRecord(string method, string Type underlyingType = Nullable.GetUnderlyingType(paramType)!; object underlyingValue = Convert.ChangeType(parameterValue, underlyingType); - return ProcessParameter(underlyingValue, parameterRecord with { Type = underlyingType, IsNullable = true }, - isServer); + return await ProcessParameter(underlyingValue, + parameterRecord with { Type = underlyingType, IsNullable = true }, isServer, js); } - if (parameterValue is IList list && paramType != typeof(MapPath) && paramType != typeof(MapPoint)) + if (parameterValue is IList list && !geoblazorEnumerableTypes.Contains(paramType)) { Type genericType = parameterRecord.SingleType ?? (paramType.IsArray ? paramType.GetElementType()! @@ -435,7 +407,7 @@ private static SerializableMethodRecord GetMethodRecord(string method, string } byte[] data = memoryStream.ToArray(); - memoryStream.Dispose(); + await memoryStream.DisposeAsync(); return [nameof(AttributesDictionary), data]; } @@ -473,63 +445,94 @@ private static SerializableParameterRecord GetSerializableParameterRecord(object { if (parameter is null) { - return new SerializableParameterRecord(typeof(object), true, null); + return new SerializableParameterRecord(typeof(object), true, + null, false); } Type paramType = parameter.GetType(); - if (simpleTypes.Contains(paramType)) - { - return new SerializableParameterRecord(paramType, true, null); - } + return CreateParameterRecord(paramType, null); + } - if (paramType.Name.Contains("AnonymousType")) + private static SerializableParameterRecord GetSerializableReturnRecord(bool returnsVoid) + { + if (returnsVoid) { - // anonymous object - return new SerializableParameterRecord(typeof(object), true, null); + return new SerializableParameterRecord(typeof(void), false, + null, false); } - bool isCollection = paramType.IsAssignableTo(typeof(IEnumerable)); - - Type? collectionType = isCollection - ? paramType.IsArray - ? paramType.GetElementType() - : paramType.GetGenericArguments()[0] - : null; + Type returnType = typeof(T); - return new SerializableParameterRecord(paramType, true, collectionType); + return CreateParameterRecord(returnType, null); } - private static SerializableParameterRecord GetSerializableReturnRecord(bool returnsVoid) + private static SerializableParameterRecord CreateParameterRecord(Type paramType, ParameterInfo? parameterInfo) { - if (returnsVoid) + if (paramType.Name.StartsWith("Task") || paramType.Name.StartsWith("ValueTask")) { - return new SerializableParameterRecord(typeof(void), false, null); + paramType = paramType.IsGenericType + ? paramType.GenericTypeArguments[0] + : typeof(void); } - Type returnType = typeof(T); + if (simpleTypes.Contains(paramType) + + // these types will trigger the "isCollection" check below, but should be treated as single types + || geoblazorEnumerableTypes.Contains(paramType)) + { + return new SerializableParameterRecord(paramType, true, + null, false); + } - if (simpleTypes.Contains(returnType)) + if (paramType.Name.Contains("AnonymousType")) { - return new SerializableParameterRecord(returnType, true, null); + // anonymous object + return new SerializableParameterRecord(typeof(object), true, + null, false); } - if (returnType == typeof(AttributesDictionary)) + if (paramType == typeof(AttributesDictionary)) { return new SerializableParameterRecord(typeof(AttributesDictionary), true, - typeof(AttributeSerializationRecord)); + typeof(AttributeSerializationRecord), false); + } + + bool typeIsNullable = IsTypeNullable(paramType) + || (parameterInfo is not null && IsParameterNullable(parameterInfo)); + + // unwrap nullable types + if (typeIsNullable && paramType.IsGenericType && paramType.GetGenericTypeDefinition() == typeof(Nullable<>)) + { + paramType = Nullable.GetUnderlyingType(paramType)!; } - bool isCollection = returnType.IsAssignableTo(typeof(IEnumerable)) - && !returnType.IsAssignableTo(typeof(IDictionary)); + bool isCollection = paramType.IsAssignableTo(typeof(IEnumerable)) + && !paramType.IsAssignableTo(typeof(IDictionary)); Type? collectionType = isCollection - ? returnType.IsArray - ? returnType.GetElementType() - : returnType.GetGenericArguments()[0] + ? paramType.IsArray + ? paramType.GetElementType() + : paramType.GenericTypeArguments[0] : null; - return new SerializableParameterRecord(returnType, true, collectionType); + bool collectionTypeIsNullable = IsTypeNullable(collectionType); + + return new SerializableParameterRecord(paramType, typeIsNullable, + collectionType, collectionTypeIsNullable); + } + + static bool IsParameterNullable(ParameterInfo paramInfo) + { + NullabilityInfo nullabilityInfo = nullabilityContext.Create(paramInfo); + + return nullabilityInfo.ReadState == NullabilityState.Nullable; + } + + static bool IsTypeNullable(Type? type) + { + return type is { IsGenericType: true } + && type.GetGenericTypeDefinition() == typeof(Nullable<>); } private static readonly NullabilityInfoContext nullabilityContext = new(); @@ -542,6 +545,12 @@ private static SerializableParameterRecord GetSerializableReturnRecord(bool r typeof(Guid), typeof(DateOnly), typeof(TimeOnly) ]; + private static readonly Type[] geoblazorEnumerableTypes = + [ + typeof(MapPath), typeof(MapPoint) + ]; + private static readonly Dictionary abortManagers = []; + private static Dictionary> _serializableMethods = []; } @@ -562,4 +571,5 @@ public record SerializableMethodRecord( /// The type of the parameter. /// Whether the parameter is nullable. /// The single generic type argument, if applicable. -public record SerializableParameterRecord(Type Type, bool IsNullable, Type? SingleType); \ No newline at end of file +/// Whether the single generic type argument is nullable, if applicable. +public record SerializableParameterRecord(Type Type, bool IsNullable, Type? SingleType, bool SingleTypeIsNullable); \ No newline at end of file diff --git a/src/dymaptic.GeoBlazor.Core/dymaptic.GeoBlazor.Core.csproj b/src/dymaptic.GeoBlazor.Core/dymaptic.GeoBlazor.Core.csproj index 8e0b40e6e..ab06ecc0b 100644 --- a/src/dymaptic.GeoBlazor.Core/dymaptic.GeoBlazor.Core.csproj +++ b/src/dymaptic.GeoBlazor.Core/dymaptic.GeoBlazor.Core.csproj @@ -59,7 +59,7 @@ - + diff --git a/src/dymaptic.GeoBlazor.Core/esBuild.js b/src/dymaptic.GeoBlazor.Core/esBuild.js index 2ca0e4eee..3cc5a9bb0 100644 --- a/src/dymaptic.GeoBlazor.Core/esBuild.js +++ b/src/dymaptic.GeoBlazor.Core/esBuild.js @@ -29,7 +29,12 @@ function saveBuildRecord() { } let options = { - entryPoints: ['./Scripts/geoBlazorCore.ts'], + entryPoints: [ + './Scripts/geoBlazorCore.ts', // main entry point + './Scripts/geometryEngine.ts', // logic components + './Scripts/locationService.ts', + './Scripts/projectionEngine.ts' + ], chunkNames: 'core_[name]_[hash]', bundle: true, sourcemap: true, diff --git a/test/dymaptic.GeoBlazor.Core.SourceGenerator.Tests/dymaptic.GeoBlazor.Core.SourceGenerator.Tests.csproj b/test/dymaptic.GeoBlazor.Core.SourceGenerator.Tests/dymaptic.GeoBlazor.Core.SourceGenerator.Tests.csproj index e762b7f21..06afe24a3 100644 --- a/test/dymaptic.GeoBlazor.Core.SourceGenerator.Tests/dymaptic.GeoBlazor.Core.SourceGenerator.Tests.csproj +++ b/test/dymaptic.GeoBlazor.Core.SourceGenerator.Tests/dymaptic.GeoBlazor.Core.SourceGenerator.Tests.csproj @@ -10,18 +10,19 @@ - - + + - - - + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + + diff --git a/test/dymaptic.GeoBlazor.Core.Test.Automation/dymaptic.GeoBlazor.Core.Test.Automation.csproj b/test/dymaptic.GeoBlazor.Core.Test.Automation/dymaptic.GeoBlazor.Core.Test.Automation.csproj index ab82b4d07..5cc2659f1 100644 --- a/test/dymaptic.GeoBlazor.Core.Test.Automation/dymaptic.GeoBlazor.Core.Test.Automation.csproj +++ b/test/dymaptic.GeoBlazor.Core.Test.Automation/dymaptic.GeoBlazor.Core.Test.Automation.csproj @@ -14,16 +14,16 @@ - - - - - - + + + + + + - + diff --git a/test/dymaptic.GeoBlazor.Core.Test.Blazor.Shared/Components/GeometryEngineTests.cs b/test/dymaptic.GeoBlazor.Core.Test.Blazor.Shared/Components/GeometryEngineTests.cs index f0fda16a7..36df8024c 100644 --- a/test/dymaptic.GeoBlazor.Core.Test.Blazor.Shared/Components/GeometryEngineTests.cs +++ b/test/dymaptic.GeoBlazor.Core.Test.Blazor.Shared/Components/GeometryEngineTests.cs @@ -179,7 +179,7 @@ public async Task TestBufferCallAfterDensified() Polygon buffer = await GeometryEngine.Buffer(polyline, 20, GeometryEngineLinearUnit.Yards); Assert.IsNotNull(buffer); } - + [TestMethod] public async Task TestClip() { @@ -219,7 +219,7 @@ public async Task TestClipNoOverlapReturnsNull() Polygon? clippedPolygon = await GeometryEngine.Clip(boundaryPolygon, envelope) as Polygon; Assert.IsNull(clippedPolygon); } - + [TestMethod] public async Task TestContainsTrue() { @@ -678,6 +678,7 @@ await GeometryEngine.Generalize(complexPolygon, 1, true, GeometryEngineLinearUnit.Feet) as Polygon; Assert.IsNotNull(generalizedPolygon?.Rings); Assert.AreNotEqual(complexPolygon, generalizedPolygon); + Assert.IsGreaterThan(generalizedPolygon.Rings.Select(r => r.Count).Sum(), complexPolygon.Rings.Select(r => r.Count).Sum()); } @@ -931,7 +932,7 @@ public async Task TestNearestCoordinate() Point point = new Point(15, 15); - NearestPointResult result = await GeometryEngine.NearestCoordinate(polygon, point); + ProximityResult result = await GeometryEngine.NearestCoordinate(polygon, point); Assert.AreEqual(10, result.Coordinate.X); Assert.AreEqual(10, result.Coordinate.Y); @@ -947,7 +948,7 @@ public async Task TestNearestVertex() Point point = new Point(15, 5); - NearestPointResult result = await GeometryEngine.NearestVertex(polygon, point); + ProximityResult result = await GeometryEngine.NearestVertex(polygon, point); Assert.AreEqual(10, result.Coordinate.X); Assert.AreEqual(10, result.Coordinate.Y); @@ -970,7 +971,7 @@ public async Task TestNearestVertices() Point point = new Point(15, 5); - NearestPointResult[] result = await GeometryEngine.NearestVertices(polygon, point, 200, 100); + ProximityResult[] result = await GeometryEngine.NearestVertices(polygon, point, 200, 100); Assert.HasCount(6, result); } @@ -1469,18 +1470,20 @@ public async Task TestToAndFromArcGisJson() new MapPoint(0, 0) ] ], new SpatialReference(102100)); - + string json = await GeometryEngine.ToArcGisJson(polygon1); Geometry fromJson = await GeometryEngine.FromArcGisJson(json); Assert.IsNotNull(fromJson); Assert.AreEqual(polygon1.Type, fromJson.Type); Assert.AreEqual(polygon1.Rings[0], ((Polygon)fromJson).Rings[0]); } - + [TestMethod] public async Task TestClone_ToFromJson() { - Polygon polygon = new Polygon([new MapPath(new MapPoint(0, 0), new MapPoint(0, 1), new MapPoint(1, 1), new MapPoint(1, 0), new MapPoint(0, 0)) + Polygon polygon = new Polygon([ + new MapPath(new MapPoint(0, 0), new MapPoint(0, 1), new MapPoint(1, 1), new MapPoint(1, 0), + new MapPoint(0, 0)) ], new SpatialReference(102100)); // Clone @@ -1531,10 +1534,434 @@ public async Task TestExtentHelpers_And_NormalizePoint() Assert.IsNotNull(maybeWidth); } + [TestMethod] + public async Task TestIsClockwise() + { + MapPath ring = new MapPath(new MapPoint(0, 0), new MapPoint(0, 10), new MapPoint(10, 10), new MapPoint(10, 0), + new MapPoint(0, 0)); + Polygon polygon = new Polygon([ring], new SpatialReference(102100)); + + bool result = await GeometryEngine.IsClockwise(polygon, ring); + Assert.IsInstanceOfType(result, typeof(bool)); + } + + [TestMethod] + public async Task TestIsClockwiseWithPointArray() + { + Point[] ring = + [ + new Point(0, 0, spatialReference: new SpatialReference(102100)), + new Point(0, 10, spatialReference: new SpatialReference(102100)), + new Point(10, 10, spatialReference: new SpatialReference(102100)), + new Point(10, 0, spatialReference: new SpatialReference(102100)), + new Point(0, 0, spatialReference: new SpatialReference(102100)) + ]; + + Polygon polygon = + new Polygon( + [ + new MapPath(new MapPoint(0, 0), new MapPoint(0, 10), new MapPoint(10, 10), new MapPoint(10, 0), + new MapPoint(0, 0)) + ], new SpatialReference(102100)); + + bool result = await GeometryEngine.IsClockwise(polygon, ring); + Assert.IsInstanceOfType(result, typeof(bool)); + } + + [TestMethod] + public async Task TestPolygonFromExtent() + { + Extent extent = new Extent(10, 0, 10, 0, spatialReference: new SpatialReference(102100)); + + Polygon polygon = await GeometryEngine.PolygonFromExtent(extent); + + Assert.IsNotNull(polygon); + Assert.IsNotNull(polygon.Rings); + Assert.HasCount(1, polygon.Rings); + } + + [TestMethod] + public async Task TestBufferSingleGeometryWithoutUnit() + { + Polygon polygon = + new Polygon([ + [ + new MapPoint(0, 0), + new MapPoint(0, 10), + new MapPoint(10, 10), + new MapPoint(10, 0), + new MapPoint(0, 0) + ] + ], new SpatialReference(103002)); + + Polygon buffer = await GeometryEngine.Buffer(polygon, 10.0); + Assert.IsNotNull(buffer); + Assert.AreNotEqual(polygon, buffer); + } + + [TestMethod] + public async Task TestGeodesicBufferMultipleGeometriesUnioned() + { + Polygon polygon1 = new Polygon([ + [ + new MapPoint(0, 0), + new MapPoint(0, 1), + new MapPoint(1, 1), + new MapPoint(1, 0), + new MapPoint(0, 0) + ] + ]); + + Polygon polygon2 = new Polygon([ + [ + new MapPoint(0.5, 0), + new MapPoint(0.5, 1), + new MapPoint(1.5, 1), + new MapPoint(1.5, 0), + new MapPoint(0.5, 0) + ] + ]); + + Polygon[] bufferedGeometries = + await GeometryEngine.GeodesicBuffer([polygon1, polygon2], [10, 10], + GeometryEngineLinearUnit.Feet, true); + + Assert.IsNotNull(bufferedGeometries); + Assert.HasCount(1, bufferedGeometries); + } + + [TestMethod] + public async Task TestAddPathWithMapPath() + { + Polyline polyline = new Polyline([[[0, 0], [1, 1]]], new SpatialReference(102100)); + MapPath newPath = new MapPath(new MapPoint(2, 2), new MapPoint(3, 3), new MapPoint(4, 4)); + + Polyline added = await GeometryEngine.AddPath(polyline, newPath); + + Assert.IsNotNull(added); + Assert.IsGreaterThanOrEqualTo(2, added.Paths.Count); + } + + [TestMethod] + public async Task TestAddRingWithMapPath() + { + Polygon polygon = new Polygon([ + new MapPath(new MapPoint(0, 0), new MapPoint(0, 5), new MapPoint(5, 5), new MapPoint(5, 0), + new MapPoint(0, 0)) + ], new SpatialReference(102100)); + + MapPath newRing = new MapPath(new MapPoint(10, 10), new MapPoint(10, 15), new MapPoint(15, 15), + new MapPoint(15, 10), new MapPoint(10, 10)); + + Polygon withRing = await GeometryEngine.AddRing(polygon, newRing); + + Assert.IsNotNull(withRing); + Assert.HasCount(2, withRing.Rings); + } + + [TestMethod] + public async Task TestPlanarLengthWithoutUnit() + { + Polyline polyline = + new Polyline([ + [new MapPoint(0, 0), new MapPoint(0, 10), new MapPoint(10, 10), new MapPoint(10, 0)] + ], new SpatialReference(102100)); + + double length = await GeometryEngine.PlanarLength(polyline); + + Assert.IsGreaterThan(0, length); + } + + [TestMethod] + public async Task TestOffsetSingleGeometry() + { + Polygon polygon = + new Polygon([ + [ + new MapPoint(0, 0), + new MapPoint(0, 10), + new MapPoint(10, 10), + new MapPoint(10, 0), + new MapPoint(0, 0) + ] + ], new SpatialReference(102100)); + + Geometry offset = await GeometryEngine.Offset(polygon, 10); + + Assert.IsNotNull(offset); + Assert.AreNotEqual(polygon, offset); + } + + [TestMethod] + public async Task TestOffsetSingleGeometryWithUnit() + { + Polygon polygon = + new Polygon([ + [ + new MapPoint(0, 0), + new MapPoint(0, 10), + new MapPoint(10, 10), + new MapPoint(10, 0), + new MapPoint(0, 0) + ] + ], new SpatialReference(102100)); + + Geometry offset = await GeometryEngine.Offset(polygon, 10, GeometryEngineLinearUnit.Feet); + + Assert.IsNotNull(offset); + Assert.AreNotEqual(polygon, offset); + } + + [TestMethod] + public async Task TestOffsetSingleGeometryWithJoinType() + { + Polygon polygon = + new Polygon([ + [ + new MapPoint(0, 0), + new MapPoint(0, 10), + new MapPoint(10, 10), + new MapPoint(10, 0), + new MapPoint(0, 0) + ] + ], new SpatialReference(102100)); + + Geometry offsetMiter = await GeometryEngine.Offset(polygon, 10, GeometryEngineLinearUnit.Feet, JoinType.Miter); + Assert.IsNotNull(offsetMiter); + + Geometry offsetRound = await GeometryEngine.Offset(polygon, 10, GeometryEngineLinearUnit.Feet, JoinType.Round); + Assert.IsNotNull(offsetRound); + } + + [TestMethod] + public async Task TestOffsetSingleGeometryWithBevelRatio() + { + Polygon polygon = + new Polygon([ + [ + new MapPoint(0, 0), + new MapPoint(0, 10), + new MapPoint(10, 10), + new MapPoint(10, 0), + new MapPoint(0, 0) + ] + ], new SpatialReference(102100)); + + Geometry offset = await GeometryEngine.Offset(polygon, 10, GeometryEngineLinearUnit.Feet, JoinType.Miter, 1.5); + + Assert.IsNotNull(offset); + Assert.AreNotEqual(polygon, offset); + } + + [TestMethod] + public async Task TestOffsetSingleGeometryWithAllParameters() + { + Polygon polygon = + new Polygon([ + [ + new MapPoint(0, 0), + new MapPoint(0, 10), + new MapPoint(10, 10), + new MapPoint(10, 0), + new MapPoint(0, 0) + ] + ], new SpatialReference(102100)); + + Geometry offset = + await GeometryEngine.Offset(polygon, 10, GeometryEngineLinearUnit.Feet, JoinType.Round, null, 0.5); + + Assert.IsNotNull(offset); + Assert.AreNotEqual(polygon, offset); + } + + [TestMethod] + public async Task TestOffsetMultipleGeometriesWithoutUnit() + { + Polygon polygon1 = + new Polygon([ + [ + new MapPoint(0, 0), + new MapPoint(0, 10), + new MapPoint(10, 10), + new MapPoint(10, 0), + new MapPoint(0, 0) + ] + ], new SpatialReference(102100)); + + Polygon polygon2 = + new Polygon([ + [ + new MapPoint(20, 20), + new MapPoint(20, 30), + new MapPoint(30, 30), + new MapPoint(30, 20), + new MapPoint(20, 20) + ] + ], new SpatialReference(102100)); + + Geometry[] offsets = await GeometryEngine.Offset([polygon1, polygon2], 10); + + Assert.IsNotNull(offsets); + Assert.HasCount(2, offsets); + } + + [TestMethod] + public async Task TestPlanarAreaWithoutUnit() + { + Polygon polygon = + new Polygon([ + [ + new MapPoint(0, 0), + new MapPoint(0, 10), + new MapPoint(10, 10), + new MapPoint(10, 0), + new MapPoint(0, 0) + ] + ], new SpatialReference(102100)); + + double area = await GeometryEngine.PlanarArea(polygon); + + Assert.AreNotEqual(0, area); + } + + [TestMethod] + public async Task TestGeodesicBufferSingleGeometryWithoutUnit() + { + Polygon polygon = new Polygon([ + [ + new MapPoint(0, 0), + new MapPoint(0, 10), + new MapPoint(10, 10), + new MapPoint(10, 0), + new MapPoint(0, 0) + ] + ]); + + Polygon bufferedPolygon = await GeometryEngine.GeodesicBuffer(polygon, 10); + + Assert.IsNotNull(bufferedPolygon); + Assert.AreNotEqual(polygon, bufferedPolygon); + } + + [TestMethod] + public async Task TestGeodesicBufferMultipleGeometriesWithoutUnit() + { + Polygon polygon1 = new Polygon([ + [ + new MapPoint(0, 0), + new MapPoint(0, 1), + new MapPoint(1, 1), + new MapPoint(1, 0), + new MapPoint(0, 0) + ] + ]); + + Polygon polygon2 = new Polygon([ + [ + new MapPoint(5, 5), + new MapPoint(5, 6), + new MapPoint(6, 6), + new MapPoint(6, 5), + new MapPoint(5, 5) + ] + ]); + + Polygon[] bufferedGeometries = + await GeometryEngine.GeodesicBuffer([polygon1, polygon2], [10, 15]); + + Assert.IsNotNull(bufferedGeometries); + Assert.HasCount(2, bufferedGeometries); + } + + [TestMethod] + public async Task TestGeodesicDensifyWithoutUnit() + { + Polygon polygon = + new Polygon([ + [new MapPoint(0, 0), new MapPoint(0, 10), new MapPoint(10, 10), new MapPoint(10, 0)] + ], new SpatialReference(102100)); + + Polygon? densifiedPolygon = + await GeometryEngine.GeodesicDensify(polygon, 100) as Polygon; + + Assert.IsNotNull(densifiedPolygon); + Assert.AreNotEqual(densifiedPolygon, polygon); + } + + [TestMethod] + public async Task TestUnionWithParamsArray() + { + Polygon polygon1 = + new Polygon([ + [ + new MapPoint(0, 0), + new MapPoint(0, 10), + new MapPoint(10, 10), + new MapPoint(10, 0), + new MapPoint(0, 0) + ] + ], new SpatialReference(102100)); + + Polygon polygon2 = + new Polygon([ + [ + new MapPoint(10, 0), + new MapPoint(20, 0), + new MapPoint(20, 10), + new MapPoint(10, 10), + new MapPoint(10, 0) + ] + ], new SpatialReference(102100)); + + Polygon polygon3 = + new Polygon([ + [ + new MapPoint(20, 0), + new MapPoint(30, 0), + new MapPoint(30, 10), + new MapPoint(20, 10), + new MapPoint(20, 0) + ] + ], new SpatialReference(102100)); + + Geometry union = await GeometryEngine.Union(polygon1, polygon2, polygon3); + + Assert.IsNotNull(union); + Assert.AreNotEqual(polygon1, union); + Assert.AreNotEqual(polygon2, union); + Assert.AreNotEqual(polygon3, union); + } + + [TestMethod] + public async Task TestBufferMultipleGeometriesWithoutUnit() + { + Point point1 = new Point(0, 0, spatialReference: new SpatialReference(103002)); + Point point2 = new Point(10, 10, spatialReference: new SpatialReference(103002)); + + Polygon[] buffers = + await GeometryEngine.Buffer([point1, point2], [10.0, 20.0]); + Assert.IsNotNull(buffers); + Assert.HasCount(2, buffers); + } + + [TestMethod] + public async Task TestBufferMultipleGeometriesWithUnit() + { + Point point1 = new Point(0, 0, spatialReference: new SpatialReference(103002)); + Point point2 = new Point(10, 10, spatialReference: new SpatialReference(103002)); + + Polygon[] buffers = + await GeometryEngine.Buffer([point1, point2], [10.0, 20.0], GeometryEngineLinearUnit.Meters); + Assert.IsNotNull(buffers); + Assert.HasCount(2, buffers); + } + + private readonly Random _random = new(); + [TestMethod] public async Task TestPolyline_PathAndPoint_Mutations() { - Polyline polyline = new Polyline([[[0,0],[1,1]]], new SpatialReference(102100)); + Polyline polyline = new Polyline([[[0, 0], [1, 1]]], new SpatialReference(102100)); // AddPath using Point[] overload Polyline added = await GeometryEngine.AddPath(polyline, [new Point(1, 2), new Point(3, 3)]); @@ -1571,12 +1998,15 @@ public async Task TestPolyline_PathAndPoint_Mutations() [TestMethod] public async Task TestPolygon_RingAndPoint_Mutations() { - Polygon polygon = new Polygon([new MapPath(new MapPoint(0, 0), new MapPoint(0, 5), new MapPoint(5, 5), new MapPoint(5, 0), new MapPoint(0, 0)) + Polygon polygon = new Polygon([ + new MapPath(new MapPoint(0, 0), new MapPoint(0, 5), new MapPoint(5, 5), new MapPoint(5, 0), + new MapPoint(0, 0)) ], new SpatialReference(102100)); int beforeRings = polygon.Rings.Count; // AddRing with Point[] - Polygon withRing = await GeometryEngine.AddRing(polygon, [new Point(10, 10), new Point(10, 20), new Point(20, 20), new Point(10, 10) + Polygon withRing = await GeometryEngine.AddRing(polygon, [ + new Point(10, 10), new Point(10, 20), new Point(20, 20), new Point(10, 10) ]); Assert.IsNotNull(withRing); Assert.HasCount(beforeRings + 1, withRing.Rings); @@ -1606,16 +2036,4 @@ public async Task TestPolygon_RingAndPoint_Mutations() #pragma warning restore MSTEST0032 Assert.HasCount(beforeRings, removedRing.Polygon.Rings); } - - [TestMethod] - public async Task TestIsClockwise() - { - MapPath ring = new MapPath(new MapPoint(0, 0), new MapPoint(0, 10), new MapPoint(10, 10), new MapPoint(10, 0), new MapPoint(0, 0)); - Polygon polygon = new Polygon([ring], new SpatialReference(102100)); - - bool result = await GeometryEngine.IsClockwise(polygon, ring); - Assert.IsInstanceOfType(result, typeof(bool)); - } - - private readonly Random _random = new(); } \ No newline at end of file diff --git a/test/dymaptic.GeoBlazor.Core.Test.Blazor.Shared/Components/ProjectionEngineTests.cs b/test/dymaptic.GeoBlazor.Core.Test.Blazor.Shared/Components/ProjectionEngineTests.cs new file mode 100644 index 000000000..bb16f8f24 --- /dev/null +++ b/test/dymaptic.GeoBlazor.Core.Test.Blazor.Shared/Components/ProjectionEngineTests.cs @@ -0,0 +1,483 @@ +using dymaptic.GeoBlazor.Core.Components; +using dymaptic.GeoBlazor.Core.Components.Geometries; +using dymaptic.GeoBlazor.Core.Model; +using Microsoft.AspNetCore.Components; + + +namespace dymaptic.GeoBlazor.Core.Test.Blazor.Shared.Components; + +[TestClass] +public class ProjectionEngineTests : TestRunnerBase +{ + [Inject] + public required ProjectionEngine ProjectionEngine { get; set; } + + [TestMethod] + public async Task TestProjectSinglePointFromWgs84ToWebMercator() + { + Point point = new Point(x: -122.4194, y: 37.7749, spatialReference: new SpatialReference(4326)); + SpatialReference targetSpatialReference = new SpatialReference(102100); + + Geometry? projectedGeometry = await ProjectionEngine.Project(point, targetSpatialReference); + + Assert.IsNotNull(projectedGeometry); + Assert.IsInstanceOfType(projectedGeometry); + Assert.IsNotNull(projectedGeometry.SpatialReference); + Assert.AreEqual(102100, projectedGeometry.SpatialReference.Wkid); + } + + [TestMethod] + public async Task TestProjectSinglePointWithNullTransformation() + { + Point point = new Point(x: -122.4194, y: 37.7749, spatialReference: new SpatialReference(4326)); + SpatialReference targetSpatialReference = new SpatialReference(102100); + + Geometry? projectedGeometry = await ProjectionEngine.Project(point, targetSpatialReference, null); + + Assert.IsNotNull(projectedGeometry); + Assert.IsInstanceOfType(projectedGeometry); + } + + [TestMethod] + public async Task TestProjectMultiplePoints() + { + Point point1 = new Point(x: -122.4194, y: 37.7749, spatialReference: new SpatialReference(4326)); + Point point2 = new Point(x: -118.2437, y: 34.0522, spatialReference: new SpatialReference(4326)); + SpatialReference targetSpatialReference = new SpatialReference(102100); + + Geometry[]? projectedGeometries = await ProjectionEngine.Project([point1, point2], targetSpatialReference); + + Assert.IsNotNull(projectedGeometries); + Assert.HasCount(2, projectedGeometries); + Assert.IsInstanceOfType(projectedGeometries[0]); + Assert.IsInstanceOfType(projectedGeometries[1]); + } + + [TestMethod] + public async Task TestProjectMultiplePointsWithNullTransformation() + { + Point point1 = new Point(x: -122.4194, y: 37.7749, spatialReference: new SpatialReference(4326)); + Point point2 = new Point(x: -118.2437, y: 34.0522, spatialReference: new SpatialReference(4326)); + SpatialReference targetSpatialReference = new SpatialReference(102100); + + Geometry[]? projectedGeometries = + await ProjectionEngine.Project([point1, point2], targetSpatialReference, null); + + Assert.IsNotNull(projectedGeometries); + Assert.HasCount(2, projectedGeometries); + } + + [TestMethod] + public async Task TestProjectPolygon() + { + Polygon polygon = new Polygon([ + [ + new MapPoint(-122.5, 37.5), + new MapPoint(-122.5, 38.0), + new MapPoint(-122.0, 38.0), + new MapPoint(-122.0, 37.5), + new MapPoint(-122.5, 37.5) + ] + ], new SpatialReference(4326)); + SpatialReference targetSpatialReference = new SpatialReference(102100); + + Geometry? projectedGeometry = await ProjectionEngine.Project(polygon, targetSpatialReference); + + Assert.IsNotNull(projectedGeometry); + Assert.IsInstanceOfType(projectedGeometry); + } + + [TestMethod] + public async Task TestProjectPolyline() + { + Polyline polyline = new Polyline([ + [ + new MapPoint(-122.5, 37.5), + new MapPoint(-122.0, 38.0), + new MapPoint(-121.5, 37.5) + ] + ], new SpatialReference(4326)); + SpatialReference targetSpatialReference = new SpatialReference(102100); + + Geometry? projectedGeometry = await ProjectionEngine.Project(polyline, targetSpatialReference); + + Assert.IsNotNull(projectedGeometry); + Assert.IsInstanceOfType(projectedGeometry); + } + + [TestMethod] + public async Task TestGetTransformationBetweenSameDatumReturnsNull() + { + // WGS84 and Web Mercator share the same datum, so no transformation is needed + SpatialReference inSpatialReference = new SpatialReference(4326); + SpatialReference outSpatialReference = new SpatialReference(102100); + + // Extent constructor: (xmax, xmin, ymax, ymin) + Extent extent = new Extent(-120, -130, 50, 30, spatialReference: new SpatialReference(4326)); + + GeographicTransformation? transformation = + await ProjectionEngine.GetTransformation(inSpatialReference, outSpatialReference, extent); + + Assert.IsNull(transformation); + } + + [TestMethod] + public async Task TestGetTransformationsBetweenSameDatumReturnsEmpty() + { + // WGS84 and Web Mercator share the same datum, so no transformations are needed + SpatialReference inSpatialReference = new SpatialReference(4326); + SpatialReference outSpatialReference = new SpatialReference(102100); + Extent extent = new Extent(-120, -130, 50, 30, spatialReference: new SpatialReference(4326)); + + GeographicTransformation[]? transformations = + await ProjectionEngine.GetTransformations(inSpatialReference, outSpatialReference, extent); + + Assert.IsNotNull(transformations); + Assert.HasCount(0, transformations); + } + + [TestMethod] + public async Task TestProjectPreservesGeometryType() + { + Point point = new Point(x: -100, y: 40, spatialReference: new SpatialReference(4326)); + + Polygon polygon = new Polygon([ + [ + new MapPoint(0, 0), + new MapPoint(0, 10), + new MapPoint(10, 10), + new MapPoint(10, 0), + new MapPoint(0, 0) + ] + ], new SpatialReference(4326)); + + Polyline polyline = new Polyline([ + [new MapPoint(0, 0), new MapPoint(10, 10)] + ], new SpatialReference(4326)); + + SpatialReference targetSpatialReference = new SpatialReference(102100); + + Geometry? projectedPoint = await ProjectionEngine.Project(point, targetSpatialReference); + Geometry? projectedPolygon = await ProjectionEngine.Project(polygon, targetSpatialReference); + Geometry? projectedPolyline = await ProjectionEngine.Project(polyline, targetSpatialReference); + + Assert.IsInstanceOfType(projectedPoint); + Assert.IsInstanceOfType(projectedPolygon); + Assert.IsInstanceOfType(projectedPolyline); + } + + [TestMethod] + public async Task TestProjectMixedGeometryArray() + { + Point point = new Point(x: -100, y: 40, spatialReference: new SpatialReference(4326)); + + Polygon polygon = new Polygon([ + [ + new MapPoint(0, 0), + new MapPoint(0, 10), + new MapPoint(10, 10), + new MapPoint(10, 0), + new MapPoint(0, 0) + ] + ], new SpatialReference(4326)); + SpatialReference targetSpatialReference = new SpatialReference(102100); + + Geometry[]? projectedGeometries = await ProjectionEngine.Project([point, polygon], targetSpatialReference); + + Assert.IsNotNull(projectedGeometries); + Assert.HasCount(2, projectedGeometries); + Assert.IsInstanceOfType(projectedGeometries[0]); + Assert.IsInstanceOfType(projectedGeometries[1]); + } + + [TestMethod] + public async Task TestProjectToSameSpatialReferencePreservesCoordinates() + { + Point point = new Point(x: -122.4194, y: 37.7749, spatialReference: new SpatialReference(4326)); + SpatialReference targetSpatialReference = new SpatialReference(4326); + + Geometry? projectedGeometry = await ProjectionEngine.Project(point, targetSpatialReference); + + Assert.IsNotNull(projectedGeometry); + Point projectedPoint = (Point)projectedGeometry; + Assert.AreEqual(point.X, projectedPoint.X); + Assert.AreEqual(point.Y, projectedPoint.Y); + } + + [TestMethod] + public async Task TestProjectFromWebMercatorToWgs84() + { + // Web Mercator coordinates for San Francisco area + Point point = new Point(x: -13627665.271218, y: 4548388.565049, spatialReference: new SpatialReference(102100)); + SpatialReference targetSpatialReference = new SpatialReference(4326); + + Geometry? projectedGeometry = await ProjectionEngine.Project(point, targetSpatialReference); + + Assert.IsNotNull(projectedGeometry); + Point projectedPoint = (Point)projectedGeometry; + + // Should be approximately -122.4194, 37.7749 (San Francisco coordinates) + Assert.IsTrue(projectedPoint.X < -122 && projectedPoint.X > -123); + Assert.IsTrue(projectedPoint.Y > 37 && projectedPoint.Y < 38); + } + + [TestMethod] + public async Task TestProjectExtent() + { + // Extent constructor: (xmax, xmin, ymax, ymin) + Extent extent = new Extent(-122.0, -122.5, 38.0, 37.5, spatialReference: new SpatialReference(4326)); + SpatialReference targetSpatialReference = new SpatialReference(102100); + + Geometry? projectedGeometry = await ProjectionEngine.Project(extent, targetSpatialReference); + + Assert.IsNotNull(projectedGeometry); + Assert.IsInstanceOfType(projectedGeometry); + Extent projectedExtent = (Extent)projectedGeometry; + Assert.IsNotNull(projectedExtent.SpatialReference); + Assert.AreEqual(102100, projectedExtent.SpatialReference.Wkid); + } + + [TestMethod] + public async Task TestProjectEmptyArrayReturnsEmpty() + { + SpatialReference targetSpatialReference = new SpatialReference(102100); + + Geometry[]? projectedGeometries = await ProjectionEngine.Project([], targetSpatialReference); + + Assert.IsNotNull(projectedGeometries); + Assert.HasCount(0, projectedGeometries); + } + + [TestMethod] + public async Task TestRoundTripWgs84ToWebMercatorAndBack() + { + // Original point in WGS84 + Point originalPoint = new Point(x: -122.4194, y: 37.7749, spatialReference: new SpatialReference(4326)); + SpatialReference webMercator = new SpatialReference(102100); + SpatialReference wgs84 = new SpatialReference(4326); + + // Project to Web Mercator + Geometry? projectedToWebMercator = await ProjectionEngine.Project(originalPoint, webMercator); + Assert.IsNotNull(projectedToWebMercator); + Point webMercatorPoint = (Point)projectedToWebMercator; + + // Project back to WGS84 + Geometry? projectedBackToWgs84 = await ProjectionEngine.Project(webMercatorPoint, wgs84); + Assert.IsNotNull(projectedBackToWgs84); + Point roundTripPoint = (Point)projectedBackToWgs84; + + // Verify coordinates match within tolerance + AssertPointsAreApproximatelyEqual(originalPoint, roundTripPoint); + } + + [TestMethod] + public async Task TestRoundTripPolygonWgs84ToWebMercatorAndBack() + { + // Original polygon in WGS84 + Polygon originalPolygon = new Polygon([ + [ + new MapPoint(-122.5, 37.5), + new MapPoint(-122.5, 38.0), + new MapPoint(-122.0, 38.0), + new MapPoint(-122.0, 37.5), + new MapPoint(-122.5, 37.5) + ] + ], new SpatialReference(4326)); + SpatialReference webMercator = new SpatialReference(102100); + SpatialReference wgs84 = new SpatialReference(4326); + + // Project to Web Mercator + Geometry? projectedToWebMercator = await ProjectionEngine.Project(originalPolygon, webMercator); + Assert.IsNotNull(projectedToWebMercator); + Polygon webMercatorPolygon = (Polygon)projectedToWebMercator; + + // Project back to WGS84 + Geometry? projectedBackToWgs84 = await ProjectionEngine.Project(webMercatorPolygon, wgs84); + Assert.IsNotNull(projectedBackToWgs84); + Polygon roundTripPolygon = (Polygon)projectedBackToWgs84; + + // Verify ring points match within tolerance + Assert.HasCount(originalPolygon.Rings.Count, roundTripPolygon.Rings); + + for (int i = 0; i < originalPolygon.Rings.Count; i++) + { + Assert.HasCount(originalPolygon.Rings[i].Count, roundTripPolygon.Rings[i]); + + for (int j = 0; j < originalPolygon.Rings[i].Count; j++) + { + AssertMapPointsAreApproximatelyEqual(originalPolygon.Rings[i][j], roundTripPolygon.Rings[i][j]); + } + } + } + + [TestMethod] + public async Task TestProjectFromWgs84ToUtmZone10N() + { + // San Francisco is in UTM Zone 10N (EPSG:32610) + Point point = new Point(x: -122.4194, y: 37.7749, spatialReference: new SpatialReference(4326)); + SpatialReference utmZone10N = new SpatialReference(32610); + + Geometry? projectedGeometry = await ProjectionEngine.Project(point, utmZone10N); + + Assert.IsNotNull(projectedGeometry); + Point projectedPoint = (Point)projectedGeometry; + Assert.IsNotNull(projectedPoint.SpatialReference); + Assert.AreEqual(32610, projectedPoint.SpatialReference.Wkid); + + // UTM coordinates should be in meters, typically 6-digit easting and 7-digit northing + Assert.IsTrue(projectedPoint.X > 500000 && projectedPoint.X < 600000); // Easting + Assert.IsTrue(projectedPoint.Y > 4000000 && projectedPoint.Y < 5000000); // Northing + } + + [TestMethod] + public async Task TestRoundTripWgs84ToUtmZone10NAndBack() + { + Point originalPoint = new Point(x: -122.4194, y: 37.7749, spatialReference: new SpatialReference(4326)); + SpatialReference utmZone10N = new SpatialReference(32610); + SpatialReference wgs84 = new SpatialReference(4326); + + // Project to UTM + Geometry? projectedToUtm = await ProjectionEngine.Project(originalPoint, utmZone10N); + Assert.IsNotNull(projectedToUtm); + Point utmPoint = (Point)projectedToUtm; + + // Project back to WGS84 + Geometry? projectedBackToWgs84 = await ProjectionEngine.Project(utmPoint, wgs84); + Assert.IsNotNull(projectedBackToWgs84); + Point roundTripPoint = (Point)projectedBackToWgs84; + + AssertPointsAreApproximatelyEqual(originalPoint, roundTripPoint); + } + + [TestMethod] + public async Task TestProjectFromWebMercatorToUtmZone10N() + { + // Web Mercator coordinates for San Francisco + Point point = new Point(x: -13627665.271218, y: 4548388.565049, spatialReference: new SpatialReference(102100)); + SpatialReference utmZone10N = new SpatialReference(32610); + + Geometry? projectedGeometry = await ProjectionEngine.Project(point, utmZone10N); + + Assert.IsNotNull(projectedGeometry); + Point projectedPoint = (Point)projectedGeometry; + Assert.IsNotNull(projectedPoint.SpatialReference); + Assert.AreEqual(32610, projectedPoint.SpatialReference.Wkid); + } + + [TestMethod] + public async Task TestRoundTripWebMercatorToUtmAndBack() + { + Point originalPoint = new Point(x: -13627665.271218, y: 4548388.565049, + spatialReference: new SpatialReference(102100)); + SpatialReference utmZone10N = new SpatialReference(32610); + SpatialReference webMercator = new SpatialReference(102100); + + // Project to UTM + Geometry? projectedToUtm = await ProjectionEngine.Project(originalPoint, utmZone10N); + Assert.IsNotNull(projectedToUtm); + Point utmPoint = (Point)projectedToUtm; + + // Project back to Web Mercator + Geometry? projectedBackToWebMercator = await ProjectionEngine.Project(utmPoint, webMercator); + Assert.IsNotNull(projectedBackToWebMercator); + Point roundTripPoint = (Point)projectedBackToWebMercator; + + // Web Mercator uses meters, so tolerance should be appropriate + AssertPointsAreApproximatelyEqual(originalPoint, roundTripPoint, tolerance: 0.01); + } + + [TestMethod] + public async Task TestProjectToStatePlaneCaliforniaZone3() + { + // California State Plane Zone 3 (EPSG:2227) - uses US Survey Feet + Point point = new Point(x: -122.4194, y: 37.7749, spatialReference: new SpatialReference(4326)); + SpatialReference statePlane = new SpatialReference(2227); + + Geometry? projectedGeometry = await ProjectionEngine.Project(point, statePlane); + + Assert.IsNotNull(projectedGeometry); + Point projectedPoint = (Point)projectedGeometry; + Assert.IsNotNull(projectedPoint.SpatialReference); + Assert.AreEqual(2227, projectedPoint.SpatialReference.Wkid); + } + + [TestMethod] + public async Task TestRoundTripWgs84ToStatePlaneAndBack() + { + Point originalPoint = new Point(x: -122.4194, y: 37.7749, spatialReference: new SpatialReference(4326)); + SpatialReference statePlane = new SpatialReference(2227); + SpatialReference wgs84 = new SpatialReference(4326); + + // Project to State Plane + Geometry? projectedToStatePlane = await ProjectionEngine.Project(originalPoint, statePlane); + Assert.IsNotNull(projectedToStatePlane); + Point statePlanePoint = (Point)projectedToStatePlane; + + // Project back to WGS84 + Geometry? projectedBackToWgs84 = await ProjectionEngine.Project(statePlanePoint, wgs84); + Assert.IsNotNull(projectedBackToWgs84); + Point roundTripPoint = (Point)projectedBackToWgs84; + + AssertPointsAreApproximatelyEqual(originalPoint, roundTripPoint); + } + + [TestMethod] + public async Task TestProjectMultiplePointsRoundTrip() + { + Point[] originalPoints = + [ + new Point(x: -122.4194, y: 37.7749, spatialReference: new SpatialReference(4326)), // San Francisco + new Point(x: -118.2437, y: 34.0522, spatialReference: new SpatialReference(4326)), // Los Angeles + new Point(x: -73.9857, y: 40.7484, spatialReference: new SpatialReference(4326)) // New York + ]; + SpatialReference webMercator = new SpatialReference(102100); + SpatialReference wgs84 = new SpatialReference(4326); + + // Project to Web Mercator + Geometry[]? projectedToWebMercator = await ProjectionEngine.Project(originalPoints, webMercator); + Assert.IsNotNull(projectedToWebMercator); + Assert.HasCount(3, projectedToWebMercator); + + // Project back to WGS84 + Geometry[]? projectedBackToWgs84 = await ProjectionEngine.Project(projectedToWebMercator, wgs84); + Assert.IsNotNull(projectedBackToWgs84); + Assert.HasCount(3, projectedBackToWgs84); + + // Verify each point matches + for (int i = 0; i < originalPoints.Length; i++) + { + AssertPointsAreApproximatelyEqual(originalPoints[i], (Point)projectedBackToWgs84[i]); + } + } + + private static void AssertPointsAreApproximatelyEqual(Point expected, Point actual, double tolerance = 0.0001) + { + Assert.IsNotNull(expected.X); + Assert.IsNotNull(expected.Y); + Assert.IsNotNull(actual.X); + Assert.IsNotNull(actual.Y); + + double xDiff = Math.Abs(expected.X.Value - actual.X.Value); + double yDiff = Math.Abs(expected.Y.Value - actual.Y.Value); + + Assert.IsLessThan(tolerance, xDiff, + $"X coordinates differ: expected {expected.X}, actual {actual.X}, difference {xDiff}"); + + Assert.IsLessThan(tolerance, yDiff, + $"Y coordinates differ: expected {expected.Y}, actual {actual.Y}, difference {yDiff}"); + } + + private static void AssertMapPointsAreApproximatelyEqual(MapPoint expected, MapPoint actual, + double tolerance = 0.0001) + { + // MapPoint is a List where [0] = X and [1] = Y + double xDiff = Math.Abs(expected[0] - actual[0]); + double yDiff = Math.Abs(expected[1] - actual[1]); + + Assert.IsLessThan(tolerance, xDiff, + $"X coordinates differ: expected {expected[0]}, actual {actual[0]}, difference {xDiff}"); + + Assert.IsLessThan(tolerance, yDiff, + $"Y coordinates differ: expected {expected[1]}, actual {actual[1]}, difference {yDiff}"); + } +} \ No newline at end of file diff --git a/test/dymaptic.GeoBlazor.Core.Test.Blazor.Shared/dymaptic.GeoBlazor.Core.Test.Blazor.Shared.csproj b/test/dymaptic.GeoBlazor.Core.Test.Blazor.Shared/dymaptic.GeoBlazor.Core.Test.Blazor.Shared.csproj index ff37a42e2..b9a84e2e8 100644 --- a/test/dymaptic.GeoBlazor.Core.Test.Blazor.Shared/dymaptic.GeoBlazor.Core.Test.Blazor.Shared.csproj +++ b/test/dymaptic.GeoBlazor.Core.Test.Blazor.Shared/dymaptic.GeoBlazor.Core.Test.Blazor.Shared.csproj @@ -10,11 +10,11 @@ - - - - - + + + + + diff --git a/test/dymaptic.GeoBlazor.Core.Test.Unit/dymaptic.GeoBlazor.Core.Test.Unit.csproj b/test/dymaptic.GeoBlazor.Core.Test.Unit/dymaptic.GeoBlazor.Core.Test.Unit.csproj index f92e0bb5b..9f218338b 100644 --- a/test/dymaptic.GeoBlazor.Core.Test.Unit/dymaptic.GeoBlazor.Core.Test.Unit.csproj +++ b/test/dymaptic.GeoBlazor.Core.Test.Unit/dymaptic.GeoBlazor.Core.Test.Unit.csproj @@ -21,6 +21,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/test/dymaptic.GeoBlazor.Core.Test.WebApp/dymaptic.GeoBlazor.Core.Test.WebApp.Client/dymaptic.GeoBlazor.Core.Test.WebApp.Client.csproj b/test/dymaptic.GeoBlazor.Core.Test.WebApp/dymaptic.GeoBlazor.Core.Test.WebApp.Client/dymaptic.GeoBlazor.Core.Test.WebApp.Client.csproj index fa2139536..1b1f6b45b 100644 --- a/test/dymaptic.GeoBlazor.Core.Test.WebApp/dymaptic.GeoBlazor.Core.Test.WebApp.Client/dymaptic.GeoBlazor.Core.Test.WebApp.Client.csproj +++ b/test/dymaptic.GeoBlazor.Core.Test.WebApp/dymaptic.GeoBlazor.Core.Test.WebApp.Client/dymaptic.GeoBlazor.Core.Test.WebApp.Client.csproj @@ -7,8 +7,8 @@ - - + + diff --git a/test/dymaptic.GeoBlazor.Core.Test.WebApp/dymaptic.GeoBlazor.Core.Test.WebApp/dymaptic.GeoBlazor.Core.Test.WebApp.csproj b/test/dymaptic.GeoBlazor.Core.Test.WebApp/dymaptic.GeoBlazor.Core.Test.WebApp/dymaptic.GeoBlazor.Core.Test.WebApp.csproj index 311143d3e..6f6f88a91 100644 --- a/test/dymaptic.GeoBlazor.Core.Test.WebApp/dymaptic.GeoBlazor.Core.Test.WebApp/dymaptic.GeoBlazor.Core.Test.WebApp.csproj +++ b/test/dymaptic.GeoBlazor.Core.Test.WebApp/dymaptic.GeoBlazor.Core.Test.WebApp/dymaptic.GeoBlazor.Core.Test.WebApp.csproj @@ -11,7 +11,7 @@ - + From 2a63b849e35b96bd55e941290532e4f52723af21 Mon Sep 17 00:00:00 2001 From: Tim Purdum Date: Sat, 24 Jan 2026 14:31:34 -0600 Subject: [PATCH 08/89] adding test coverage --- .gitignore | 2 + badge_fullmethodcoverage.svg | 2 +- badge_linecoverage.svg | 2 +- badge_methodcoverage.svg | 2 +- build-scripts/ConsoleDialog.cs | 93 +++++- build-scripts/ESBuild.cs | 116 +++++++- build-scripts/ESBuildClearLocks.cs | 17 +- build-scripts/ESBuildWaitForCompletion.cs | 19 +- build-scripts/GeoBlazorBuild.cs | 75 ++++- build-scripts/ScriptBuilder.cs | 42 ++- build-tools/BuildAppSettings | Bin 78256 -> 0 bytes build-tools/BuildAppSettings.deps.json | 23 -- build-tools/BuildAppSettings.dll | Bin 10240 -> 10240 bytes build-tools/BuildAppSettings.exe | Bin 162304 -> 162304 bytes .../BuildAppSettings.runtimeconfig.json | 36 --- build-tools/BuildTemplates | Bin 78256 -> 0 bytes build-tools/BuildTemplates.deps.json | 23 -- build-tools/BuildTemplates.dll | Bin 24576 -> 25088 bytes build-tools/BuildTemplates.exe | Bin 162304 -> 162304 bytes build-tools/BuildTemplates.runtimeconfig.json | 15 - build-tools/ConsoleDialog | Bin 78256 -> 0 bytes build-tools/ConsoleDialog.deps.json | 23 -- build-tools/ConsoleDialog.dll | Bin 11264 -> 11264 bytes build-tools/ConsoleDialog.exe | Bin 162304 -> 162304 bytes build-tools/ConsoleDialog.runtimeconfig.json | 36 --- build-tools/ESBuild | Bin 78256 -> 0 bytes build-tools/ESBuild.deps.json | 23 -- build-tools/ESBuild.dll | Bin 18944 -> 19456 bytes build-tools/ESBuild.exe | Bin 162304 -> 162304 bytes build-tools/ESBuild.runtimeconfig.json | 36 --- build-tools/ESBuildClearLocks | Bin 78256 -> 0 bytes build-tools/ESBuildClearLocks.deps.json | 23 -- build-tools/ESBuildClearLocks.dll | Bin 8192 -> 8192 bytes build-tools/ESBuildClearLocks.exe | Bin 162304 -> 162304 bytes .../ESBuildClearLocks.runtimeconfig.json | 36 --- build-tools/ESBuildWaitForCompletion | Bin 78256 -> 0 bytes .../ESBuildWaitForCompletion.deps.json | 23 -- build-tools/ESBuildWaitForCompletion.dll | Bin 9728 -> 9728 bytes build-tools/ESBuildWaitForCompletion.exe | Bin 162816 -> 162816 bytes ...SBuildWaitForCompletion.runtimeconfig.json | 36 --- build-tools/FetchNuGetVersion | Bin 78256 -> 0 bytes build-tools/FetchNuGetVersion.deps.json | 23 -- build-tools/FetchNuGetVersion.dll | Bin 9216 -> 9216 bytes build-tools/FetchNuGetVersion.exe | Bin 162304 -> 162304 bytes .../FetchNuGetVersion.runtimeconfig.json | 36 --- build-tools/GeoBlazorBuild | Bin 78256 -> 0 bytes build-tools/GeoBlazorBuild.deps.json | 23 -- build-tools/GeoBlazorBuild.dll | Bin 40448 -> 40448 bytes build-tools/GeoBlazorBuild.exe | Bin 162304 -> 162304 bytes build-tools/GeoBlazorBuild.runtimeconfig.json | 36 --- .../dymaptic.GeoBlazor.Core.Analyzers.csproj | 2 +- ...oBlazor.Core.SourceGenerator.Shared.csproj | 2 +- .../ESBuildGenerator.cs | 37 +-- .../ProtobufSourceGenerator.cs | 10 +- ...ptic.GeoBlazor.Core.SourceGenerator.csproj | 2 +- .../JsModuleManager.cs | 47 ++- .../Model/LogicComponent.cs | 2 +- .../badge_fullmethodcoverage.svg | 4 +- .../badge_linecoverage.svg | 4 +- .../badge_methodcoverage.svg | 4 +- .../dymaptic.GeoBlazor.Core.csproj | 2 +- .../GenerateTests.cs | 36 ++- .../GeoBlazorTestClass.cs | 21 +- .../GlobalUsings.cs | 35 +++ .../TestConfig.cs | 276 +++++++++++++++--- .../Utilities.cs | 117 ++++++++ ...ptic.GeoBlazor.Core.Test.Automation.csproj | 1 + .../Components/ExtentGeometryTests.cs | 6 +- .../Components/GeometryEngineTests.cs | 160 +++++++--- .../Components/LocationServiceTests.cs | 82 ++++++ .../Components/ProjectionEngineTests.cs | 1 + .../dymaptic.GeoBlazor.Core.Test.Unit.csproj | 6 +- 72 files changed, 1037 insertions(+), 641 deletions(-) delete mode 100755 build-tools/BuildAppSettings delete mode 100644 build-tools/BuildAppSettings.deps.json delete mode 100644 build-tools/BuildAppSettings.runtimeconfig.json delete mode 100755 build-tools/BuildTemplates delete mode 100644 build-tools/BuildTemplates.deps.json delete mode 100644 build-tools/BuildTemplates.runtimeconfig.json delete mode 100755 build-tools/ConsoleDialog delete mode 100644 build-tools/ConsoleDialog.deps.json delete mode 100644 build-tools/ConsoleDialog.runtimeconfig.json delete mode 100755 build-tools/ESBuild delete mode 100644 build-tools/ESBuild.deps.json delete mode 100644 build-tools/ESBuild.runtimeconfig.json delete mode 100755 build-tools/ESBuildClearLocks delete mode 100644 build-tools/ESBuildClearLocks.deps.json delete mode 100644 build-tools/ESBuildClearLocks.runtimeconfig.json delete mode 100755 build-tools/ESBuildWaitForCompletion delete mode 100644 build-tools/ESBuildWaitForCompletion.deps.json delete mode 100644 build-tools/ESBuildWaitForCompletion.runtimeconfig.json delete mode 100755 build-tools/FetchNuGetVersion delete mode 100644 build-tools/FetchNuGetVersion.deps.json delete mode 100644 build-tools/FetchNuGetVersion.runtimeconfig.json delete mode 100755 build-tools/GeoBlazorBuild delete mode 100644 build-tools/GeoBlazorBuild.deps.json delete mode 100644 build-tools/GeoBlazorBuild.runtimeconfig.json create mode 100644 test/dymaptic.GeoBlazor.Core.Test.Automation/GlobalUsings.cs create mode 100644 test/dymaptic.GeoBlazor.Core.Test.Automation/Utilities.cs diff --git a/.gitignore b/.gitignore index 1fd10f24f..cf1071401 100644 --- a/.gitignore +++ b/.gitignore @@ -19,9 +19,11 @@ CustomerTests.razor test/dymaptic.GeoBlazor.Core.Test.Automation/test.txt test/dymaptic.GeoBlazor.Core.Test.Automation/test-run.log test/dymaptic.GeoBlazor.Core.Test.Automation/coverage* +test/dymaptic.GeoBlazor.Core.Test.Automation/history* test/dymaptic.GeoBlazor.Core.Test.Automation/unit-coverage* test/dymaptic.GeoBlazor.Core.Test.Automation/sgen-coverage* test/dymaptic.GeoBlazor.Core.Test.Automation/Summary.txt +build-tools/*.json # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs diff --git a/badge_fullmethodcoverage.svg b/badge_fullmethodcoverage.svg index f80e7c08e..b677d1c71 100644 --- a/badge_fullmethodcoverage.svg +++ b/badge_fullmethodcoverage.svg @@ -132,7 +132,7 @@ - 3%3% + 23.3%23.3% diff --git a/badge_linecoverage.svg b/badge_linecoverage.svg index 24ecd11fc..342686d19 100644 --- a/badge_linecoverage.svg +++ b/badge_linecoverage.svg @@ -123,7 +123,7 @@ Coverage Coverage - 1.3%1.3% + 8.5%8.5% diff --git a/badge_methodcoverage.svg b/badge_methodcoverage.svg index f06ecb44c..d7128d523 100644 --- a/badge_methodcoverage.svg +++ b/badge_methodcoverage.svg @@ -125,7 +125,7 @@ Coverage - 3.2%3.2% + 26.3%26.3% diff --git a/build-scripts/ConsoleDialog.cs b/build-scripts/ConsoleDialog.cs index 1fca2a591..2282349e4 100644 --- a/build-scripts/ConsoleDialog.cs +++ b/build-scripts/ConsoleDialog.cs @@ -1,8 +1,34 @@ #!/usr/bin/env dotnet -using System.Diagnostics; +// Console Dialog - Build Progress Display Window +// =============================================== +// Manages a console window for displaying log messages during source generation +// and build processes. Opens a separate terminal window that tails a log file, +// allowing real-time visibility of build progress. +// +// Usage: +// dotnet ConsoleDialog.cs [title] [options] +// dotnet ConsoleDialog.cs "GeoBlazor Build" Start with custom title +// dotnet ConsoleDialog.cs "Build" -w 5 -t 120 Custom wait/timeout +// +// Options: +// -w, --wait Seconds to wait before closing on exit (default: 3) +// -t, --timeout Idle timeout before auto-close (default: 60) +// +// Communication: +// The dialog reads from stdin. Send lines of text to display in the console window. +// Special commands: +// "hold" - Prevent auto-timeout (keeps window open indefinitely) +// "exit" - Close the console window +// +// Cross-Platform Support: +// - Windows: Opens PowerShell 7 (pwsh) window with Get-Content -Wait +// - macOS: Opens Terminal.app via osascript +// - Linux: Tries gnome-terminal, konsole, xfce4-terminal, or xterm +// +// Note: Messages are written to a temp file and tailed by the console window. -// Manages a console window for displaying log messages during source generation. +using System.Diagnostics; object _consoleLock = new(); Process? _consoleProcess = null; @@ -10,6 +36,7 @@ string? title = null; int wait = 3; +int idleTimeout = 60; for (int i = 0; i < args.Length; i++) { @@ -22,6 +49,11 @@ wait = int.TryParse(args[i + 1], out int parsedWait) ? parsedWait : wait; i++; break; + case "-t": + case "--timeout": + idleTimeout = int.TryParse(args[i + 1], out int parsedTimeout) ? parsedTimeout : idleTimeout; + i++; + break; default: if (title is null) { @@ -37,6 +69,12 @@ title ??= "GeoBlazor Build"; +/// +/// Shows or updates the console window with a new message. +/// Creates the temp log file and starts the console window on first call. +/// +/// The title for the console window. +/// The message to display (empty string to just ensure window is open). void ShowOrUpdateConsole(string title, string message) { lock (_consoleLock) @@ -65,21 +103,27 @@ void ShowOrUpdateConsole(string title, string message) } } +/// +/// Starts a platform-specific console window that tails the log file. +/// Dispatches to Windows, macOS, or Linux-specific implementations. +/// +/// The title for the console window. void StartConsoleWindow(string title) { + string windowTitle = string.IsNullOrWhiteSpace(title) ? "GeoBlazor Build" : title; try { if (OperatingSystem.IsWindows()) { - StartWindowsConsole(title); + StartWindowsConsole(windowTitle); } else if (OperatingSystem.IsMacOS()) { - StartMacConsole(); + StartMacConsole(windowTitle); } else if (OperatingSystem.IsLinux()) { - StartLinuxConsole(); + StartLinuxConsole(windowTitle); } } catch @@ -89,12 +133,15 @@ void StartConsoleWindow(string title) } } +/// +/// Starts a PowerShell 7 console window on Windows using Get-Content -Wait to tail the log file. +/// +/// The title for the console window. void StartWindowsConsole(string title) { string escapedPath = _consoleTempFile!.Replace("'", "''"); - string windowTitle = string.IsNullOrWhiteSpace(title) ? "GeoBlazor Build" : title; - string command = $"$Host.UI.RawUI.WindowTitle = '{windowTitle}'; " + - $"Write-Host 'GeoBlazor Source Generator Output' -ForegroundColor Cyan; " + + string command = $"$Host.UI.RawUI.WindowTitle = '{title}'; " + + $"Write-Host '{title}' -ForegroundColor Cyan; " + $"Write-Host ('=' * 50); " + $"Get-Content -Path '{escapedPath}' -Wait -Tail 100"; @@ -111,13 +158,16 @@ void StartWindowsConsole(string title) _consoleProcess.Start(); } -void StartMacConsole() +/// +/// Starts a Terminal.app window on macOS using osascript/AppleScript. +/// +void StartMacConsole(string title) { string escapedPath = _consoleTempFile!.Replace("'", "'\\''"); // Use osascript to open Terminal.app with a pwsh command string pwshCommand = "pwsh -NoProfile -NoLogo -Command \\\"" + - "Write-Host 'GeoBlazor Source Generator Output' -ForegroundColor Cyan; " + + $"Write-Host '{title}' -ForegroundColor Cyan; " + "Write-Host ('=' * 50); " + $"Get-Content -Path '{escapedPath}' -Wait -Tail 100\\\""; @@ -136,11 +186,15 @@ void StartMacConsole() _consoleProcess.Start(); } -void StartLinuxConsole() +/// +/// Starts a terminal window on Linux by trying common terminal emulators +/// (gnome-terminal, konsole, xfce4-terminal, xterm) until one succeeds. +/// +void StartLinuxConsole(string title) { string escapedPath = _consoleTempFile!.Replace("'", "'\\''"); string pwshCommand = "pwsh -NoProfile -NoLogo -Command \\\"" + - "Write-Host 'GeoBlazor Source Generator Output' -ForegroundColor Cyan; " + + $"Write-Host '{title}' -ForegroundColor Cyan; " + "Write-Host ('=' * 50); " + $"Get-Content -Path '{escapedPath}' -Wait -Tail 100\\\""; @@ -182,6 +236,12 @@ void StartLinuxConsole() // No terminal emulator found - messages still go to temp file and diagnostics } +/// +/// Closes the console window gracefully, waiting for final messages to display +/// before killing the process and cleaning up the temp file. +/// +/// The title (used in closing message). +/// Seconds to wait before killing the process. void CloseConsole(string title, int wait) { lock (_consoleLock) @@ -231,6 +291,7 @@ void CloseConsole(string title, int wait) cts.CancelAfter(TimeSpan.FromSeconds(60)); bool hold = false; +bool messageReceived = false; _ = Task.Run(async () => { @@ -238,6 +299,13 @@ void CloseConsole(string title, int wait) && (_consoleProcess is null || !_consoleProcess.HasExited)) { await Task.Delay(1000); + + if (messageReceived) + { + cts = new CancellationTokenSource(); + cts.CancelAfter(TimeSpan.FromSeconds(idleTimeout)); + messageReceived = false; + } } Console.WriteLine("Console dialog timed out. Closing..."); CloseConsole(title, wait); @@ -270,6 +338,7 @@ void CloseConsole(string title, int wait) break; } + messageReceived = true; ShowOrUpdateConsole(title, inputLine); } diff --git a/build-scripts/ESBuild.cs b/build-scripts/ESBuild.cs index 447e458b8..17132d36c 100644 --- a/build-scripts/ESBuild.cs +++ b/build-scripts/ESBuild.cs @@ -14,6 +14,7 @@ using System.Reflection; using System.Runtime.CompilerServices; using System.Text.Json; +using System.Text.RegularExpressions; // Get the actual script location using CallerFilePath (resolved at compile time) string scriptDir = GetScriptsDirectory(); @@ -266,8 +267,15 @@ } } -// Helper methods +// ============================================================================ +// Helper Methods +// ============================================================================ +/// +/// Gets the current Git branch name for a repository. +/// +/// The directory within the Git repository. +/// The branch name, or "unknown" if Git is unavailable or fails. static string GetCurrentGitBranch(string workingDirectory) { try @@ -297,6 +305,12 @@ static string GetCurrentGitBranch(string workingDirectory) } } +/// +/// Reads the last build record from the JSON file. +/// The record contains the timestamp of the last successful build and the branch name. +/// +/// Path to the .esbuild-record.json file. +/// A tuple containing the Unix timestamp (milliseconds) and branch name. static (long Timestamp, string Branch) GetLastBuildRecord(string recordFilePath) { if (!File.Exists(recordFilePath)) @@ -321,6 +335,16 @@ static string GetCurrentGitBranch(string workingDirectory) } } +/// +/// Determines whether a TypeScript build is needed. +/// A build is needed if: the branch changed, scripts were modified since last build, +/// or the output directory is empty. +/// +/// Path to the build record file. +/// The current Git branch name. +/// Path to the TypeScript source files. +/// Path to the JavaScript output directory. +/// True if a build should be performed. static bool CheckIfNeedsBuild(string recordFilePath, string currentBranch, string scriptsDir, string outputDir) { // Check if build is needed @@ -354,6 +378,14 @@ static bool CheckIfNeedsBuild(string recordFilePath, string currentBranch, strin return true; } +/// +/// Copies TypeScript files from Core's Scripts directory to Pro's Scripts directory. +/// Files listed in Pro's esBuild.js with a "pro_" prefix are renamed accordingly. +/// Only copies files that are newer than the destination. +/// +/// Path to Core's Scripts directory. +/// Path to Pro's Scripts directory. +/// If true, logs each file operation. static void CopyScriptsToPro(string coreScriptsDir, string proScriptsDir, bool verbose) { Trace.WriteLine("Copying core Scripts to Pro Scripts directory..."); @@ -362,6 +394,30 @@ static void CopyScriptsToPro(string coreScriptsDir, string proScriptsDir, bool v Directory.CreateDirectory(proScriptsDir); } + Regex proPrefixedFileRegex = new(@"^\s*'\.\/Scripts\/pro_(?\w+)\.ts',\s*$", + RegexOptions.Compiled); + + string proEsBuildJsFilePath = Path.GetFullPath( + Path.Combine(proScriptsDir, "..", "esBuild.js")); + + List proPrefixedFiles = []; + + foreach (string line in File.ReadAllLines(proEsBuildJsFilePath)) + { + if (proPrefixedFileRegex.Match(line) is Match match) + { + string fileName = match.Groups["fileName"].Value; + proPrefixedFiles.Add($"{fileName}.ts"); + Console.WriteLine($"Found pro_ file: {fileName}.ts"); + continue; + } + if (line.TrimStart().StartsWith("chunkNames")) + { + // we are past all the pro_ files + break; + } + } + int copiedCount = 0; int skippedCount = 0; List fileNames = []; @@ -370,7 +426,12 @@ static void CopyScriptsToPro(string coreScriptsDir, string proScriptsDir, bool v { string fileName = Path.GetFileName(filePath); fileNames.Add(fileName); - string destinationPath = Path.Combine(proScriptsDir, fileName); + string destinationFileName = fileName; + if (proPrefixedFiles.Contains(fileName)) + { + destinationFileName = $"pro_{fileName}"; + } + string destinationPath = Path.Combine(proScriptsDir, destinationFileName); if (!File.Exists(destinationPath)) { @@ -404,6 +465,12 @@ static void CopyScriptsToPro(string coreScriptsDir, string proScriptsDir, bool v Trace.WriteLine($"Copied {copiedCount} files, skipped {skippedCount} files."); } +/// +/// Saves the build record to a JSON file after a successful build. +/// Records the current timestamp and branch name for incremental build detection. +/// +/// Path where the record should be saved. +/// The current Git branch name. static void SaveBuildRecord(string recordFilePath, string branch) { long timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); @@ -417,6 +484,13 @@ static void SaveBuildRecord(string recordFilePath, string branch) File.WriteAllText(recordFilePath, json); } +/// +/// Checks if any TypeScript files in the Scripts directory have been modified +/// since the given timestamp. +/// +/// Path to the Scripts directory to scan. +/// Unix timestamp (milliseconds) of the last build. +/// True if any files have been modified since the timestamp. static bool GetScriptsModifiedSince(string scriptsDir, long lastTimestamp) { if (!Directory.Exists(scriptsDir)) @@ -437,6 +511,12 @@ static bool GetScriptsModifiedSince(string scriptsDir, long lastTimestamp) return false; } +/// +/// Starts the ConsoleDialog process to display build progress in a separate window. +/// +/// The build-tools directory containing ConsoleDialog.dll. +/// The title for the console window. +/// The started Process, or null if it failed to start. static Process? StartConsoleDialog(string buildDir, string title) { try @@ -488,6 +568,10 @@ static bool GetScriptsModifiedSince(string scriptsDir, long lastTimestamp) } } +/// +/// Gracefully closes the ConsoleDialog process by sending an "exit" command. +/// +/// The ConsoleDialog process to close. static void KillDialog(Process? dialog) { if (dialog is null || dialog.HasExited) @@ -512,6 +596,14 @@ static void KillDialog(Process? dialog) } } +/// +/// Runs an npm command using PowerShell 7 for cross-platform compatibility. +/// Output is captured and also written to the Trace listeners. +/// +/// The directory to run the command in. +/// The npm command (e.g., "install", "run build"). +/// Optional ConsoleDialog process for output display. +/// A tuple containing the output lines and exit code. static (List Output, int ExitCode) RunNpmCommand(string workingDirectory, string command, Process? dialogProcess) { var output = new List(); @@ -570,6 +662,11 @@ static void KillDialog(Process? dialog) } } +/// +/// Checks if the output contains any error or warning messages. +/// +/// The list of output lines to check. +/// True if any line contains "Error" or "Warning" (case-insensitive). static bool HasErrorOrWarning(List output) { return output.Any(line => @@ -577,6 +674,13 @@ static bool HasErrorOrWarning(List output) line.Contains("Warning", StringComparison.OrdinalIgnoreCase)); } +/// +/// Gets the directory containing the build scripts. +/// When running as a .cs file, uses [CallerFilePath]. When running as a compiled DLL, +/// calculates the path relative to the DLL location. +/// +/// Automatically populated with the source file path at compile time. +/// The absolute path to the build-scripts directory. static string GetScriptsDirectory([CallerFilePath] string? callerFilePath = null) { // When running as a pre-compiled DLL, [CallerFilePath] contains the compile-time path @@ -593,6 +697,10 @@ static string GetScriptsDirectory([CallerFilePath] string? callerFilePath = null return Path.Combine(Path.GetDirectoryName(dllDirectory)!, "build-scripts"); } +/// +/// Sends a failure message to the dialog and keeps it open for user review. +/// +/// The ConsoleDialog process. static void HoldDialog(Process? dialog) { if (dialog?.StandardInput is not null && !dialog.HasExited) @@ -601,6 +709,10 @@ static void HoldDialog(Process? dialog) } } +/// +/// A TraceListener that forwards trace output to a ConsoleDialog process via stdin. +/// This allows build output to be displayed in the popup console window. +/// public class DialogTraceListener(Process dialog) : TraceListener { public override void Write(string? message) diff --git a/build-scripts/ESBuildClearLocks.cs b/build-scripts/ESBuildClearLocks.cs index 948db0b4b..2b626dd5b 100644 --- a/build-scripts/ESBuildClearLocks.cs +++ b/build-scripts/ESBuildClearLocks.cs @@ -24,7 +24,7 @@ return 0; } -string scriptDir = GetScriptDirectory(); +string scriptDir = GetScriptsDirectory(); // Define lock file paths relative to script location (build-scripts folder) string[] lockFiles = @@ -66,7 +66,18 @@ return 0; // Helper method -static string GetScriptDirectory([CallerFilePath] string? callerFilePath = null) +static string GetScriptsDirectory([CallerFilePath] string? callerFilePath = null) { - return Path.GetDirectoryName(callerFilePath!) ?? Environment.CurrentDirectory; + // When running as a pre-compiled DLL, [CallerFilePath] contains the compile-time path + // which is invalid at runtime (especially in Docker containers). + // Detect this by checking if the file exists at the caller path. + if (!string.IsNullOrEmpty(callerFilePath) && File.Exists(callerFilePath)) + { + return Path.GetDirectoryName(callerFilePath)!; + } + + // Running as a DLL - use AppContext.BaseDirectory which points to the DLL location + // The DLL is in build-tools/, and scripts are in build-scripts/ (sibling directory) + string dllDirectory = AppContext.BaseDirectory.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); + return Path.Combine(Path.GetDirectoryName(dllDirectory)!, "build-scripts"); } diff --git a/build-scripts/ESBuildWaitForCompletion.cs b/build-scripts/ESBuildWaitForCompletion.cs index a76c72a29..a851ff95a 100644 --- a/build-scripts/ESBuildWaitForCompletion.cs +++ b/build-scripts/ESBuildWaitForCompletion.cs @@ -69,7 +69,7 @@ // Normalize configuration configuration = configuration.Equals("release", StringComparison.OrdinalIgnoreCase) ? "Release" : "Debug"; -string scriptDir = GetScriptDirectory(); +string scriptDir = GetScriptsDirectory(); // Define paths relative to script location (build-scripts folder) // Core: ../src/dymaptic.GeoBlazor.Core @@ -143,7 +143,18 @@ return 0; // Helper method -static string GetScriptDirectory([CallerFilePath] string? callerFilePath = null) +static string GetScriptsDirectory([CallerFilePath] string? callerFilePath = null) { - return Path.GetDirectoryName(callerFilePath!) ?? Environment.CurrentDirectory; -} + // When running as a pre-compiled DLL, [CallerFilePath] contains the compile-time path + // which is invalid at runtime (especially in Docker containers). + // Detect this by checking if the file exists at the caller path. + if (!string.IsNullOrEmpty(callerFilePath) && File.Exists(callerFilePath)) + { + return Path.GetDirectoryName(callerFilePath)!; + } + + // Running as a DLL - use AppContext.BaseDirectory which points to the DLL location + // The DLL is in build-tools/, and scripts are in build-scripts/ (sibling directory) + string dllDirectory = AppContext.BaseDirectory.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); + return Path.Combine(Path.GetDirectoryName(dllDirectory)!, "build-scripts"); +} \ No newline at end of file diff --git a/build-scripts/GeoBlazorBuild.cs b/build-scripts/GeoBlazorBuild.cs index 8ed261266..9c5a2faad 100644 --- a/build-scripts/GeoBlazorBuild.cs +++ b/build-scripts/GeoBlazorBuild.cs @@ -723,8 +723,17 @@ await RunDotnetCommand(validatorProjectPath, "clean", Console.WriteLine(); } -// Helper methods - +// ============================================================================ +// Helper Methods +// ============================================================================ + +/// +/// Gets the directory containing the build scripts. +/// When running as a .cs file, uses [CallerFilePath]. When running as a compiled DLL, +/// calculates the path relative to the DLL location. +/// +/// Automatically populated with the source file path at compile time. +/// The absolute path to the build-scripts directory. static string GetScriptsDirectory([CallerFilePath] string? callerFilePath = null) { // When running as a pre-compiled DLL, [CallerFilePath] contains the compile-time path @@ -741,6 +750,11 @@ static string GetScriptsDirectory([CallerFilePath] string? callerFilePath = null return Path.Combine(Path.GetDirectoryName(dllDirectory)!, "build-scripts"); } +/// +/// Writes a formatted step header to the console with colored background. +/// +/// The step number. +/// A description of what this step does. static void WriteStepHeader(int step, string description) { Console.WriteLine(); @@ -752,6 +766,11 @@ static void WriteStepHeader(int step, string description) Console.WriteLine(); } +/// +/// Writes a step completion message showing elapsed time. +/// +/// The step number that completed. +/// The time when this step started. static void WriteStepCompleted(int step, DateTime stepStartTime) { TimeSpan elapsed = DateTime.Now - stepStartTime; @@ -762,6 +781,14 @@ static void WriteStepCompleted(int step, DateTime stepStartTime) Console.WriteLine(); } +/// +/// Deletes a directory and all its contents if it exists. +/// +/// The directory path to delete. +/// +/// If true, uses PowerShell 7 for deletion which handles long paths on Windows. +/// Useful for node_modules directories. +/// static void DeleteDirectoryIfExists(string path, bool usePowerShell = false) { if (!Directory.Exists(path)) @@ -817,6 +844,10 @@ static void DeleteDirectoryIfExists(string path, bool usePowerShell = false) } } +/// +/// Deletes all files within a directory recursively, but keeps the directory structure. +/// +/// The directory whose contents should be deleted. static void DeleteDirectoryContentsIfExists(string path) { if (Directory.Exists(path)) @@ -835,6 +866,12 @@ static void DeleteDirectoryContentsIfExists(string path) } } +/// +/// Runs a dotnet command without capturing output. +/// +/// The working directory for the command. +/// The dotnet command (e.g., "build", "restore", "clean"). +/// Additional arguments to pass to the command. static async Task RunDotnetCommand(string workingDirectory, string command, params string[] args) { var psi = new ProcessStartInfo @@ -853,6 +890,14 @@ static async Task RunDotnetCommand(string workingDirectory, string command, para } } +/// +/// Runs a dotnet command and captures both stdout and stderr output. +/// Output is also written to the console in real-time. +/// +/// The working directory for the command. +/// The dotnet command (e.g., "build", "restore"). +/// Additional arguments to pass to the command. +/// A tuple containing the exit code and a list of all output lines. static async Task<(int ExitCode, List Output)> RunDotnetCommandWithOutputAsync(string workingDirectory, string command, params string[] args) { @@ -900,6 +945,13 @@ static async Task RunDotnetCommand(string workingDirectory, string command, para return (process.ExitCode, output); } +/// +/// Runs a compiled dotnet script (DLL) with the specified arguments. +/// +/// The working directory for the script. +/// The name of the DLL to run (e.g., "ESBuild.dll"). +/// Arguments to pass to the script. +/// The exit code from the script. static async Task RunDotnetScriptAsync(string workingDirectory, string scriptName, string args) { var psi = new ProcessStartInfo @@ -942,6 +994,15 @@ static async Task RunDotnetScriptAsync(string workingDirectory, string scri return process.ExitCode; } +/// +/// Increments the version number in Directory.Build.props. +/// For publish builds, checks NuGet for the latest version and increments appropriately. +/// For non-publish builds, simply increments the build number. +/// +/// The repository root containing Directory.Build.props. +/// If true, prepares a release version (3-digit, checks NuGet). +/// If true, updates ProVersion; otherwise updates CoreVersion. +/// The new version string. static async Task BumpVersionAsync(string repoRoot, bool publish, bool isPro) { string directoryBuildPropsPath = Path.Combine(repoRoot, "Directory.Build.props"); @@ -1044,6 +1105,16 @@ static async Task BumpVersionAsync(string repoRoot, bool publish, bool i return newVersion; } +/// +/// Compares two semantic version strings. +/// +/// The first version string (e.g., "4.33.1.5"). +/// The second version string. +/// +/// Less than 0 if version1 is less than version2; +/// 0 if they are equal; +/// Greater than 0 if version1 is greater than version2. +/// static int CompareVersions(string version1, string version2) { // Simple version comparison - extract numeric parts diff --git a/build-scripts/ScriptBuilder.cs b/build-scripts/ScriptBuilder.cs index 2337cdf8c..f08199a17 100644 --- a/build-scripts/ScriptBuilder.cs +++ b/build-scripts/ScriptBuilder.cs @@ -1,16 +1,29 @@ #!/usr/bin/env dotnet +// Script Builder - Compiles C# build scripts to DLLs +// ==================================================== +// Builds all C# file-based apps in the build-scripts directory using 'dotnet build'. +// Outputs compiled DLLs to the ../build-tools/ directory for faster execution. +// +// This tool is used to pre-compile the build scripts so they can be run as DLLs +// rather than interpreted C# files, significantly improving startup time. +// +// Usage: +// dotnet ScriptBuilder.cs Build all scripts +// dotnet ScriptBuilder.cs Script1.cs Script2.cs Build only specified scripts +// dotnet ScriptBuilder.cs --exclude Script1.cs Build all except specified scripts +// +// Options: +// --exclude When specified, the listed scripts will be skipped instead of included +// +// Output: +// Compiled DLLs are placed in GeoBlazor/build-tools/ directory +// +// Note: ScriptBuilder.cs itself is always skipped to avoid self-compilation issues. + using System.Diagnostics; using System.Runtime.CompilerServices; -// Builds all C# scripts in the current directory using dotnet build. -// Outputs built DLLs to ../build-tools/ -// Usage: dotnet ScriptBuilder.cs -// Can also pass in script names to build specific scripts only: -// Usage: dotnet ScriptBuilder.cs Script1.cs Script2.cs ... -// or the --exclude option to skip specific scripts: -// Usage: dotnet ScriptBuilder.cs --exclude Script1.cs Script2.cs ... - bool excludeMode = false; HashSet scriptsToProcess = new(); string scriptDir = GetScriptDirectory(); @@ -65,6 +78,13 @@ +/// +/// Compiles a single C# script to a DLL using 'dotnet build'. +/// +/// The name of the script file (e.g., "ESBuild.cs"). +/// The directory containing the script. +/// The output directory for the compiled DLL. +/// 0 on success, non-zero on failure. static int BuildScript(string scriptName, string scriptDir, string outDir) { string[] args = @@ -119,6 +139,12 @@ static int BuildScript(string scriptName, string scriptDir, string outDir) } +/// +/// Gets the directory containing this script file. +/// Uses [CallerFilePath] to resolve the path at compile time. +/// +/// Automatically populated with the source file path. +/// The directory path containing this script. static string GetScriptDirectory([CallerFilePath] string? callerFilePath = null) { return Path.GetDirectoryName(callerFilePath) ?? Environment.CurrentDirectory; diff --git a/build-tools/BuildAppSettings b/build-tools/BuildAppSettings deleted file mode 100755 index 45ce8ddd230d3d024b08a790b3c67ba031b0f85e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 78256 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 diff --git a/build-tools/BuildAppSettings.exe b/build-tools/BuildAppSettings.exe index fb1f90b5edd4ea9e0ca1c544de57428998bad529..f145eb96a8a4b616326bb8f9d24a31e04833d4fd 100644 GIT binary patch delta 97 zcmZqp!r1^sEsR^3qRM@Z8H^Yb8Il;x8B!T68BBm|2u@;11445KV+ISLOfpd10?dN2 dQyI*Fat1(AQ-&0vcoKslkZrhqc{x)g696i_6R`jQ delta 97 zcmZqp!r1^sEsR^3qRM?O7?K&%7|a+_84MVb7>t2DBL)iwL!d|sSlpZ;5h!j5<)t#1 e0C|Z(Ri+G=rSP*OC@2oVM$c?C0yl}Q1`G)-|! ziyoHUvg}O{i!2QvSE;YB=y8i~-LkSeGlIwbx#I z?X}lhd+o;=H|E%K^*SBXK4I*9M!B{t1SC@}sGaD?$z-W4ie<91*;yC0#@PgB= zdKBWLrACouVMwXoA+9j)SNsn$BBR4>%0wOEvBDT@_(s%OAon_8~*PfJ(6DDbpY zu19@5dOA@+njV-QC!+Yo#)#V>uR+Y1sYV{U>;V2Dg?|1qqs4|wX*6W>1+%OiLzOD|EmTF^mA?j&e z7B&B~Z>mp!C)9ofr`akQEhSv~Os%X~H6wXyW$Bd4ifU)Wl!nw9Q)VP5)Yl|T9 zs8913E+urXmueHde1r<4^1IF(b>z9nvhO)wadX6NJu@HMm6>;hR~q1pXbu|yJ{&Of z0Pu4{z|Ri>Uk*6c3I41GV4!w4hJfD@0{&nK_+ug9--du63jrtf9Ei{65O8-0_*)_1 zheE)=3jv>mK_7^ptPt>JA>f-sz#k3)e<1{%^m`zFPKVgAzz%K~_zbgd%ix6-l=Bt7DxgZ35a|rksA>hYCz)yyNkH%y$5I^UKfaivQ zUmpVQ2?5_60{%b<__HD4$3wu=Fp&?~uMqH@5b%W|;7dcmSA>9# z7Xt1I0ly^#{EiUtdqcn<3ITsQ1iUi@{Fe}L9Rj6+#?cr8ZVCY(83H~g1blo5_~a1q zX(8b2L%^>K0lx|Gf&ApA5coSnz+VXgKNtf3RtWe9A>f~cfFB6~|5pe&V|v7Y^q(HF zUl_jii2Drq3r8*-F?A%qjrfng38G;MT*4Rs4N}u^AB1Rxv%&m3oDBh7)D;+lriML) zmus}5u)Y%i5k$k$N(mCpVo+l8wWSB2R8Bql1$|wKIbOuJIyQ8WoHwwu1M)iNKz^$a z-@tAR$m`f{3yKkKp-8cCVV> z^yk~*p$%-in!larW7xAQJyCxUuTSkEJY?l+dO88*Pf-z86D39Uj^a8;QB`q8H7hDw zT~$-9LW)?CwIHvk)Lv&_T~Y6_*A?W=tE{QE7Zk6mw2QKVONvSwii^rBs*5Wtt_0#G z3zs-jD{AVCRu$LVldaVij(JHrYX^d7b2zVF?65afI9R=-uF?*^h{nt!lr$tIB~1gN z3gWx2qI$KpWQijwsieHPuEAFr>ZJ{LCGKECn*sO)!Az+i%V2GRUlManXk&?kMys&zP@60bQb6h0)gr2Y* zrA6y2?3JZZPgqACs8z4y3RZ7-u+?@)ZAB?7t3hisk{Lj2S2@er+Ok@xtD}sS)jAy| zY;AqDnnyF0^+l*vR9sqG$BXNUaE;T!%E~I8^(2BnsQy(>2dO_-+A1=p>XNEjk-`?$ zXDv}7f6PEq>i7tfa7ns<#Ku$-e02Uu{iJEcLQ!RLy`!k6jE}QA(8=0RLZ&Dxsw=Ku zZ7(XTa)8|{|Cn@BVaxV9hz!F0V-%8-{qCTs*x3L=Rot`u0r!+NlXtzWq7Hqpu95ps zid$V%R9ORSs;H@^!FSX+$r;ENAPYvbfN4IlRaCEqeU}y$*R6(QsV1>85P?0B?*9SZ zoLOhDx7V$O@Q9z1@;Z>KtXW6oxc%4JxnZg~Sgp?&{1L_QId9cX{vqw0QSV#@UTL@; z74WXBt9SxX8GlgCy`-|Ho?LHP1Dvk|_Fq+1T#E$uU9t{dq0VlnE^}|fN-L=rlKS;k z_||~j50qAFq)@fgUReQA)t3}kmyyK(fQkqYE9|pOkOm(RJF#Z3UdxJ0O6>LZlyky; zA*o$Q4^?YQE8vxoum6KB`@~;WB?bU7^tX&c5*>@Dn4T%S4Ojw7y~{Eyu*aOio6}dp3+LOX*%C# zojb2+TEeu16av>m??8{r5-ATqTrz1AWoIysx_o)j^n_&jIUy-9J1sGwbXpRdw|sfl zTx(HM!gM0Jx&)yMQA}d<=gk8(-cX6u2H2S{Xy8dDjwh`hdYr)q+yVyqDnUq%^R)ea z1hM>+UnkPz>b|%JZ&Tq+-DlU}9V*-y1n*ie=)?xWYp)mZ_#k-ZT>@?nf}0-^@CiZi zw)X`*F$ms88?*SN2Eh}@Ql!gRW)OUA5(hI&5PbC%0nZPDFH05h(>mLb@1&i;q zU4mna&-dw&;Mi*OeL5vL7Baq1mjuUFgYT2LMDRo3bk?AKx+Q$=42;TpB=}ej8ELNs zS4GB44@vOLBz$(hXqR}A`7}y!l9Bd_mEf98sVrWCt4k_g#Vo-GYebMvklwBCJCM|!D-#7eU?jbdEaBD1Rtg$BQ2NUbe5xiY9+XK z6i8(a5`3hFjI>dLkCNc65KR1UXD@aYnr zdXg7s8zgv|gx@H^(Zd`kB@FfyFUxF7%@Z}O*+yD@TD<$|c3BO!|UnIe6B{-eUYo7)Q zez68b+9<&IToe1!yWli-(1@OBAaD8aW%@FEGmU4pNa;2jdYSb}#-@KqALOM;h3 z@NNlSD#3dsxLtzxO7JoXen^6^mf*BcpS`GHVNOBO;zf%J%o*m{SiYOFX>>j-E3U*+ zGBIh|^yHKoGgH%8KGsZTM~!)0y}8O+?=Y{jo6DS)mFvw{II%o0tFV`*n^%mhFJuMf zc5_v6b@6I@sX5zbGq0*~R+myuM|nlP+1_9;!M+eSam^L=>E?6C)t}2Yu)$_?R&8x% zh1e!AW37ZuzM8uA=_oLp*~K+Z(P%|A7Pgg@<^=FeBH)?UiLyv2w0w zOY4b-x;~`M2lLwEx(d$yxOz^{TwG6F&??-lZF3~Bg%D#I^_!UsSutCzB9zormsPAr z$IT^Wt1}?UQu|sYY#by}R8(72Z=SHOyrQI>FveZ!oM=9u_ioDh_1MdvFfpi9<`!c8 zp9mhTViT$k)Qd@gG++&82o}2_rDm}qX`V2ybRvWJSPtz>iorFP*4XRKq$JoNuXnIq zKDOFMJnCu#q=|wLZ~~j^tgo9|k8N`M)Y2M9H8v_`p%!UEB|XRyi}3(qt7^cBV0%sdZ@ zHP5zKw9lm_6h`9HSzTFE40%_qsw=KrKiSN;VA5e>%vPiFQ&VRy#uinD1NPvovrnnV z?l+9BL=&`cYt3AS-UF%%oQzPVOBTBhTFUitvU!!$fzdB6rS4WhOY3T?2!rwo~R&aNkP$k+oHKyHrvHTi*x2%mlWhI&dDyy&nmDCtcx%!2M&Mu0gKr9vby3b`?{LCHAR}& zP@Mod3b@9)wcJyfiygw-jXSS)3jWOF&Ra31Ze2rtZ6O|cHa&4lTJe-J%9lcRgCW#l z|Hm!K=O2p$+$Qu`sHr`{OvAAX+sia&*syX+zB55}KEkymz@a2ujuVlPBUpm%*HY0g z41~-#!MwyyBjIyj=9*gC^Qt$mNNgC_P{@Ls;N$~F%ert$0YJsKu?xzyWv@tzlT>V7V@0izBz)N%g zXC7C|G_Q+DL>zq;t*e6z#6~@dsJN~QJWi%D@JoV(kZxv(VOT+3F-_)}VQ8W*HD^Kt zWh-YmJt!&188Hr$F_|nOjkRWHXj86aIg>A|OzmfA}b7_PoTUaLqFC6#s9_2jkH z*)PZ`ub?2OFi4YV1lCm4Vlr8sz_MTsq62(75>dzM%9>Thm71gXsgszO28=q!)lVH) zpD?a8p|S>{%%yBxiFw>5Oxy65#7(xvunk^OZBJkjBgq@%EBpc-=7>goZddmwMUAr_(KUGiawZ~1 zz_{_sh;bJ!&e3ME@np#v42BbExcrEaE9P(ZM4LXzTWZJaFdj$UdUI_JPL1WhP!}ry z6I~oUIEWcY0!Jofyqx!Z18f~vLFDfg8=b?zVvb2yp zBh7u}EwvS3-LNc&Cc8xomgdtb5{}jon~TYZlgvdVy$Y8f{OcIuG9?Sp;s>A62s=?j zZ5oMzhV0bIn<75$Gt}wj6b5N2fzFUjVDsbaDoQih_?r3*R$E&#BN-kr6tkylJQLl>cX|2A7+y&7R_Cpla)Ph@zT7xOC)b2ZU7A=mq6c|K!c;r zdUP48XcgDUfF*}q?(g&xC^91OT+(fFL877-D3c~>qoO)p-;kJbpi4r>O1@?l1<*9H zk>5kGjFliwp_<3S3If5j8Z%&}&k$%L*yo`j0)Bp=Y7UlzP|Uf$X~GO~fe4=~)bUnB z1wQ-Nt($^gouWX2f->6?D8NK%AyR#=x(uaxMkOfDkt=9>fefA6p2}kftU+O^+UhfS z{s~-tWas2&!O)AWYFx`#9uy(SBM>k&p(me!DgzAalqplpyf*ZgoV&W}AU{%w^#IH} z!+aIXgH>UG*V@VV)~s>X@*M)G%s?UW6q;?PKo>+_V!V$y zJ2iF=*qK66ns4ovL90+nDSdN|>MQ@#NY+1rpj*O2lYInhkC<50Fi*?_+8jjV zm=Lg>AGoq_;VySoY<^+JTswmWM=PiRm+^n@g#FfsyMtKpLycCgmt8Zr=09$f0YN9N z-%oK;KFKhalvY+Y-JVu5eMaK6VtcARH90A5TFRxr>c?ny!ywc5(d_m?XTKiJ-X9d+6|FloDC$@=>l{4)-Wb+D zIO?@%<{mPvJDP17Vm%(snxev=kJjB5H5PQ*$$p55Y8=TrOxJ80#XgE%bl*t!#nAIn`oyrn>n8Xt~hzUIbei`&I;dS{L&}1bam{ zX}>KaAb4k7^BLPv~DJ;Xqy^CtvcDj@_(_`InAu)eZVV#~#sLrPiBuvu+)( z`9Go6_vq1;btondAfP*Ab$ee6Kzkq}3gUPw0tZAO zz(1t_;c{PeL=_OY~v`%$4!py)UoW-p4YKPT?`&?)`=6Q{<|;MEo3-V zS|0UjBwHKvex&ZNx->kV1em?9kNPB%^@N#}NcKq>)bHN#+jQSXvZoD)K>5#+QC~;0 zj>xDlBiVmNQvLTM;Y2jHXbc8t&j%lF9Y3C;F>&dlj_BBC9eR0)KOWJYMV&xd48tLD z%4s`wY~dD-@7R^6f2wEi>Nb3_rfMQ5UntpgX904L0;= zLlmC37|e|AG8pz6*k28XFAcia3}ZF1gU)CMg~wmamN3?&zZuVe4vWI`mM|Rhque-o z&((0|34>1`14wHatb(_-oY4!j-|1ps zh-TO7f5r2Ou-I+U(0;Ilu9`MSv#o|TjJYF>*G03Qh>>4Mu`eS&N8zi+*gK-x2S!*F z`+RVe62)4Ej5-pdJQK;Dh~Qemabg*@`KvAl z-o&ME;IOQsVNb#4bt7Q;Kj?-(Vr2i+4|~kW8pAq3xWjP9$3}M2F#HK4Yl$4S4bPEL zz`xTN1)Ob059o=$4Owwm2PJ()56dEchOGEe_p=_Zf~*etSjOq;hu##%_NZ--S!sSR z4CXoNa2UHoH|E(e_KVI4rMphQ$){_v>!6{F_UfS}GrrNY7xl0C_%|j032yY_ z39y`Nb(mF&)-AdZbiZ=$-ax32GEzLIM+z_s|g;s?Wzoq0-;th~}qp{TNd4G?E<$@#DIfcl4OX4S&g5iusnr7o}xQPb=)yuh3lTyVI0^E z2E$DTc8kH#VbE>z8yIAvJ0ols{q{gRo;z^?N5OM9F5Ha3Ww&0W$w=dI(WU@tDpCj1 zG+YLTg=$wgpc%fq{ml*Z=}Xj@NGmtkj_Sm zb68f2^f=O1f|sEVQVY^3!e8EHFG<0$0UF=j)4JJMw2+u<)(BJI5zbdYv$V5}153ex?^AHN3jK&ouS zbPo9OMvQYj>RpHT&XATjgd9&pJvU^4wD%Us1!;R5 z@KC<}&(JqqX6#1#IN+U|&|l<_BRzzC_w6V@9sRlkC<;0Ni1=fMNusYpAK zHXuDrcu1p67@HT+F474|3y`KF?LocXPS_pgUj!Yb7Nl%6jC#2;sGnNSW zgx#PQ3w)$2kzd}0aYbs`i}6I-z7OMxw0uAGat!!50Q~@dJJJH=TMt4%kZm8 zZ)l(LNH-xpjb|Sq8sqzu@ z=uEWt4~z%$+mYIkw|tCx$S+44MScb8WTfp#8_op$Q{Yh^X(dwQXV5#O^cf)%rn_=6 z(>28E&WMUMw&@~c!6u1}bx&k$l8Q4huD~PQ$IqiTCh^INiM?JwFFL{kV#re2Z2aAW z@+73b^0`f6Wk8|wOY!$I%6S`F{RRH|SN~4=J-<`_^6!*?gz_jbHxPegQFa1@J6nLgU8YMy9s4cC>w}hDt{d1lTkj9Jk2P79p(2dV$6zXU;7vj z{ak-vjsm9}I5UE9%7K!D&JgNkl*Q{|Prxgd@if`YSCKCebcr_$@Xh%H^G-4>M|tcr zu!Z)0yj?)D(XRj%k|Q&t2$4QZy$A~vJp1tUn=}+IC&cXR@WtYtVIPjFSJW2bKlU!l zf2%D%{=hqN31fp!<>_BqgVeXNVQ6m!-XE5EQu|JHveok6YR?S3O5jBm!jH&wXnZRC za%=-mIdG-~;b=D1fpVI|Pm{|F{Phv9^YVdoC>3>%qFlL*v9IL%^P3`W5?a3N8 z%4tlNs(CK=$>EqoRx&n?>Ml_I!AfAjXOf(3C_jPn0DqB2P&U{Do0fMZ^-qns=aRo9j=9#J5U~vn5_}Y|BS2~ zDsEV_X8Hm*8@MA%;MZl`1)5C5#%b6@w-&hNrHt*6aW%RtHB9InaPI-`%N2|*mHE|d zP}M)O)&0Qx5qNjVH1nDwZ;J5nQ4O>E>C!k*JX*Sju^|C;{c;=;fjJQ60e)tYzdTtj z4;W`@UMN6$a%E6`$?rR;eqE3b=L`BIqfJx}BQePR@yS#FH*KV-?_Iz>el=r{$+&Xg zH~ITcw2uORd-LzpCi#z!gime>;?suK$wIi-7kZryJV)!mzK`Txit_z84eYO|ycy;1 z-W*flg!4VytxhWr7UCLc!XIs37dhN z4_u`^Xs+aQU|0p=QGXr4Nrr#g&z?qU?Gddm;7&m7I*`Acfbv5q zr`YyqJo|ixO||*YHBG|Yh2p3|7|*TP&&0D2m-DXY(x&$4w z@A-aP)PLk}ljt=9KlT2B=~4Msl(#-izlwp6=JQnBr~Y-JyaDAIayjSuLbV}spu(RV z0q#w}{Vjh&dOrdKvmbclWtucglip`>a@?QOdhB)J4VCdEKX9p9J`nHJ&ebTt^by7| z1^fG^`8UbmJP5o^z(eB4Q~59hv>ye@k6~=&axQ~%&Me7E0Zt}xrpe>toA<)bk#%YW z9OH;55mN`y^Y=R$<=M|OcBWj8d3*^_s9yyrPkte2jum=z5GZ_>p|QOhcpvR&EKa5) z`N4bC@@wRBO+JJt%~##PP5dkLNyhcrsD6!xO|tkAxXr-bI56(z8ZPl@j>6gtxS?c` ziSq6cTf4$I0%>|$I3$14yCOpgv+$e+-Sl?iM) z|G$E|#Flb6zV+D&3|GvGdB4!R^$BbpprBBVn?C#-54HSxMFJ~IU~5p^kig0k*eagK z185ckx`@-c0M~aC*rmK|Spst=uq$}OKHy~>qR&cTSMquw0M@`MXy6%KgHV1#EU^YC zD4`gvfhWyRB(RHkt6<0XKdRCN8qR}0lRp7TU&5<{0;=PGb@WJQZh~UY#(MaDtU1Ep z`FvnK`<`s~0;mqwwy4vEI?DNs_0iaUE7kG&vamvJw8@tpCHySd6XAa-;m_x5%dfOi zCVobPk0WytCx-*~za(5~e8WzLX;nBs`KV*DGIk!?34dRz)yA%CU_gqp-%Q<#%5BT`=mPB+T>F|KDB}K1it>g9%*=!gfF!b*6sI+Wbg6l zb0(bdL_7zyajBm+$ukpptwo?ucsEG2g*+eAunD&kxRon$d_lNtPQiUt!zG^Efty&& zSUS@16%ww@^GuSx#it9jo|_FPd6?+S{q*UA{$Z^R(w!5)A6zbek<@ZFBf|<;b#npKS#qS z`E&yR&>F_-h<^WPep`{`qnF}Us&fQ&CRbr8PIbNtQRjWF7s8*KBcRXKYCG?Qs3WyQ za-#KI_7#jZQ9Cb5b%dPW(OMv$8iBhVYrBs~H=mVorGA8+6KG@4p-#bCtcg)S{Ge1v zi*>HoS`d9W4Ezxd3`cpue^|no+o<*HKk*fh`6Ko!oJmn19t%WF?*gv#eS8t(?8zX7oh;jWW#H5*>1VGBRE9r#_q|BCQ08USDFGmSwn@Y@gw z(!N~yObK7`vQMKyNx&pqrOg+aAo=2;qNQeNkyH^X4o2J8$MmCBm2gC{4oK| zvz4fG^-YX@N@Kl3sw1~^nSZQNh5Ne)QLl6p!$~CS&6n!=ZE2#$9r5-q@b_=VUJUFz ze3FDO^LCzAL$qNWgxK?Lu_qEfN2;UkwOksAp9=iUEeuD+XydE_@Ye+5mjgd>E1l*8 zzyDLez54r5tks}Cv;)8Ae#S=9_ z_*ZXJ=bjfNeE(b(BjZoTn&_T~aAu3T;SWgon$FU;wYtV2`m~;#{V;y9k^0ay0KRW$ zM(}?V;Xi`8oakRQ0RFvx`Xr;5fq&#t_`xXP7fJZ?IM0goRiZjaQD^y!j9pJMdPS-u z%XNy@YvDh}4#E0hCr*T^&cnZ7X9C(;jyhet5gSrpZjkB-`@2|Uig25OTkr~FP+1nf z`V?H>j7$7?05`r1{-0zzU&597U+VW|sN(x1ePeaKb(zKWnWgFQ(fk6N$B_Okyt^lQ z-!qhC+=C74{w4NODz8_9>dRH03+?V#r1{bG>hBt8gMKvU5@C&{zGL6 zrnBG(@G0D9Ix7s@@1J_QO7q z%ogZ{ePzJl-Fuy?qwS@rT(}P)$}h>cc*aB#*RD^LH6#~z#*eR2C*sBd&*J*n(zM^W zqOh+BWrB?FThN<>{$}mz?=S3&x;_xaUp{Fkl^xsKC+e1P(-i?Wtw zEzK%eV!8H6C;D!2?Z`xjEv{cnE$;Afq;3{hPLJgv<_GH4{CJD2Wdg^TZ?qhoA4}i^ z^W&M#vvC57Q&F5KiYK6$R1n3fRMUbY5;q=~<1tlWS%8%QWX@f!A7%XBdU48B1(tX*!-+mkVT=SHXS~(uR(mW)vhHQ%cc9OIM!jE9W?rmZ_z? zsg$EA^E3oga}$8*^;Yz{&2liO9a(lDXDhS0ogFsUPm1ys+VX5@OFEYHw$1gO)zziM z@g6waz+=wt{(i8x?>y?0v(1+FrDK7`Eylu=*Jw%Wb}UCHF+MwJd`z)9?WUo8jL5nd zkz66St==kMufIaCL9rPA-Mil6UfOPHdE1d}aqr^e+w_ahd5-D%h}KB2`Ba+J2Mm+< zEM+lq?apb3UXVgq+#af~iW)edlHj!766%IFOWK*#PY2L%_p@?(Of7U7%X000@(nApF3l>;x-6@xaG#f(ktEg0eJ=w;j09Pj z7y(mD8%frWXKJBI>lA)WEtq11*fF*2<}g(Vsg_y0QXw=`%RYdu?%87jNaTQSj(3$2 z0P;+xmj4DIs2gtsMEbw{3Almj_>!4wUPGfXhC_oSWojn>?;QgLSSDJYYV_TDAnf+v1*M zY3YZmU))HHU1hO&<}DXPJX6qzub!7GAmg+aoA$+9(mrtvT9I{GVV?<5de}$Z_FQFA z&L=d_RTGpHdT82bq#17_o=L1pNd5ESzyARYl&*M;TF*F)5yN~K2P^Z>hl^0=<6Jz@ zd`I&U=2X|OHupNC&66?V18SgONhDIPPm+$o=fbYYZxsSWMR8P=p+oRqHwq?@@;Hp+ z!`%#d7_xFMY~)hzUFV_j1FWB5K#O02>{*?wm(TFRE&_T){V_csnI2(kxeQihdVanxJy&OHxlm;E>A8AS zOSZ^_rRRp3T4;v`XyNI(;ieY)SC)`5q~{t;En`F`B0U$>Vnrr0JvY+S5+*Xn^jxE< zUll+^n7fSlNZIfF5b~ zp~Y(xF!C1H4_U60{uU9d**sNUHdj>#CPebunEtz<^BX#pBX^O)o0^XUNC1QucU~hv zUvN;)Rz8Dk6KT#?p#9Nc%p&GY<=hikyS_}5Ej7IjL77^zdn($5#3Rs68U)X-Lnlev z&JLv{1z7GKl(%`d9i=4gOH(r`9S^lUd0P>Rn3~Za0FZGx+f1=73J^_05hYMi<)JcH z3#aMw5RJZV0A;=0LQ^TCAq?-JN4OpD{}Ykf&|ypa7A|w-!+|4+Iq>obnnw>|8YGcR z)1bw@nHr^-CJ7YUEP(+{09Q8GHr^o~&|LcvHOV=K$lEF46w!=e_#iIUJ^vx-Fx^)3tu^;GftqCE=dW$88&albqYz&0mFB7uEVjF;()@nm9>VCH1K ze3uv=ibl6R#=A1^9CW2dS%~5DiR{!c@b?5ahpKCR=dqh|;(LT??rk=7Ij7~Yqd+ZI zo;wa13=lG%c#t><3Yos*Kn%Sn2TFO+)cnl9F^RkdXy0~vI`|zQ8&mU{WUYb$MUR$A z=Sfkgkx95)VoC5A!$>?E>DkrNNs8~bx95Sma6Gg@`8xpb0$iyfx~`UPQa|?&ihCfj z=bqyd3pheP2mU*0((F=RBZwzs3d(^^v;o|~fm{cb z3ToA}tA|>39Z)U_Bg6QKW_9;wVhfQuah>OafAvuPtp4}2T>Be$_2L;*-<=$%h1aLf zyO2-n@9*hrqykksW6_A!wVz9erpJM$Blnz^29RAi_YOjElC039OSbW@d_Be|n18~x zqjS9+q6wx?0QU(r(!u+M4n9o~l|B1WF?fD4EWr>4i-Fif^iR^r>VFq9P{xdBEU%^C zd1ltNCvJz9JL8CF*z7JbEO2}m{M_H~lT{Ve2VM8cst|cDs{&r2jd`2sT0cf9>t&)V z6Z{#f+&9*bZ*A)Dcbt~>ax=%vSSE&jAL{me-s_=8V$el(pZ15pZXTX#INXR ze-FCU_ZoNZoS07sUl%lMh~}e2({)g}4g&I)(o+LHUE;+Cz&^F3&PD!C;vW}*GI%ox zb0J|SdK0{pCEPzrxT84kP{KWjaKpWNFI@gUZJfUO5rO*@;1#_7>uD7_Yh0VtByes zLjPMR((#a8jT!>u1s(&P5-cc@ap=hR7%YUsbruQ}#^q73mUciH?j=XyNjXi-laHTH z1@cPz>-(@;Z8>($q#W0e2-ZJ)2%a?Q8*M-C0(Y_(0XnR#4mGX)f6Yrf;`|z97u?5f z?@~a8HYsQS)Zg#gr(AGOkTjebJiJEy#)i=jv! z%EJVHhJz{3_wN?Ig^poRAG?=2m_c`IuwJp#HlA<)?mm?HD2WHEZ*}!pT;EaXi2XYj zh!QCOYmZRykmvfV)%Cj7^}aPIYHK<6QZ(wDay~wWmp&I;o31f3Y`%C>mTre5as6a- zeXY!bcrZ*UR`(1@@gXrPBTu7cq?7#i!&|vr!vV3mzOuQFDHnt5XLWr=g9x&NJoK@B zdc@1{L#9o8jC-7etxX$^%(QurSD*AMmnrRkNs*(=^EzrZzv?KYp**Inc5ogRS=}?y z;xaIY@x3)(ZSkb?)fN(7%6$(L*ZBWyrR8i9eLRBh5u?xQQ1OINfs0YG4{yY=WBbA# zSd+rwTwf~tX7M(#(d+E6x<`x!{xe&6Llc4TEjhN&=(x=0ac;FC@bm5a;9Xd{)#6&(mgi0$ zivWdBYVOjpMr;Y1Hg{p?%%=O4OVHHp1Zkk;lOJ*QSes77)}2jlTRc2WLx|gS56IKr znA;<;^0v~PJ{q_>r!mX)d<^y#V_?G=GMno&rG)U^VwY2n4m~MPLEc20w)m8*Ay|mI z9iriVt>k$~;B!REw6cYW8ZqqL!(ofkmUhV0O#dh);szClKISy`xe4aUFy7Ct{V?R? z`a4GT2-*AZ?73RhFux5=T6f`mfj%2iOY9#a|FtiVGszJ#YC$oXM`nf%o1Cpi9K4ZoZhZxI9$X6l*vgZpbt=GZv5i=(}ksv+U%*jfda4 zvAFWj1ZF%i4}=@}5avM^JW_0GfPv*lF$#=oRosad zcfL{OPWd;O(uO(JN2fcS_)qy7TQn-7pz|iM;j^sni+luL1_+~WYA%P`LDD*mrC_E> z5P9M*KM@3gOu3Qd;9lB^1)qtu02zw1xwMojYQd9phL1{ypt60dp9&0(DcPI~4-GA% zOMQO_Jj)atJ~qoVGy3hO@AF@OH)kEm(nnLQt~AS%w1TCXp8$0 z_hu3u+DfF3T6xKqe0r{$2-aN(Y;Ilz3~G>X!tFbs1YmfoohoMhz^yQ4=4Rwvf5+6D z=lYp%!f_!h5yMD!v=4Wx3SY39I?a_xY@ehF0P&*DQE3NhGDWw?h%(|$oAvmihyn1V z4=p@Ape>kx;c5wTuzM{6{Tz^h2-MW#fgljNp1Na%CFKJ=lvxPd5kx8G*a{N75sj?# z5%1$cG}HLdW*oi`!ixn{LB*$w;tMf7D7sUN>BkEo&4;M=+ov-8ktn`_il05Tc$X+H zr{Ya0=23W%z)j6RK-2oLpU3*X#Mi($Z&Goyub32))UHC=avNg1RRdSTB*46j`{*>O z&rQ!*Op@;l94u4IdxSt=qPVu7tFWngAHiSr!8O&+RVGlQb2v5+|4hw~66#ibdE*6< zKxJ_x;W)vyudhiE^HvI^Sl-fC&Iw>Du!3{SDSSF+dJ~l=Zv~gZ$c}fE+XXxy1OwFY z8V>ibD-BAefM^;6ExH7d0Id*9p%Q?VYJ3GQV1m~t$9b1_DC8RgcguS$JlbCg6GU7{ z_vK9&b(tpefB|s>`B&71dI`UWrBugFGzMG9S6Mt88kNNPFljg6%eA<-kZZN3ePn75 zhg!katt9Dro**H5NBZt_Sey2nIo8LZZABms^(r!Fo~r0nd@K5v*-!(-wIN{3*77mKgQ5naRG@23#W^@+OIt2{KEM0*hH zjnmO4R~`g4o{ZCPxh=vQ#a7H-Gx9i&dSv>&iz)AFH%Fa9q?d4M69S`MMp*{Wonlk8rNpzvcN%zKe{EI{6i>8rr+6#QLt zCVHpy4Jy8)+Wr9+w7)2~60*1?(}hEYPJY(mN%@oq`x`oyXNFNfQ{K3lQ~~+F z@H`$)Yvgu(7FM#`vDn8!Nlqu0u?~ouPEB&Uk#6^%MKh-poMp6u0#xgJtSG%1azNb$ z#I4j}Y!`H4{BbJQO`=7h{ju`HFSPpvoog5I&T!7|2DC);y#YDD>}KWknp&<00lqPn zPkO|U_5v`R<=~|oaf{Nx#iY@8n$ZZ6bxse&PbGtU89r0F179kV<3nw;kaP0i%2g-} zsORYB`09w$cw;}y^@GLr3)&yeBkvRgEihdlC?5-#I9izEJEHJ7Jh}^KrwYBStDa>0 z&QUdbxK6MU7CkRh*xtV|0pm_VEKI~qN8e;7k+|XZ55QOWDuX9Y|G<;`EYdPt+OJqN zyH0vz`29LzZ@&iF+wCCZJ#2NKNya>k91UHd<3jGclA88q2RiRXTb?l+cL5Co{qRQO zG^6wZB%Zj<&l6O;kM}0Ze3l{}IKQ|*(R5^`U&^i=1d%Gs%Wu#)r_HxNB>H&XoSiH;KJAM7>>^oIrd2ORxTj_#O83ZVS= zWpwT9ni&ehE!xhRLR6La3#dDw&5I>3D0Gop-q!Zh z)bce;<^Jx>3}>4~3?m_R()RIlop?z=rp8i`%g2e(E^J z)NmKDAmYnBT*Q~UDBhP01;$FjDxqmHG4N3zSonsforhCLe2t;<7|y;Vsc_LYzu zcW)+4I;KvysOJ@QEIUCQ%Z|5b*ZEpxf-b&Hv$%S_&I+7^LD|czZ{hXA3frx&PpnM`hdNG22&b%t+Rz;%*Fn16^9#>=!*Bt=8aMt8oWzs(SEp#%?I>Ql{&xUY!L=Jhso z?52f0Dmi#13Xnt%2Wq(XbdsWBpC0#{g`#=j@Lh(hI!Q1bBHY0YOkM{JH2?$dY(uPw z8?6~S;Y(g5(){M>htwuEq-`#b1weS}6hCEn{-%ssXg_Sj zWqv69r>TV&B0@oq6{bbeQI;3T!&JNXK9bH7i9(DuM7r3IvTnRnFEH^zzC2gwsp<{^U547 z(S4Hi&yTn!-orWK0)zOd-w+=;4q^?GAlCy)2oWrW2nrzrJ48?g5jeXbh8|fA$I^E?GZDNUw_50 z^UW^TX1=^}d1!?KzWLI@<)Nhm_)Z1iOz<7clg(zHY$47}6SrmZ<~TVHYN9@ z7d`2rMFL%z_vB#^^SUiB`Uf$u3nd+<2|18K&Q?4_E;)_l={$K|N!a>=yHegqSNgkb zCC44{t99wOuyu-uKu(LHi^8$i9krzzunRA!FZHutzRJXzd^bMAuf%rZNpt)3e= za#JdEjE06`n*WL->7PMa)vzy>6UZDe7>J0^0d!s)sBXPD6uNdljg>EwMsqvw6bpWG z((i!^E&e%KWjk_B8QY!(O;bw=`1Nk#e&RlY_*z+fN-Nw&tN>Uk!+0|0P?TKq~f|F4!^gk9pJo$b+tbnoDFXNyJ3?h z4jVZ4IY5-&oZ>e?jw$z3Q*;?Az8d<1)j`XL;3m8ID8gIZ5T?#w!>enzc2^?j?+yip zOSAFTVAK9oDLM^ANQNzr#i~zvmvc>fH2hMQK^cAuF+P2a23R>4U#eWWz0&)0h&Nn6 zU|)j*<{PdA=XAC4c61@neGM06^R38O-4SyeuTJfs>R8w~YpTPV=UJ_@dX~k$Xrj)h z^*F+KFui?sgI z4oQIS9Kx~+F(&T8(>~8SqZloawduZ7cN58eXb;8aI_`Vzu8a0Opf#oI(Vz>5Y1kQr z*7VRceg=Q-F4FY;5LIc=4|?6sLb&vLoeO(^?z}E_C$ZyNOe?sK0t?|bGG>xSe+4qiDt!6Z3#8BSK_^~^EOY@6Hx(c@E3C+q;@#NaPs?MdG49Ij zB|B3d#qiP*wyS@40!lRS0{~lGd!WL&W!Gg*d&6}5UgKWpH5Si2h$#$rb@H@5cUerP z<`bO!sdAdTHds!k<_C#0?6-^eZ^R|CzFUY3(Te3DZi?b_U|uZi!zv!G0+y+9Fvphm zoy|1&JMQMe#4k48_hPKBf|+8V@M^3yPj`7J_Nh1PoEH?)>ieBepVHB~NcMmX&UMHRI?RJoZOqmaNi7~_eQaCy4 z#E6VShvmUK(|qmeuptlq=8$5CM6Tn#!u|uHyv5-r^7ez#k=#eQ}0q0 znywB*uJDG!u?L(GVqcP<5PGhlV)zhfOIO3`rn9hTn$?E&_y%%dgUJArXqo-;s-lS`@;*OAM-f`8WkJ@y{O)V6mK^t0m7_6R+ zVw2L@ifQe(THfN?tl>zK;hps5o8?mEQP6>7rNLh+_(rjIPp?=Xy1&7A|b8EE&ti^@%@I*{!mZm*85`O~`xNEw_jrT5=#Cju4u{zUGJ-?ReS^`&z z--O5%(@thA8lpQj-#RjsJvA7b8GbgdMP3ZIZ!)*I>y4ZP-anOzJGoj=TuvvkSD}_) zNBQgNPxIcn# z)SuJ6G2Y=kEBZHB`4r0(ZGt)q3Y zlx$k6>{V`3QJr5xeCiRa9CB*(HUAKw<$5E_^*5SK1E<;^EPPz=q49L0=)uvjYgQiK z(9aV$=CP36OTUF6Zp^>MxwAIEYHA+F)!W|9*O9p0vl%xyPpynte zH9zcF$*;{@xi2?)7ZagZF~C@p3PRYAR0%1ARU$MYAnWXfkF_7#7l`Qd93mo zrgg8rtesy3476jCcbOon+zEe@v{8)^(-$46wO-Eqf0kIPudG}&^?Z(M%4;sJ$W-nRLIzFv9yA|DBG~ii) zfgXb0l#$d$I-bOZZ;~{{=&{~pOJ`+8wX*^LB)Y|=JCJ2erpXpJGZ2!VM!o@6esYXd zwqaf7HQ};GglQ;#sY3)avOpCqzPOYU`C*HNU)A814N5sG2japBeWF|euyQrNe8R#5 z?>}AU%6RGu`n{alS*Mn(^6-M8wbD}3-eGfBVNyKI*OH&vbYJuJiK$r^MplYhx#`~`n8x%-FDZ;cbEru(qSdts zmi~=nI&gH(vta2_V-bkK#VqBVO^fHZ`yi<@cSw@U7xz8@)9s0hgR4I z7X@v^X%Wa!>Ii@1NQ*$eah%D8F<29Zsf|y^yx-riCN_hj0zs`q*;Yd8ZEAUpx~UD< zYNKt|>R3my)ssCI;}t#@gN2j&4R~4k8$MuPYm0wVa}sBCLn{wq&Y+Vb5hhilRjP^+ zYqh#;WBK}j5|5Eafy+GlwWZM*8H`$+vT-E5#3ar#epu%hWsb_S)s;OKV-+{d6ffYVEnM!tlE@wv-j>Gvb3Cs$G8oPF=gJV zt%@Z}hJ%X71{XUoN6m|=i1?)_Vdz1ej159O;vMgeH-dKMJ8GwGNTKAUGpkVm8)!+zyfJ7jnU_UuQ|O+UPUaao`ShBlqYiuL@UEcn8O|<~KIAmuy7oe*JdjwD=boeNr<`kFw%dFVP0%X zw(Cz>u83!K0a%W|D|1SN<#+^SFCel7Alo#HIm1vF)qLBhz0Oxj_x=Tc!6m}wF>rig zDZwNXy@0k--N}0}!FJOE?j_#Y=toy$mg$T_M%|!R>Ok^;zZSuajDg_yhm$~1?y=%b z9U#Q;lsgD- z-2)Xct)=j_hsVLn0$#LV1XX}h_}Wi?9GeT|$0M>@!^e7K#Jj%a6wgBiErL?_NNreF z!Rt6C*xa*nk`BxB%{q}rUZ44c% zfljocWn~DyB!daGhkVgxe|_l|^1+<$h8fA&Kh5b1gK%EK{wY)b4Y{`FbiXo#GDmsl z)mM@!^EuDF1`XP1hwZ~_2zB8v59JnDO~P#V_4ON>zj^f3kPB4x^%mDFjlU3 z*)9@>C04KWzHiX#RopekdiS8hKow4=oo>#A|H|)-WM(~24yv8v&F@yK#ry@TcaZb< zliwJ=0ep)CseIp79Avi*u?3NQ8aI#;NSiJ0B}Ps*+KrMd`j`FS3h=X6N)2dw@1e^g z{F-vV^G@$Qeoy9%@uFK42Da1jhC#^>Wu>;OvADyyVtUGS{P@e3cAS2_V}X491#0Br>GC-hsL?pTvg?+ePlBT{v2^nC6m6DX$fQPUkU9{P=9Nq|GyKu!2N2<`V8* z3Fjfg!E^%S?-tG?9FU;lY-KbqWEohK40r>)0;8LLIV__LwfWSGt%H7S9n2QUs}4fTeVYcDzfw)g6!vW~rA8snb~C;%d4ohqug^l}&wLgffwZB%pM4&9-RP6K{BsZiNKTS^{u)S~K_)`b zT3DkB`depGV3ipp=qxm+jOIM^9iET8L$opb*72sI59Y@4nsO#!lCzWTNtJ)V8QJg4 z{~=lam=Ce#ho2H4`F9KX&&EAZA%6gVNB(BQ6?-1q#sl@UZw0``*L(%s;|vR=Mevy0lB3g4tXtMv-5j2IXn+5!ejh9NP)7 z@>hIu!TS8u|B*f)xkQua@9T39;hv(;yNkH*;re{?-=xDrpFjH~Sf9I4KcK^=mj6_r z8!;0oi4e;FK%cKbbBc}gJdi$53+4v;yb!S8*5?Tp*MGr5@Hqd3zcUoL@M{}bO8%m( z!{kU};J-qb_D{zos|S}F+!6HKv8numgKGwUH?|XVFD4Jn3R4mJK>m4MUi{)ILVH|N zKacKQ(FOI<;)43=R;dpCv>lAY8?Vg1n2$-P)s>4CV2oG*O21x0rP`H_Ov3eZdA~YX z2o!LNf6{Rt-^_A{5trDEblJ9{X!vCki~ce)l5YE7Ze14G!5WAl+I(9j9~vg4cjz z_4IoY1MT^#`we`Kt(Eruw04~IgL_&NP6QMiH0sV`zi!goE@i~!O0=iU3S#UvFFH>_ zlSaqUsf-F@^;z#Q-wvC`{(Z`K%L2_=aonXuqh^2s1^X|HFrJ{X>9Ov@uVm3@qs=20 z2?ujJ#?fSlnHSS;4%(8YUCYke%3^1AN%c)IHSoH@uCi6#NFEG;YZSZra z2S6#SaSrY>+({FRCkI!9<{3@5Jguh|QX=79e6i1?Xo}X`dzVs*pAo_Q(6k3uf!`3h zmhfqw;AaPoz@%kGRaWEq%&{6|j|#Gv2(mvweRmG0PC)oeIN6_+H6V*g2G(nt1aoNW z0_C3iv--xP4k&ypD8wVjfA+=ZNr}G{TKE6j`x@w|i>vPqpHb0`$d@1zb66N&La|?P3S8KMf$IG`a5-ukyw!I>}p~>#`I(e7`@nv z)WO>r&n&p{YEnNew55(@9Bp|B3--Rk>cBvz(gr06xgLz7Hd+)HefxsliW;U-_Fc?3 z8j#^5hAp-Ghp0y`Lp6At3zo5?IP_2JP6HsEi^&Ru@J!vwR5Clas8iX1a;V0;TFaOi zJWZW70UtD{5x5d0S_>35H5S)W_+HUi{NOt7tL~Oc(mr`nmvxOP48x$u-UTXGu=T}r z4#4r7T3A|({EU}vy9ko(u+}fd?!!-j5gPv!Hhvd=qmNhWnn2h~#9P#SfysW!+V)(U zI9_mpWVx3$ZieXc^zGx2M z4okIj^l0Ex?JGtQ{%FtcP)6fC)cGF>v8ye%{uIE>@@lva4rTE-`ZMvi{YH7kLzgh~ z6Ur-oExf#v?+R$kEuef{5(S9s>5iCR&zx*9)8?cv_hwRaVtg$i_=7a1^| z_pM@wXtDM>;d)d~ZfB)>Gxm7*eBRYt^?8af_@b}s0GQ4>r}#v(06e~XN&Y^cd&(hB z1t~B3+>`MIvFRti4A{HGCA0N)FzSapuY2C_s=mY435sR58R0JHfnTQ02*M4%QX0xE z4Xwo{E+%W&%3EMUICMA&Qu3vI!t*=PGJ5NOJO-AGneF{;IImL?H zwIMPH**{iqAUgU7Q5`~d9?SSb#W?1Gf(Z8QFfU(PUlK0Eh2Z+M=w!!vGi3U0-cYJ9 z*kgz{~yC=li^Ir zxt>MbHWZu+bqxPH03KW-WP#S+AoE8A=nVB3;tS2v<&_3+YV$Qtm-31lx=Vq+@T_#E zMMlss@}DgQB1M}oe}CW^!-Y}e&eG8ORF+x_vvsBUUk8RoClX-`_Kl8m%F`(GN33M| z|4eX~!;%E^rEGJagcs)uU$7Vk0n1apd*1EpO@XELc@Ci=*Lo|G)_Uyi^4 zboB*2mj?HP$$D`(h}jKi0?R?rZF1}-dIOlU>v8(T!vawGS-?fk5UG(|3hF(Bhk+n- z90roiaiv&NwLZ~KL-V) zT#PYj=DQ+hi{q_?L5i(;2Ud@E;K*TwP310EjY zk=Rv%ZzKqqJ1LlR6$qje8h{8TCeB0fSO&k#a=7rN;yoT#SYMAx;#_zp#uvI64sVr> zAJd;&o*$2RlJoJBaA}kJLUvj9WO~=di3KPu;YEIiM+WpV+??s(*&(7}vIX91D}F4Q zS^A;|gV%{sD^_UDtUne$P3R>ox|-3U+>_z@Z)5RR?Y9kn#ptxsT6KxWv{tWH&O#wr z9)P=XnbkE3oz}pA=tYaeq&pJ^(oLGc&^R(wOum3)r)37P--#ca@tf)~KnXGZ2UQdN z{_odsH@6o%`6CMowE#N~;9;se#DNv;pC@DWFIBhAhwAC(SKZ?og+KyRUL`FQsLiz!!VT9^FkT>?LZ96En`YQT?Ewtm>?+>RibaG?U+ z&*i_?3tkWTbecERw2_Y9i8aDuim|*KHR04%?mtZ$6>f9T#6DvD-ijE z#$@SM-VaYgq23a@iskNpZa-Lm3;LoES_H9WAQFSfXW2i2YL z(r5=(_{JBz*Fdehk-mRU-&6T{N}w-{;%C)QEPb%5xxK0>B|0>#K2iqsQukGUfukef z+ulU~(@maRRhy;(WQ7d@p3dO~i5^~FkU$5bSSaY{?{`<9gG>khJ=V z-5d{s94BE@9T=jskh8ZyM!YMzIfr1SMeRd&>R$XfB0{eHWB4S*_P?sO!p{;6=|lYq zBNy~y?{BI`z;t8WQB6h=NUox z^p`Hds7gaG;1qhlo9N?I;|Hmw;gvWTy3}0k!C{kJ?%~AZ%m0JD+=F^!D=nuRz0l4` z%F8{OCBf^==>{(M6obA|+EY9L2%r8Pl*a9zEpeWd2!W&7asBUU%mz%3?>|6Gv8%dR zd<;&D^+}6&V)51!AGK&?ek9#sky)6441k>B8kzmL3hhLu`3x2wN9G!fj3BbN7*k+7 zVNavKhnj=(^o|Yz6EVSUMQaqmGcjvfj7@{Lx&%M*^@_bufU9nwbWmx4CVM8jT+Kuh zQDwZr+!A=noAHMG7QW5m%{b%>-w3ssi+r8W86xLTQ}$wc4`sgKpS>x2+%;!176qqO z$I@1xkMWD?3Tp-`AH+a%l2>QT9)eBxlb;#$P+-PXl26WSGvJ7=;Y~@MqwgHl zfaFyU8bobFiFUrX_GT!c5q;ETy_04fbW0~;74|Su747e7UD&YurUrlQ0pEgU&2%`8 zH5cL+8R2?N1=gZ8cD23|`zu(P!p)9W*<$H+pSAbApex()R}g+6lVfs713BvYN^e+H zw@~IClUWkXTZF_O1cWS`sJMy+DC|9V+-JR`Rx?3J^uv&DBYM43q}e)GVyN!>3svu) z2c1{yMal}KDXu>3uD-4_YxqT{)fidbD)T(r5G<6-sp>1uK-ISUScJ60M8wy2mt)n= zf7f00s5Fnf?FTX}%{T(4zX_;U!W2_UO0+cN!_xfsOWl*-H-vnsB&F3|b2(C(#=zd~ zM2)eb=!A`d3w~&fRf(O^`LM5~t>-o`zM)UJ+ChHnUljPgHnS6340kQhG1&Kxi5 z2y85v%S(7TA=wD?Lo7fT)OyAs-GksGcLVT+zKh*@i@WLvC=Tjh7+mF`r*0&$lq1j+ z^rjrb4Y^dj<iT&gawNw*723|B z8`?~&4TV`8^bFBDEvkqw_84Q4Ff7m$InevoCqP9{r9|&VA(1~LGtBK9IY08nMCs1N z2ZoY+0sCU~sKSeh=@&6EH9F1Xb73YoM`V@Q18qd^+D;Q03RepQnItU5DC*ckyj^^v z8X&$l3SSbIOjWNoT`>?P<_A?QRM4V!{Z>~7www+d^fl*ts(`p9Sv!PTt*_A?cOxwF z3w$NDaSIzGT;RVLafSfVq~LlT{Eq@+lJi5%E*PfJmc_y>7d@F2oBIP~_JWQ*Q^&@j zM(k9?ju}Ohwjj=@Di`n)lSGM#M+eYuzwjbj^-GTqHjv>=alIVFwd*cS!ba=m?18s> z89#PyOV{pyg2zHLQz0qwhESHmWL7^iM4b0SJ1r&6(K` zE<_nK38(WZz=_`AjcFypY4A7>1GZ)@Hi>EFr1d=aj#DNiuX{A`w%WtLoTj|0z|+M!IXpz&lv{U=i&myR2+qFC2RlCz*+bMFyY8 z+)~e%kg-F6prffIL%DBhL;BL-d!-o=3}H#%c_j44aR;pp>k(cnRV68H?wXGg31SZH z6|oF)yk$#M3~rtYC75D3>K`S7-hNilOXYK?{8Cn(BKLhKABjUrrWwRud-cPp4}!M2 zccgN^Og6&no55G2LkcUuzPiwTM`K}SLk+&)D|Fx9P+0w0;ClCtY;XJC!s?@b#wl8V z5Wiz?O2hAH|GUvEt^5{n{@T}~-pch4j)bm%<>wyE5UB?O!zb23-nHg+ z40E)gUuqD$A4tVH?tdVPNsFFUu}Le-5e9dgger;t{$}J-iica;UjWCAEsrV@%*mep zQnLp9hn?Yrq&?#!_bs%c$4+8bV58yn-2fGLJSiMk zamNEKZv!b9>wAMW&KEdJ*H@Fw*~UUGl* zm;UI*u8=-a@4jmK<8-KOn;tklnx^fU>*lW?pUn5x%h8o{TbID41M|S42V>?0KY#2Z z;pbq_OVLe<(Q*;(yO6!8GtgT0q~7%T#T^R8_-av?!CTG8&k4r+yvSXcnsq>Izk&Ur zwqfK8e$1o3pxB9q)w2gv6O#bQzW ztJoRf+NxJ;bAU|iitKgKI@B2Kz?I9iow2ik$Hi%U@9vzU6Fxt4Tpi94;zC56`F6Cl za43N7`yp?*yp>BG5rV2Sk-f}Ua$eAT2@AvfuaZ&0VICXc{ns-3+Vg^02s=UhYx@#6 ze<*ww~WcR&Y0BNT~5&@^1Xeu2CYA{rE7$SofPmYQS?;>o!VK!0j`N+25WA!*v z#?zdr;ac$~5gN`KP1G&-CnPiNVCH*B9AF1zcRebxa|l=q$o>HkYj7Vq-YIC-2LVO< z=|`Z3Oy>ycy%Y;g#M`-@7tLt6=VbGlOhuw9`_JF2x*Hf21@{*PU-tz+O_GB&14XD? z*Q@S8hKOB=RZw^{$^SXv?7}kPbcEoHdrC=o=y$QwD#e4VAE47iX|Pa-r6P77NbcPR z5n>6pnCcY?!)X3rh)kR1!M`GW1jBLNSX!E=I9+_ffGZ2nYriu!_<2$AP%+elp9J;s zNSb(zL<3a;j*l@Om7Rwv5)IE9Io9XE?7j(jAdjL^HFIpNBOLu9>LX(IV_xJfObbqu zb@B(?p-gHaTz=77(^wibpci4^PxY5kxQ|Ewqi7q5gJf>D5UUtJ#Tt~xN~_?-pfAuQ zkzWI6^;=L$C=dHucl8#+e@95{It|31)sMhe2)j{rMQXMNCVy?X+r5@o!|EVqqB_yh zEM~4vE@7kAM0w9i4R`;UkwxthY{wez)~2>6W5A(BaJ$@K*QX$JrV9j`fF}&)@vv5{PE;*qS%E zdEF5pNfS+uZ3WmXo*bw}NJ;S5n;4<7=59D>NIVEbf1KBV<#K1caTE4K-taC)GYU{Q zNy~`OZfwNyz5>TTyW*)>S8zA97~brSn)<-m>RQGL7yM@t(V<-?G~nF-1Q6c7O|ouR zS0GAFeUW=J-Uw($PGBj-z9fjX?iNNW4f$!41oruX98dKj6u_(`gfoNqhBTV8r}M$X z*@D=ujzLwl9elP=!f4oDFC_^sqC57KEHK;E6JwAs2Dp$^8+P07wK#U&MRW{R)k=&S z@e%#-F!SQ(JYP2FL$T=O=6oc%a21AH98Cz3F#aO^k+l!>%id7eFh3!@nmT3_3NvfH zf0c6GI|k|FxeCOr{9+8=V*=lSC~6g>H`Z)~gXV+oQ%$A)3~v#ng3^xOx-qL(uUt50 zO*v)*@vkdiRxxII*(z8os%g}@uxvMSX{g!HOt2QY4Ge>=e+0#H!5Gr5kl%te=9>@< z^P*<82!Sb$<^nuHlT3<+gnk?BPoM>fB|4uNiV_~StSZ1^z;$T#+tU9gQqF;FmduCQ z79s}az`J2Og7drTiGrb?)M6Pm}WPY8ZJA#k!e=p2$LE_oL{%^NXTBky7_nBk>fM=8-Et`aDEeYmr-|FTN7!I&d|{)k|0|s-p-`3@Sv> z#EA%s5|Q!dMNsG+gVo4b!HTPv6~HF?Le(cZy??yQhM(TpTV!yko18ZAd}6V<4A-L6 zD6oco_~{Vce;|5CA_F7;!emjIH%9D##rOaJQ0KAd(AHgLUgsu!5Pk3-N&y?*u#(dr zW^=aLx!>}`OK0n=nOs$HZdUs^xPs6(=}xwE4rk_OdW8#ifyP86NY3=wR^%C2`K$p#F9NtE%67hdmokxIH0+g#u+0 zc9}XvwWW&ytS6+Q^~mT7jC!=lqrXDspcsIue2lL5ViREyqT<7b$%@vuWU!zv`U$u=1P{i$dW6d_4T{EvGaE5h!(TAP(>7+ zhFJPO8kkOtDq|f*gF*EF06A&1HIS?CqCt<`p~O(Hw`#pR#FpviTzIPt{KR@KPG6J_ zC0zAYE?~(%T$B6`58TdN`yb+`tGO=;1K> zO!q%V2>~w!l(43U{oonMuMaoTLJoK2NJkGxj?t+?4mf&osfTquOAdF9kyt4# zH;ic< zRS~^NSN?1XQR%wy-Z&1{g|F0wS8oR<`jw;SFy11!6R|1iesGFLe7jt6d5XFiX>dY& znZ_Z|uc+#xF8&Bm8^h86CWayVJ9-t?1tpC^?_a>qX`hTn0+aPGHW>PP2DtKcPP zgV;d~F;pF7>Tp9BTNr(1q;5>Pf8m%#EB)T!^t8t5ef=xS@c_y6)YH(U z4bun2#g_@I(E@)4YR!8MuGd78h5Ow423tDN!rR(h)NwoNdrs}~OtJpG5GW4=Xo zXQBwXdHwQW_NO+PmS{2zH?Miefj6&fqo?Vc*H9o1eq^p*-<^E*8f1AdS0A{8pbw`i zk+)aB5dra6r9Sk%PCP~8i`Dl$K+X=sUg!6uG?6ndacp!*Vmf2o`cKFc<`Rwgat#QW zCpzmAatzQizUqa7)RFT$AhgdI1=Fo%uzXy-ATc$Tk3?~oO_Wv0d4C@5;ef*G=MqzH zAZ%2(0Z1s^U!@*}Uwq6bjdyue978ej3U@;{u>MIH?tes|seHB=xJ!;4Y(voewR#BS z8ZyiUT!&u(wuCjjv)DuK>I)Df0uj7RT>z?p!f0p!BoP7J4J(*-fkRgFY%SIx4c!v3 zM)D{+A(D^en$7w!k70cehSmcNjio+?@sd>J(@fKqMRrHv$(Y1|^S+Tu^g!^pW)erz zi6CzxIwc*#B)l;uanBZ^Ny8*QL=w~_0d+EoWB)|h@f$3W)qFwD!6fcQZi!jRy!tS_ z)f4zhcjT92a@nu$6!u*2HDL zSI{;^9q3aXM)7JTalpj#*-bGao*h3Y++UnzN3r>Qa=rlc{r(^fbAn^h{UO$f;2&lg z{R$u_LalcGu&XL{;S-q9A*cJRn(U&am3)DT1N)+w14$YttEvuJ32FQo-s-3Lam*iD zeqQzAIxNYHf?vlGpq1tr70?5%&t0kE<#j z+h*&3M12oF33>Y;RAKnVCkK3yzU6M3dKLRjp)}cJ&5LwGkH-0lq5lr_a*`pC#&siy zz{@X5$DipK0ud|$qa)M;l*i8T#qF8o4}_Pj70?J+Yu<;LaSFd5P(pnI(-L>6vMtu5 zRZ) z>~qq9a_nsC7flEPP>ksM^thl#bsFU!2p`0tr0Cz6lQG+OrXF zCe{Kcz8zpM+8jw`Gm;03k`J4>X3J&1%)?tZ14f%*;i8{aI%$5X2yE3%*31 zHS#XLy!PGYoacsMf*6c`!qn!jUPuJMc~2<1Ee3a!i?zO~1(;Eb??y&w8!SeH`dQ!P zci;4D!oiv}=2|zmc+RE1Su?>n#J{m-IK1a0FEm#_e9B6_W+c9lsW}0*4=nSQDint1 zbTJ(5$2nf4q|3Kay(+k+5*{7&IWWzBq+a-p!_-}UKSq7g(MA|70K3O(ycEWaj({wO zf@Y-lJ5fT>(dS`4cmbk%L;5A)P?*^1sK-gcXsDVZi7>AYvi(!t8d|z> zYEPe&W7Qc3sjI%|astKPkJZnZE>xqdQ6mqL>BW~m$uLc}Sptj&$zf~&y%+=c$KYVk znjYuD0d_iTkSmty(pS}&Avp3|l44A8WbikZTD6PCx%`(rb2?Mm4#-&mf$bxPn$U?WMMY;`{sqdjg9Ef*7Hdc|({Aa3XdqbMyut!W{bXU)XC|}p41YevX zAH;yxVYg~JTm=I3hBmLGqqSR=i%lT3x(ab3Jg7qbv6i9@lEElQujOm*26kBQk~y%+ zF{Ra$mB7&0>LobtX)Jy|lViEAE1Ck^j9MtD^6q@uvQ1wD?wUK;XF3kF^>FnA>TZ%*&U<~Od6-NN_*IGNN&f;& z4p6|tOtCjP&IlU^R}I3_SWqd+*z3M!d>1^Kl)={w0=+OEliCq)HkvwOZamE9wuVHc zsqwgkA4(#yBT`e#hJX%56qXHW;TOp3%%EIiCjcD0A{HtJ2FLh_BYSAQ^{+XLHKrhd z7xS;zkkRm2Dv-3cJ`KE5dw7IY^z?KRy{kRlr>{0koj?G&o^8 zaMjgqU|iS3 z3k|VNBK(t?@DCt!ur#t0I~kH!N!l0(?cJ03*XRr_2W2Td>4(}qZgxOdG2vaEGwMpe~ONO2rGQGQD@i7ycBym#?uEJFgQZf z+5N0Pdy2=4TRP@M)geT^jX7P^5wThw{4TOkFbjYR@&uPuioOb57B6YcIo+X0G#O1EpqrHie zSBn?jHQ32Zaqy@GOEWUh_^CNq=2Ts(N0VwHo%}{Qz@DwW3_A>rNgxDTU2jptkyv%o zIV48+A5b4)1(A@(|FY`F1P2h%u>#eTP@dYZ!<@CKp1^=#NI-0qxT_&4Y*!n!Z)~L0 zVEfq~;ScvWqK2j(04eb#DQ>HSTRyx0kv%ZTst4|?VkZ3~B9r+{gzS|^G?WS*=m9@{ zG-;1$zJ?d}acV6H4T~);)=M>Vi02?pa;cvqCoIC5Su1iu-Z$vH0}n>`$II9DRXoX( zSzz=AYJshEohoA?!3JbBSj-rN7GiR2R}TcIgePc_h!+e&$yv6Hp6 zx$kJl6|!RJ=$F?*jG5-b;t%#+P=Xi2v-V4IOT(ezg0nu{Z$nNINURcPfxso`;XKE3 zB;H!oa2%@!l7j(Z3J&dP!RVic+!CXL;#<@#c&n@MV}v7)@e=C~{isMhvGmF$kooQ8 z2Pg>i8q;IE?Gza=PCdro!HZ>$Mf*pdVNXOrK3b3Q&ibjTct+=!@TLAqI-+28T~F(N z?5FC-5g=NOjcOdSk@E_+qQbpFt?Y_o%k9EcqjZLAGd$|x6+6B^(Xp2< zM_vVI_2jCGN~urQU|0&5=e?+|OwvK8u^E!SZ|V=81~y2C zq(gSYTfK;%SceFIyZOUfv_w39u=toaG#;w9)L9l)TQv@Cha00ezwN>xx%uy|LYM0l z-c3NF7=zHyWiu;0m%UJzBKZdokcHw=L3C-nXu%=IKa4(Q9b&))WN`C^C>kn7PNbUE zZt^Pj91n{1@siqU{T>uQNVT^>)uBBKxuD3BcmfDzzctujVfDzF$rry-jO`L5d#k3T zrknGH_tg^Q$TKq*G>e&rCY6R)oP~_^a{EezL#OF=^!&7*2b!vmb-f+FN<%RET&+QJEJy^-Z>7^!<1QP(|+ckSrdbVVj=URJn_F##`3~Y+ZNF_gITT z#uxGuf@xh{_3iDCxVB+^Dl6fl%Jr_yz`&x)8+*Cj)fXWSKQjZT!RZYj&S6xKDVew? z4W{8K2@J|T0!aM8yVQl)PjZwQF&mZDhhav7?42zI1~pP5*d*~5#C368g?$ujAP6QP z&bbX=Xpmwwa@d2U8&e`-28v-lUJm{kEg9JYFE&jv#%R22NuND&(8A7lFdsDtWydI0 ze+a#3`!L?%-{V;X<4>qyyL{*ZZn&FKBG8!xM%aX!{DfP77@E*)?mMspizlB5GZ81T z6mxBdh%%Jcz+2sjpF{-X^e(gl`(7y35#?XSUsK*IUplMMjP3Kcs=~t^B1#pu!CUb2svR6 z>K)Yw2^r0#f~D9}mxzKw%eT>t87#5Uidk7ncy*dsFe|Y4pI~^Q4KjI{kJ8r5W*+Nc zysRNT$#_|x(%@Po(zeTXW0OLAj?w2(Q>W>&25q`*st9aj_t#jSm@mUEaau2%d90)H zvWD~|<7ItWptW7L%UY0(?$V~q8eqDt9Hz^@3=D|o0Eu)9_J|JK1XE_PSN5g9mk)1s z5uk+S1JxVbx#+u$$Y!JA0$ezJUww@cOAlZ0a@?Q8oFzufU?L;gXc>-^5)9OGftkfE zBF-~a9de128JWuvbs5M4d`^7_Zeo}Wgs|2i!}Gf?wg7s3hZWP&GtN zzv`@}FmK#HNzZ!I(g#G1ST9Ts9w}ZwzEGvn{m{_=%#%wK!X8;I6E^ez1IerWqp!b=z$X$2c^ANnd`w2Uj# zEbvQ&(bTR8W+H^|B@Fs^MKCD=-bom(?26P1`~+d}Nmpc(z>g9JV|PU=1%7}qg`bgC z0^dUziw9Ta8i8*o4Bq03%og}o!dO$fB839qLKxGDE8-FO`-CyUx+3WUuO^Juiz|{Q z@U?`w5Q(@1UO*VjT33WS9>QwifR3H2Aa6Jea0 zp#B0MM_x;C+=BWG{4rr1jG_Jle?S-q`l!Fa?-1rzGt!2s3jI$wop7tbuM$3waI?TK z5k8-Ay})}34<@`#;GKkr5Uv&Y3Bp4OZxZ-X!ovtx3j6@!;e=NSd=KFfgs&0!cETBi zXA68Q;R^^C3VaLUk%T=0f1fahCF(EmYQm!lrwM#5;Y`9Vffo=SL%0pIEBc?Xhj6RF za|mY4CfGU0lG_Yx-Oifj{jC*i4tYXyFSa53Rc0zXQ48sSQT zA0RxP@G61tA?zi5jlj1P_7R>f@U4Vr5H1w>7Qz=3_6Yoa!X<>$1zt_KlyI8B*Akvd z*d_1+!m|js9hd$mJezQ(nn_zJ?S1ny7xO2XF&+>`Ks5S}gYsf4d0TqtlS!dDaa2z(s8X32L5rvrxj z|K%Si6#gA2r^la#&m^aJz~?2wuO-3$B>2`Oc(;Mc@c74xgMaAym2^?AU_b`#{Nseg zzcg}Q{24SMrqr;>)&=G(e^79e{K)wEhe0WS$HLHa(Q)f7bg_{wV7~EApEoS z=7h?>!@MnwKTfFp`_jN|rv5enQ;W1mvx$h2C9tiRPW}JxcjH8jXL1R{c({+hT;onN z?p)(m7UH?*W87iJ%{A^cly!cY|^7GVVjh-C^7Y;~q5bd&WI#+-@1B zeB%x?Zmw~s8F#L6D~!9=xEqXnmvJ95?hfNN826xY-!tw}<94HBGwW~MVaClh?lj}h zHExA**BW<&aqlwjL&n`<+y>(wH12!Gl|sMaC)BgkGuM`-=dJ$19i4jpsW$tpPoH`3 z$m3)CeKc|OD?^^V_VIYqto(|KvE}&-$L4yn%gXc0^TuW6XXh+fFfKQ%%u}{#fhTud zSyn|>zGrN1?)b8ta?hf?@#T3Hxt_d=aoIU#3*$Nc`|E#x3rr3yUAE}5^5u9R#9zK9 zbJ4P8|MTng?;8BO1^(Rv|5^(aju@OVw|w=Qr7KsY^HGw_?ChM(+^n&lobYtE4Zd^`wjwgFuPJYh#aoOby%02m>?6GAF%Ex>17ggkBEh^8=@p$qV5Q9Zy^A;}5 z%N{?veC)W2(MUOVG~$dKomD(y<1=$IJ)_44 z1j@QGD=*VCE<^j`#Bb?}>&jP8NG~awnx5^+%*xDjEnHT%VsZLfgSPQ>WiDN@)IYy$ z_3E-4T$vRB?6<#so!^zYcqM)oqDti}G?ck&89tzvT&BO6+?DBHux5=bQ+^iRuwspW zwJWoH$^44dWy{OwFIfa=@rppEDSAG@r7Qg9t5>-)7nS+Tm|5oPl_papPl$~;$nb{c z;yHg&`GUY=Jh5|~##P5$QnqG^q+Y#p{;IM?il(PqR;^mHa*aQ8(SpUUAtjem?(pCGTZ`0OLD~INtNF05-KW?>^lHmOF4rM^a!+N` ze`!9fn;wrUUld$A|9T=rXZmx@e$2WQbUFW$(?5Vf?%izpc0Xs`db3~SK8v{9{g&P5 zZH6EBPBy*WUs<;q;rz4Z+IZGoj4+oG9?)-?kx%?hlg$2NWDT|JndBy~TeNC3pC*E8^QrnmBjFHL$YhdEh#3Pzpj)6Bj+t+y_})e2Hf2FdtO zgFm~dvq7xQ?62DT>-0(8*P;C={-6lPu#K2}<)5Xu`LYxqR@n4besj!}??ssN&!)3} zH{yeN*z|=)E>bu^wp{Dle0Z?nEdOpJ@49=SE`K({ zoqsmH^&5>3XL>6yQzm^3F_R!U{fqFYpT%Dl>T*@OcKaFmm`N^W*>Adx?kvA;wocb( z(*My843o&#+vd?JDSguwI(?IokJxzDO~#*sY$yK7=`H^~S$eW7mgun&jBCrMAlvz; qvD0bjp9v4!cFuVHjeKRAO<)3(K-Z)2PxfQfYm9s(xxJFd|9=6s%WcKtgVe*sMac|zfxbd{QCMVS`c2b@6nd0RBg3ddH-|nOcJSY!hHAp{@;Gi zx#ygO;TPHP%j~XOiwC~D>i3lVoT3tPvudK%030Uxk3CPU@;yFCv_#0mM5nBKiUXd9 zi9C?|2Z&Y}#Y)&%!zQ`XzXGTT-_^$LtiX553U(M@8@^W>KVp;E)y7FyQuH>$iO)iO z6|z!MBhk`kA_iH7xv^|j_2^RNR@NSfl};ys-wt{_@a`wc;5A=#o}E4YlgiAx<&=tmd(L!(pjtn*mvKgjNR}nnSbi?yWt+ zD`BS$p>T`d04?7x{7|0DqYR-9izC(qAvO~v;PSilSq8T(s=frG-~D-J0gBMv`fP|^ zXRJjSdHf#!ID+^s`W(n%Czy!wXG>mUu2B2@KD|{i-pVGwS8o$+nm>&{BV5u=mm)Cp zpfKyo)#rl`yS219M#dGeRyyH`p>$QfaMEuJPV@S`p9c$3Nh}QT_kOFsNGQ>O9Syio zJceNgs9h?&MR`9Kxor9z<34N6#Z@&9W8_nXW%x7rQDK~E7IqC7QCJ|T1;bZr!B8lE znGm!5S(>I>h${VA{5Tx+#en@;`W1M@u#jBguof(=3dWX#4?f@z>B|Hd(gN9N3=e&| zkR4hm5YU1Fzi807+E&>vW~_2)VNI5qDBGXSPmIOSPEK4qjG!oFsl?f-YO;CDI4G;p zR~RR4^9okN`1v*kW29tk6GNqJ#bUJB^{*Ha`#@q9Or{X!VnxkP`+j zThoJB8cOyqtP@uLYYeYN#~b9@hBw5b6#8~yonmeBdC2`pd590nXBK}$KC}5WMc_Gn zP(E|{&*U?Ym#PBK=R@L|C@a+(8c$djGLb}84q7&l`1bkBA9JnQ9^tT zswtQGdB{fnu`8u4>fklwvZ#kI8ka>~d|OgZAb=>4-#2a`3gx5YvM89pIWCLBxoRIv zDC*~taapv%w~Wi88-8$H7ESSYO}S8XAe!UZj>R`FQC~ze$N;dY20s`jgCQ!8*ewEH^!hfl{X;%dr~$IyLKo3 zDcBWgfmb?{oPL!M@A#tlPD`aO?<8^sbitER?r#y)i_&+Hy z=sxi(7CB$&g$nP0EnrhLOL}pMNXUI6iUf}GDqF>kiSDR)F|3NtRf{R_30M)J7)yk_Z6&aOQeFK1i zU-Qe9;h-7s6F8)5KPUp3<%KPpAGME{7C zU^BpyEj&*d5?l=T3>R;QIqwCDU2p91)dlYZs|;4NRS>)*hN(@WoM zT<|Rl-XO$~G9(j74=ofMGS;Q#CgeGn>(Aw{i4gL5%hhtsGcT9FI{^%7IkBw>Bo-|{ zFBI~8%1s_W(2Vt&jVs85C(8qQ`~vKfH7>}&(B#Fo!BD?Z5HXj=$7!_*`RXr*Iyt>^ z=D!Cyq>H6uPV62mSUIu$n6R8K6!}`dd3O+?tuJs4-pSF5(LS#BPFR>b==6 zyo$AA7_S;L{sZm{$`D?5OZo2J(3)=qO0;5r;7eUgwbG`m7-mo_Keu83vBh^7-_=5iTSUqHR|ue04hakW zRv@w41aV*+2n7oG+Y_{7Z>=^N3wN?MS;K_3EWU8z0_rRdesK_DxH?qkCNE4?{;COu zR%s=o(AG+z2ikZw7Mo7`J~nHJr^6?Y!n3agjm;s2Cbnf2WExAWH&SYGx>fvn8Q9( zn_&+7Onzh>G@EJ62sCNaW4lF|S&ciz9yBZVbx7uVYSx;=16nix)x_+<$~AKA%B#G_ zD}nUIa;9hdXHOWw*{PJXCyd{0^M$($(c^nW!b|*@=y!t&+qC9DQ>6!~4WlHZEOB~b zI_&c@oz@_Sy~!qS2u)nr0Gc@Rg<09=t+d78!q1Jh+M-pPZ+r_^L#aV&NmbawGbexz z!ZP9Vx0tnxH@)#*jLP^+7f&uqk-l*}efhr>6qut;*XE?wX<>P?wunE%OUE)sQnf{R z{TNsgNfj62%f`?Mx|JJOBw3qrUFMX;H&9`DD2CI+Rj#8_SL{BJz+5d~E7a!3_Chc> z_&5S#llkHXU+LlxiUgw1u}x5H_C5c&klgE!r1Z(tt|bfhauaiEPCd~D&n9_sa`MZB z>VA=4oM@FB&Eu?9Yn538ZOV|?AhkC0TntPN1}0a|)TU~a9|+9TTD5s0{Q)@hwFslN z@faRvPHmq4AQa=9@7U`-)wnyDnb;!oiWbb%>7A6Atax&Es$v)41YUDZI7G2Q+5&FP z9S)h7haE7Gmj})5KJh&)wZbcszv|5Q&*wiEq2yjRUvrtWF`p;i7)QA^XUrmbYmjp` zpO=m+d(PoU#33IlQ5;=cx$ym)x>0E)vlus~a2&f!9B;TP^=IWPev%1T|KoLgE*X z+ahgII72U#m&blsm_;vgpWJGe`4f?Fh0&UI9ouI-owax&F5Ti+xCr8l&tGjsndr}s z&Ac3P3C=L=WAqAcZCFRN4u=tr1Njm76R`gcw(YG(Bs-9^3#xD6y9^rPIYZg%!Z4}& zH;umR+?5X@SJ-WdiKiT^Da%dai(rAGXNjsFQ>)aB%8)9L8~r9g$-`1^K8kt#tTRq# zPr2kpq$$NsAdE6!#!+0_KEQkXu8E5}-$yV3>=U z|AD0a5?L$xM(LYl6>;)B&9M2aMNlr0pU{5bzZ1VO({0jnk=2rLxhTdyQhaoa=a9os zzjdE?+Gv+_I47NdX0f<}^c&AkN01%?wb4V=48G01(-EitznM2UC?P>fw-N5ekir~$&@wjW=9UyNH2{E?WkK6^fhVkm-tOy5#bK!VWohMdX72@ z64YeDg9e>P;qW9XDxmwJD4|c)<3Khz4l5Nj(|g?Iqn++9w~uZB4N|pTjKoz)c zA0g;^jG~Qx>iif>e6sURv0!Prq)h%{EuBiR&YQqv!tt*Qx+W@TWG9uSQ=v)}#b4u0 zNxUIZa^goVPN(z6vAju%5hdGVqcm$iXu7iq^iEe5Xp3bZ65s8qu`pWB>OuF?B@-;$ zz~AD!9QZ6&1$qY){7Y;p==*Fn=;v&o!bnpDEs?ZASqt2W(PWfM!k#B6ACIllbT3_N znL%~RfF(%ZVVgnU1hvuMZTpm^MWXXPXmK0t4Jqx+I)e#SXrHNbtW|EYDD=FP3~Hub z7M0SA;YJ4CNDo=;^t+@KVu$dT&~OQkWunu}>$Rb(zlhxe(yuKQkUmRFLg%7~8^(1m zdRj_*vi{_}U2)S-rDPW4ro&R&PS3D!S={tyQaT06OMjVQnMNN9DM8!m0rr#ydog;u z1+S(_MLNOXjonJYWQaeZ-YK+>7fqdU zVnRUi%7eOSG9}J9)2RkRI+l%~5lI(Gx=hkFl6HbJ1QE_7pqbcA&p31G0BDhD0^4x&=LMyBGAY9zh>=-b;sI{~)b_<)h$#pgsY*PtyCaat_hWlGb|!e_Z`OZDM~1 zvV}e7eU?TT^CX_7+gOj~5ba?%cwYejiQ_Qn-$1vpqv|W*e`tA)!m{2l)j4w50k+kC z2wI1yggry)>SQ*ZhS5edU8l}wWz??DWzDjsvZ_N;3wYeTXfz`fqt;8nYz=WK)IM3;9kC%aSK1IirfK;Cr- zue0`jpwm1D7-JVKPlC2OMx|vs9byM9KSNnt9EU+Scy@q_sX5A)c>9#Z<+8KOWoKL1 z^&S!K4b-@Wt#&7rE$mM3&58(dyCOznH|P;`e;PygEJEMwycaeO&uerRHtFzs+PhC$ zmay~eQuQI_6ISYZOkv6i_ji?SrQP$KqABNWo7ri$#`U@~IiVa=OV|htdc~0LMY0iA z;{6>$tj46Qki|Wdr1!i3q%;R1b=~TodE|Uv+8M{Hn zvQ^gEPpQjTzVnZWyenZpr;4HYP#s}|-cQxr*r@$Ayd+|_JaV=?a<)3z>&j;4r((*m z6w%jcr*#sorAus6$xWk{sT82+L38PC&{8@BT1jQrsZ@u*eom!XG!L|umV;g<@pjNw z>ZZgy)=nCtGuCbh?^t`lSK5XtP6w>RNb@+Tz_&~Ku*4sh{G*b-D3KQ>e?-#Li15Dk z50WSLD0~?kqED?Bww)Sm7Ip`aGVlkibA-0d+9rjEnMip+n!YIcm%!U>oyrjTZ3jR< zwK8>x3T+IdBmN&NjQ`KNf$qXabJW;hTD&U2YFQ)Oz&0x(T~ceF%};! ztH3e7g7|)_kR@gEZp_I zfzpau$8I$?FZpq{; zD}IsL_(|m{VvCpJn5XCQeHrucvT$To~Whz_qZ{&!_b;_KJ!IN3V4Z^`I$pMEq?|-m#*0bKKTB(AjZ$th;kV$BO92{*Ip5KwosAgAewt z@7)mXm^;|Jp+_h-bPh!0RW(fwwLLY{8|&-qdZL{T(az~JnxYLojn%a^(`!0wYwM%+ zb<=AbH`b!)>S(lneP?ZVceJLv2fFHpo@jkXtn0cC9^DYdin=KH)c&3>YF`+st)D^3 z`lrQ{^`q&l?1S~rJ{`c}_Z zm(*k1tEEmXt_Cc*CYlc3{C3fL{4|Jd5t?q`jgm9@D9wPb8fZNhY$N<@z}Fa$u8pui z8Sk#0r+Qspqq5^t<~0U8BA`F&XwbZ_B7B#Oxy>3hoipL1uDfWFcB-)}qL za=vrVIT=R2&qj{3yKXD${@=g9PpQxEXa;#%4beIXT$ZqL^yvD)?+1uhN`8ds=Z?{$ zu#qLR5(FI`c~`Kk#!0$Ps*Xd^ecSu`;&VJi}%bMh79l z=Q4aXa?smNa66ZXfw#fjZL{%h;kyc!aj{vo0r*37a>y* zcSSdZUEIZ;2m9(yiwvS&+7JTUjaq2MHQ91p&AmoeWXqnz&8A~_jn7?$?UYu3)2<9}MSY{|wd{Y`{S&3Z( zLM9fD@JQ@R9*IW#7fUufl+C$eC#nc#i4F@hKI3C@-7|?hj}C%k_DQN=oOu^V->3kYO}3CIiVbJV#0q;s&UI<1ci;I z1Lvf($q_Rrg(@3kg_-YMoR7unIg_Uqd@`(Ijk{I^O6#6@nO^J#x{s-d38pX>zM3z3m%X~!TBro$4@f4qg1DT+}Ymjyz6BYMP z@-kuZ-AP_1F5a2sW&WbXIWb(KKyiz>S@&uZ|1I9Y@b$1(EZ)SKJlS^S{Y=FU>=dh%k^OS{HJ|R6Sr4CElIU#*U zO5X$UfY>-8{jQY0Wl6V8NPi%uFI&>yw6yYy=3`PcKOlBb$l98}Vwv4LAzI%2B}+6m zA=+562T^~fcvJHpOpZ%(1xgSZ?i6Kglx*Tc_T@Ay-khMiJn6B&nxK|>)3R67l;v-U zFRi$8g1XC>ObdUU7Dl|ML6h2AOGV5N1H1`(Sl76{q;gs**ToAETJSxoz7pvgR{@5D za%B!lG_D3;S!8UIrTh$5De-3Ti7kMr_jE79BwcbJzwvXBgq@muf*Ho^Xy;BN4n<{9 z8gRXpSPCdE7F5XzJC`642NZj z{ntw}6C~-e#oCah5<>cph7q+Akmxsm6sVtj6-aF;Dre+YkXAL;T*yI|N*j{9wLG-+ z!~nd=L(CX7TQb^{pOpbWVjkuh4~ToDrCdGX>3q6+ zFIsj&DV~R>$Vdri5kNY@seJe} zc3*6;&)m(clZ&OdSSTiLm%daZV!YgHL~P1kAg1Pfcp6p!FXI)T=-8I@{+lq`z(t^< zatSY+EUtgQ%vX%UC9fEgV~j6ClDI_zZoL)JaK3n0YLhAT#4F2Zp_jAxERJ5bF30*t z>Sr%X9eX)dm7&ThxmWTTR_>Kka<5dmhYfC6?_F8SC-~fnyktt}WlBb>ndb3O z3A^3=Luls8PfdRvuWLDkdl0V^t&z{_Pd%@18I%U~V(rBS`^*htBPrK(xQREl@Lh!%B$a*GV9 z!sJ-HT*VHx#f2C0g(_-zkv1fEL%zs58N*eP@T`hCyo%3yAiS6_} z^>|n@`C{WfC{o)jsEN0^%KUL8D|v~GD@(8rts`kRmGQfhR~dUmLk@g6wYv9ctzMBEIm^$soJ9;+KmMADp;iq|IT)MZ2UY>9Yh zQla`M%5vdLZBQx)ANQqmI1Y`KgU@?9h8(n^3`|_h*Rbxa55k8pXzDC)uW0H=cbNcmH+{~5OGn;^_i$>js*1(}dNfNuwA>8DZK z>%q*~4E&|-DX;2>E8ToHhp&AQe9UW4$fugf^3@h|p)g-F5W-Xbbz7w^vm$C!XOA&u z-&PxuW}QVF%-r1aOa2uBXW|IXMEYO#gYFy27jr!FSjgb892-Z!B7>B-s8s>q6f_rxwT2%g}K59f#vE_BGl(I! zP7gZfI(52V?{Mi9RQS3>(tlETjgqWZbhR4G9n#M^Z#``OXqVWhB+t_xk50cLsU1<4 zmpaCiZV&BbA7}ye<(Ml(&v@T}dcPX*wi^7becTwZOlPlfq5%81ZuThJ4szj!77r$^!?{vcBM1AU++BlMAH7=c^dZ@5FW z!XxovmrUYgn{;}@c|z0auusx|c1pZk;XxH9fGTszP>FITwRd@C677CTyOq4#FFkB? zOPppuq2<#vzT>WZs<8u;wAy_frcWT3e7XUO8T7vGBqZIg6Iv-{`=9d!sN37>4bXaE zgvy+94mKm0haUBO0H?i}Kb`J%zlonUboVc0Z>d8eQ@^AIDF^*;0gYDU|2TA#bCQh} z1Suasj`U(hNH$3FE$SsV{<9SY>A%eI^s?lL7PRZ6J2HXqx}v~7Pcg93ZX$5PXV@8a zuo&<@nmxsGF6bW5LdfT^V&IpUq>redqhJw+kx4hwA-j`)mExl8S-duX0f+ZcNkPX`P3J;zqMyao?bM=|;s_$$04&#obKD*kQYu4yU-^g7ed{DV7=ZyyTK}Gu_XQ z*yW4MFrjIb;Ut~FHl(2$@;B2yrTw`#7-V!9TNtBXLY~GSq4OI4gq4!1^fa5D`tbW} zABsBRX2gLyQjUJ z*aCZy`q&S>5>M)ffWP)h9Cja~7hr#wHp21<=%;N*frk|y#L9Vrh7^X7&yUPA7?PDAK$rmW5a*0v7OJ}#Ue&@3^%FcQN>}krdWwF_`2`SE_K3h2}rq#A8 zHczGd7M-&%WN*?c9iwIZ-voW?*Z*C*aR``PQMAst^S*oeSnQC%RjPq+ac|5jC^0w z&av-!zp8Pq#`lco*51~8nDl%=JH;A3KhTP_S8a0Cd7qr)-3ULPWM2PI;BF=Qu}0<5akGQExFRa)>ezjPe4JwJ-~9;K3f}GfkwZki0BM9>lP#WE=@DU|{MA1hS9#@j%ihfz)I9$Br_`RZu9l=<}hUk5Vo!v}T zx}DttNipdA9G5EXB1J#KWW-0MWu4==Voreh*b&!;$gMvLeBZ%rLln``6YPU7yrdgw z58XvC(YMXN%`8fur+ik-Hn1M;P3>LnBdySOh3!?_Dchxka{sfBNSyj<#omekCqCzF z+p%|EEPGR#TxfSp-mfki`*0FBwhM)tefTpBi#{XBlXORCw}?$QUfvFWo)9Aj5keaq%t zyz_=Wk=SWE>TWP^saxb+I4DH#&H?j{x&;A$4<%@TcH)kJAJ@j_*5~_+@2IA2knVu2 zngk6(EA;~0=OGtZHPvQ(UKsya2h^%@G}q^{>ap=ln=*n;e|kA*$)?K|J@wwTYyR=l z+3%ais(*6?0=ScyAFX5Q9qnMpCf7{U!y~1*-b}|pzR&;V&M3<=vpbvF zbhD$gI#Q&CM()wVL8fV`5qK1thdP(AS?1}^8dt4eC>__1Wvxjtqo<|2sW)C%UmNdf zsA=qu_tbPZ#pBJhw>EUw$LnXebV1wG+tqA7x%Sn1`c~Zj0kP$)t-ZxA9;V-gT99@f9S^M!qUgK)k?GKZHXPd)nahx&R$Vn zwLQ@t-(KCnEiQVidlCaXdk3UyYv1IH*++k4jZ?%1_GKG54=8SiOqsjKO&kH;J9 zwszOWx3)CY_qKG^)W#dS;`Q~tU0rqY`mH^UvzxnTH#hZm#k*T_yP9enn`)aH>S`Mr z>Z=o7*H(+(?Y;5--fDBl`rNuyde?4FrH3j9`aW3xjUBgsw8mc$t@h%DOMglvT{v14 zVNvts`Wp0^%=7DiG8VXUiEU*ME*Ra^gvAr5I_y-nc=k{O)lehklCDwuIOt}YOpF diff --git a/build-tools/BuildTemplates.exe b/build-tools/BuildTemplates.exe index dd104b61fde6d5a5a05930377468e78f0287daca..eb7b1362cb03ab83e5cdaa2830116040e4da264f 100644 GIT binary patch delta 97 zcmZqp!r1^sEsR^3+{%588H^Yb8Il;x8B!T68BBm|2u@;11445KV+ISLOfpd10?dN2 dQyI*Fat1(AQ-&0vcoKslkZrhqaye5j696N~6P*A6 delta 97 zcmZqp!r1^sEsR^3+{%3|7?K&%7|a+_84MVb7>t2DBL)iwL!d|sSlpZ;5h!j5<)t#1 e0C|Z(Ri+G=rSP*OC@2oVM$c?C0yl}Q1`G)-|! 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 s=by$-r=fo)JZ#%JA+5lleU^~~vkksWm zX;+!xunts4*&41;S41^nh^G)@q2tjB+6gvb1S>>(#FPHOdH!+*>}!O(o&|~ z>VE(GKmUL3Ilt5K%f~N2xL-Tc6WTvQ_ zJw)4WKX0-7Su=mZZ-0|Dv6J@0?6SHeh>_1lcsaT0%>cqHi5O(O!k(%R+grHC{$%&@ zK*2+#iJGw@NfvjH3DC;ouE%7sepiXdLdY&-E|E7B@L2k+u)t%@n-wA+t9Djs4E74H zS6zNrxyyiyH{=m{nLWsg)G9C)2RBZ;XoXoHxpI*EG*yCfGDKch7b+5-G06n0grl)q zv6{y~Q(k3|-a7wI=E4nMeDTF}P_eMu;ic@;VK-JGAgf^yM6O!Kx`_&!$b)E$yRS8B zqjB%+$-`fSQxFO*6(TrLo7c&YdpN*{Uq|O9UGcq1JagLYOLDm7Db=XKWAJ zwSViXwm)^PU9QqkJ8%txhBvG zQ7)_}X2V615s_(xb@YtRHKRLbzpcGfI01|GA|TrZSFg*!7x9K%uAp1(dU3g{?mir$ zYD4BPcUeE5&E(RZOfIX4%@pjid}s0ZoW;L>h8Ht&pD|iG-Koo1g8U^s7XxX`EthC5 z;3#W_Vw=EvtfW0Cdbg^Q)6%9&wswIP)?pWlkO!&QIssO<7HZRqbFwx-KbdDE}xcG@s7AMxWzgQqi!Y9nrf;-1UFlgU(qeMURApHWGdM_{F?rUU-^vA zHatI#oH-%f2qVZ+2Z=ie?95T&(?^9{pW1aruU}G(i&9Q}3}Xv)-Wcu@=nanAz%>d^ zOGYn}E}`16i<)t4apQeFFTK4yFN2FkOT6nKcFDfbyQm-y+%r}xiIa?T0~aLt&YN)I zd1)x&H%M}N9&R?{^BTR*;@sGXmGr#4TrB~8b{1&5x?6f)j-s>1f$q!!$Et2#<>?sH z^KwR9-BPQoSrFTd%jmFKIS*nhAzKUG*x^lbHC@dzJzr!`dFQQA)vL0LjLk6ru7WQ< zt*UdHF);P97iYJ#eVcUq6*|{Sp-NR)> z*GPNcHu_!#k<8K1+qZrV=tK75;&xVQzgpZ;nCr$a;}P_c>rx@;3QgksJSxtk-N-9w zjq8XeD6VaF3wqimaa7?Q3eQvea>f5jWjMuU21D<5d+F~Y3jR0j05TXV^Y7i#x|DY# z&waeR01{AP?`cP2IHC;0Dn*Hqu4%V)CFOHW1z+isNuSZAK1&iWQa-)l%cw$S*rha^ zP((Sk3c0+_btdR7P2!K3jB1CE&r7yDDk^COYMf8~`f1x&TET*LW2qmfs~G*itzCnV zf3r2pt3y52=xMz9Wr7a?@wi(jhD^=wPE` zJF2A5n;hvanm{4l_(_miKBf@!(72MO*r+aW&tAY^2t-qCoY%=)hY|}9VBZ@_Wi?-mgE z0zcN~P@GM&2f^FyG2kOe5o0DlhTwjc)UO{y>;e4*QoqFRro(ht{~7Qp?WBsruCNn{d7p>_IND%q!f-rs!#23ry*Cz&~pt_5}S{?*>+jRqSQNWoe35v0m^o?6;3y z%nLFs$pU%_~k`z zRta6WHyXKU8y&@4!fq#n9imGnZ}mOEN{7u{F0sSB#7vo)-IFa9&$zvr!69>dX4uS* z+JCIxUFi1cbGnM!nZnLx8M8cuW!b|u>q zZMN6n7y00hTfaqY(-yf=&*I(RgI5hF2zX?__(h>+ysP4P8|*v$^@W#iX;|p3TCwgB z_x>mS+$$~8ZT0ejzMN_02GG`)QEPB?cp$!O*P6sgVqjx7l^7b>ZVvAn7|f3BG)D%k z(VgkckU6kAyEB&^GS_4hL)jgnRB~BYdoq?zn32v%*c=Q;W9_MESF*ExFxt^!4i2^@ z+LtAx9gXQ&qAik2Mq=SKAQEm5r&8f`XZt`ldF_B@4w;FZIbd(BuZc`odP~)GrLy5` zQ{SfdpE|hwjTi4eI)SEUo<}8LH{q}3N8}4sTK?Y+-0SXb}1&W@Ccd?C&gb^<* z_14vOk65F*k@Y*%S=(W@^;RjO<-mYQJl&MMNmDuE(*#+G?5CkD9p= tb8w55*^PbOVdiEcThD~b1zKlUK&rpq<{C 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 diff --git a/build-tools/ConsoleDialog.exe b/build-tools/ConsoleDialog.exe index 67de680ee0ce0051aaaa8ee1d166af9c10ae028c..21bbdd398b1735e5cb474ba1996ed52b56fd7cf7 100644 GIT binary patch delta 97 zcmZqp!r1^sEsR^3jLLnD8H^Yb8Il;x8B!T68BBm|2u@;11445KV+ISLOfpd10?dN2 dQyI*Fat1(AQ-&0vcoKslkZriVzMQF%2>=|_6Nvx- delta 97 zcmZqp!r1^sEsR^3jLLm27?K&%7|a+_84MVb7>t2DBL)iwL!d|sSlpZ;5h!j5<)t#1 e0C|Z(Ri+G=rSP*OC@2oVM$c?C0yl}Q1`G)-|! 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�r+E9OiPbG}QB-CHvPK0r0 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 AivR!s delta 6242 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 dr!trU<3{;)C-MO4;FB1S&*%TfC diff --git a/build-tools/ESBuild.runtimeconfig.json b/build-tools/ESBuild.runtimeconfig.json deleted file mode 100644 index 3b38c9acb..000000000 --- a/build-tools/ESBuild.runtimeconfig.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "runtimeOptions": { - "tfm": "net10.0", - "framework": { - "name": "Microsoft.NETCore.App", - "version": "10.0.0" - }, - "configProperties": { - "EntryPointFilePath": "/home/runner/work/GeoBlazor/GeoBlazor/build-scripts/ESBuild.cs", - "EntryPointFileDirectoryPath": "/home/runner/work/GeoBlazor/GeoBlazor/build-scripts", - "Microsoft.Extensions.DependencyInjection.VerifyOpenGenericServiceTrimmability": true, - "System.ComponentModel.DefaultValueAttribute.IsSupported": false, - "System.ComponentModel.Design.IDesignerHost.IsSupported": false, - "System.ComponentModel.TypeConverter.EnableUnsafeBinaryFormatterInDesigntimeLicenseContextSerialization": false, - "System.ComponentModel.TypeDescriptor.IsComObjectDescriptorSupported": false, - "System.Data.DataSet.XmlSerializationIsSupported": false, - "System.Diagnostics.Tracing.EventSource.IsSupported": false, - "System.Linq.Enumerable.IsSizeOptimized": true, - "System.Net.SocketsHttpHandler.Http3Support": false, - "System.Reflection.Metadata.MetadataUpdater.IsSupported": false, - "System.Resources.ResourceManager.AllowCustomResourceTypes": false, - "System.Resources.UseSystemResourceKeys": false, - "System.Runtime.CompilerServices.RuntimeFeature.IsDynamicCodeSupported": false, - "System.Runtime.InteropServices.BuiltInComInterop.IsSupported": false, - "System.Runtime.InteropServices.EnableConsumingManagedCodeFromNativeHosting": false, - "System.Runtime.InteropServices.EnableCppCLIHostActivation": false, - "System.Runtime.InteropServices.Marshalling.EnableGeneratedComInterfaceComImportInterop": false, - "System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization": false, - "System.StartupHookProvider.IsSupported": false, - "System.Text.Encoding.EnableUnsafeUTF7Encoding": false, - "System.Text.Json.JsonSerializer.IsReflectionEnabledByDefault": false, - "System.Threading.Thread.EnableAutoreleasePool": false, - "System.Linq.Expressions.CanEmitObjectArrayDelegate": false - } - } -} \ No newline at end of file diff --git a/build-tools/ESBuildClearLocks b/build-tools/ESBuildClearLocks deleted file mode 100755 index bb0a0a176259ff34bb597d605456f162b2b657c0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 78256 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 s=by$-r=fp3JZ#%J;|($N*w?H1vVh(QUCw| diff --git a/build-tools/ESBuildClearLocks.deps.json b/build-tools/ESBuildClearLocks.deps.json deleted file mode 100644 index 4bfbba92d..000000000 --- a/build-tools/ESBuildClearLocks.deps.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "runtimeTarget": { - "name": ".NETCoreApp,Version=v10.0", - "signature": "" - }, - "compilationOptions": {}, - "targets": { - ".NETCoreApp,Version=v10.0": { - "ESBuildClearLocks/1.0.0": { - "runtime": { - "ESBuildClearLocks.dll": {} - } - } - } - }, - "libraries": { - "ESBuildClearLocks/1.0.0": { - "type": "project", - "serviceable": false, - "sha512": "" - } - } -} \ No newline at end of file diff --git a/build-tools/ESBuildClearLocks.dll b/build-tools/ESBuildClearLocks.dll index 26bb90e7b044930a88355cc77fc2ddee11f83602..72c51f07eee8035c5ec031bed68b17d8e59d0e7a 100644 GIT binary patch delta 2467 zcmZvedu&tJ9ml`tJ{;_j#Ev02PQsD^jVUDfmDny}!->=KXiFeT8O2+BZQn41W1DNc zN?3*IQZXv%wCLw z?+2EX&V+$U-lJ^f`&b|QfImdM&d;&E-LFy?(YDd0K*!r_fCrrbBTbi$_pI{sapkKU zmMbXkZdE)!1=wJ<)>L~+Y@y6 zk)GssTOaG?0h^ok^D*1{>i1N!`Ly?VMEQkgLzgP}Bs-(E;9VNQh8t|JYJ;XOBmW*n zutjpCK}~or$>?)3`ljR;aivP(;}A)Ili@ccE9{Eu zz*@GJy3OJ$nz5NYG=M7jIrU|l)+Qzxki*;~hjGcGkE`FIX#-yqpi?a#&xxPYqQq*7 z9OuXF^K;sX>F zn+hEFoTRBM@Jq$9kp@yQUsr!o!%W4OgsOrgu8uGrPs`wfXF*G&0nbQPE7O1rQq7|k zcd-WQ#qfdVbTZ7+<}sw{c@=O~_-pns9SeplG`*7=gNtZGJJEslL?;50F=Ew;xD?wZ zcT4V*G>8mYBApH5N_s}Y$E4@H=|~Ntj%;f?CiAm8eHrR^=-2MiT_Z}$8`s5r4?ysJxF6f>BD~PRH_4L z#2z*PH}(@daD=!9^TZ8!kr={N;%5AjIEw4Uozg!+jLSl!k_nVBrX=ubDt$i=!ABIs zq$M+$Cwm?z5mKHa4l6H8zqbk-+l&Kv7}_zhXaC#iP=LPAH=C19%7mr9weqwt^@U}g zIDn!p!)1B5W4|T>cKE2WsC^kN!f31q8sX!+9qla>6quqV+lw36hZ}s(kzghMW5-Lj zNh>=umS6l#V^Ccc&y^Ed%S@LG*4%_yG%SOx(NFR7&985b7mK5XeA%2WBb(2dvxw*C zaKJ3zH)52`o4rsd-Zyr*V&t;rIczW8UCHHkSz|NB^4#JJEuUZw=M%UMN$`uVTN^Vl z>3wS=k0c*&?O2(Vs(=yX=(~Fa1y~e0=;~le{-|^P;&)w#8SCJOTwca_w|j+i&(y!o zFIW2G&%QbRo6AoP-^mm1i+07P>fNllOI=h~3^CteC}^Z2W-M&bCpA4C2}RAc5sjKe zb133B1~buAzzA?-WiV(w@v9SHca4ihzC_mr`q{nECf2jo#8SKIqE|hR&=2pCRs5$b zd#l?{^o`7Vt(_0Q^`q?7-~HqZB5m}xv33C#_0;I-4_-V-q8lY%VjJGYyNf4wd@NNGvuK&ZPWlGZY*O1*5482gQ-7eCNnv7J zJUBRn`cs_I>Q6z7v8>v&q?yuny{6nWYe@VSg(Dr6@i4hi6K~|jRB%U`%#UGNi7Vm5?eQEj8#yXNcD(_-HV3uA89^fFS4NlZ_n_Ii;QL{fofi z{Y_d+NCSG^cPngjh5fU_S}Sa)Y}Mmce|?R=zQ$j#`cKKDdOTUlZ?uFAFMpM7uCU5% zMr(x~)4Y`!Z56gtzI8L&ToY}si8j|no7LzkIc+4wIeFS>sr?1>Hm-AD*W}Yiyzect zPzT^|^4ElXTvLyJj&*X28ujd zH0tgHI&+5T)3;uC(~8I|X6%j}_uPFP_#OKRljG@UJ&^>%iEEx^>HD~bQy7;W_ z!@snTh?5F^3cD44SK%*(XU(Y$Ulm@4e;XI6G%DO{c*^t2`a@-XS>d;Eu}r6v@Ld0- z%$F6;iHo`)ZK91~U&a;GV}J@L&^H!`R}Ua?A=8Z0Sx z0!f!}g@IYXe4=)-2BA3KNvL=`3#rikSL zCKcJQ@P36BQDBzn!#9WZh{yFZ z-of7tNjyU98~BIu7>=SrJfX(lE8`)ic#3#d4-jwQ$HX4v2`s2-uHloSj1REQ{5gae z(9aVWjh9d>F6&o0eh=?5QPLEmiF*!+E*5dS)`QPuMC(T#CPhD*@i}4}7Kq(AM(oAU zh%sCe<$m0b*GNY3XX2g8FilLUM57AFP{b~63}0dDgE)jw5WQ(K3T>R=_yiuoPVM`| znD&CwTk#=t&){n?mc63`$TjQ+SRy;6?8I73{-xz7p5vtNt;$weG^| zJ9QCV-rn&0uJQblS+|g%ck(67=51#Qsr*sQIHd<|=7Vz7JmS zF#IsY)f;b|Ikn^9==q<<3~TwLS{c}Ie)W|NLn34+GsBKG5Q|#&U}Pv`*^$h!WhLU- z!A#7G#gl1TZ6}?OL&1SfSd#BufY>+Xo$IrF-sD&C&5iPSFz7qO&)%7!JQLhn?)Xja z;U7%)rXD}=>N68}Jo`=W%)`0Dyc2d8^LfV&_sPoxG0hw$H(w^vur{qFty zAL7{yABM)4qPff0s^4~Q2)g{yrZ(;UxV*S&RCac}zFOaMK)>7OFJ}hBJa`KOe0!qo zZ44rUA?n^ZqU;vO3B-}5Eki$xPn@yoYOKu+X(Z%xZ7Fe8e$X~9{4&-48F{)rC0}YE zZt!=q?J7E%MR&Gzbhiv{8J7=i`SgU+Y2khs7M)_rv8UYGBRtp{r?|Tk6uucnzAIdE o7mKCIe73OLzbkANi_Uy{jtQfpqc&3cU#DeowXx%`VkJ`ZAJ#A6TL1t6 diff --git a/build-tools/ESBuildClearLocks.exe b/build-tools/ESBuildClearLocks.exe index 14ba34676365d048d75a683af5040b02f2b7ca5a..17ce1d63723245106e344c5924256aeba8a4f6aa 100644 GIT binary patch delta 97 zcmZqp!r1^sEsR^3qRM@Z8H^Yb8Il;x8B!T68BBm|2u@;11445KV+ISLOfpd10?dN2 dQyI*Fat1(AQ-&0vcoKslkZrhqc{x)g696i_6R`jQ delta 97 zcmZqp!r1^sEsR^3qRM?O7?K&%7|a+_84MVb7>t2DBL)iwL!d|sSlpZ;5h!j5<)t#1 e0C|Z(Ri+G=rSP*OC@2oVM$c?C0yl}Q1`G)-|! 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 b<##yOylt0Qd7$*K<)!#qb;qY{HBk0%H?P?8 delta 2167 zcmZuz3v64}8UD`o^-bfZt<#W@M=5raHl~JN$97^{ZL_6mDGiUN$_iAa3fJ~^laV^) z+F=Wo=7vg?36KbDfF38bz0a8tvbK!f5b z;9D|mTr2mA9^<`Md7oG>(()B?Td2w$)ox%n;K!P7pt}hWqB4xSnM0+d23x|)~60eDVvNZO$%>p92Mi{ zD*T)&7G4(ZJ_|oH7Kx_DQ$pEiHSW{OthT#!Zd#~1ADG7(GhizFqEFdn?LDBqk7yjm z@d_Q!YRAJGTZIZu>V&;G?hD{7(L$Xrx7&A=6I#Skti$foh{w5knHr-oPE9ceiXi$?0rrzx?APLRjD+aNJlVh5|I(${{jFmxZs23hBQCbhbczA|4wl%i zahQ7LE%O$fLs0A?4*T|Ndzy~-&3m;yr|muX1n-zf@ej0`4>38;60;m&o_LopNPG=X z5Z7|BPY|V7RhYvZP9uqFJ)v{>D|hZZTKvyKh)<0diEhRGDeA=wzSAT>!XL4k+xscP z$l*)TgMDZ)HexxN#YQw?EwKYzh+Vjz7{&d>1fC~u!UAy!Cy6&|eUvz)>uf^_maz?o zSo2QYh0lqqevVlAQsi-jDmh zOUuT0sC(QJn?t&^w_*|w?$zq$HF@&Nc40qyMXLIEbC1FII)2r@qkCXiW!K5>;dd8b z`~9N)`;{+Nf7se7BKb@%v&j> zbiP+zx#m^ht~_5?Iiz?x4&uD*y?o};hZtX8Ic>+Z-=Ur6-&%pb9OE4JBy+P%xp`K| zA}#AegW}I}XK1?!$obH)d_OcO*M?K80wK1|B{w(YO_XN`WFfp={y2R74O*v%>zkf& zOJz4d;!WJcy*uER25L$6*IDG7?XovhD(@^7rUn9=ZKqUn4`wH42da(TZ;AR??cawp LY0}Tce5~$&E8@gk diff --git a/build-tools/ESBuildWaitForCompletion.exe b/build-tools/ESBuildWaitForCompletion.exe index e707c8c899186d5d096352b387ba67aca945eadf..f8d388a847bccfb2bef359964a6b6c6a7415851d 100644 GIT binary patch delta 97 zcmZqp!Px*rEsR^3R+al2GZ-->G9)pWGo&(DGME6_5S+x2288Ae#tarfnPi~21(*e4 dr!trUJ_i`pq763_t6bk?V delta 97 zcmZqp!Px*rEsR^3R+al&FeEdiF_<3{;)C{d+kRCkp^UxD(v~ diff --git a/build-tools/ESBuildWaitForCompletion.runtimeconfig.json b/build-tools/ESBuildWaitForCompletion.runtimeconfig.json deleted file mode 100644 index 3e7d1efdf..000000000 --- a/build-tools/ESBuildWaitForCompletion.runtimeconfig.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "runtimeOptions": { - "tfm": "net10.0", - "framework": { - "name": "Microsoft.NETCore.App", - "version": "10.0.0" - }, - "configProperties": { - "EntryPointFilePath": "/home/runner/work/GeoBlazor/GeoBlazor/build-scripts/ESBuildWaitForCompletion.cs", - "EntryPointFileDirectoryPath": "/home/runner/work/GeoBlazor/GeoBlazor/build-scripts", - "Microsoft.Extensions.DependencyInjection.VerifyOpenGenericServiceTrimmability": true, - "System.ComponentModel.DefaultValueAttribute.IsSupported": false, - "System.ComponentModel.Design.IDesignerHost.IsSupported": false, - "System.ComponentModel.TypeConverter.EnableUnsafeBinaryFormatterInDesigntimeLicenseContextSerialization": false, - "System.ComponentModel.TypeDescriptor.IsComObjectDescriptorSupported": false, - "System.Data.DataSet.XmlSerializationIsSupported": false, - "System.Diagnostics.Tracing.EventSource.IsSupported": false, - "System.Linq.Enumerable.IsSizeOptimized": true, - "System.Net.SocketsHttpHandler.Http3Support": false, - "System.Reflection.Metadata.MetadataUpdater.IsSupported": false, - "System.Resources.ResourceManager.AllowCustomResourceTypes": false, - "System.Resources.UseSystemResourceKeys": false, - "System.Runtime.CompilerServices.RuntimeFeature.IsDynamicCodeSupported": false, - "System.Runtime.InteropServices.BuiltInComInterop.IsSupported": false, - "System.Runtime.InteropServices.EnableConsumingManagedCodeFromNativeHosting": false, - "System.Runtime.InteropServices.EnableCppCLIHostActivation": false, - "System.Runtime.InteropServices.Marshalling.EnableGeneratedComInterfaceComImportInterop": false, - "System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization": false, - "System.StartupHookProvider.IsSupported": false, - "System.Text.Encoding.EnableUnsafeUTF7Encoding": false, - "System.Text.Json.JsonSerializer.IsReflectionEnabledByDefault": false, - "System.Threading.Thread.EnableAutoreleasePool": false, - "System.Linq.Expressions.CanEmitObjectArrayDelegate": false - } - } -} \ No newline at end of file diff --git a/build-tools/FetchNuGetVersion b/build-tools/FetchNuGetVersion deleted file mode 100755 index 077ee4a1f0e334485400a685c677c4214630dfd6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 78256 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 XWK&G_NeJx#O9it%IPw{{r0u@}SfRNX delta 609 zcmX|7OKTHR6uvha#89geEUC06QM3iA^PW2cAsD1_d^KG);QP+ZohFjZ*qMnSE^J+h zLWG3-2i#VCEaGe7qU$1pzd!^D*##Fa6a`Oe@WADsd%p9X^L=;STDLlnc2~c>dERZ$ z;Bt4fI{BR-R6!7@5gH`B`83bt8~7sMz;k%OH*pmo@8Luxec(@X^>;8)&!k3m4uyq2J$l_~gt-3_U_EXfh#e`92GUQ;oo(4I!%0kO^)O z`z#(oUcJGDGK-zsQtK>>S6CR;1Al=qW{4^=HBFUG)dgvyW)eduswu0A}?5uIv8 zmrR?<#;ommx+@XWu!(Hzk|PB6Eg@tLMk6Nht?5~LtnmHsu|jYY_z!+1UM8=z3kyqI z(*{T$hce1@-TV~%-igBl+!ZH!lQWlhRa|5u2NA>&MKV&51hpU--}LB2MvA?mTbjO8irf>`&z&3K4T!VLiTEt2DBL)iwL!d|sSlpZ;5h!j5<)t#1 e0C|Z(Ri+G=rSP*OC@2oVM$c?C0yl}Q1`G)-|! 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 C%VBK* delta 3762 zcmb7Hdu)@}6~Es{;yf@(oWzefacn2HV-r8)H-r>2AmOEk0b@K0b!KG_8z@iFFcN|KZW@q0w%16gH`oSlPMX=kLQb=^t~H z>vMj;bM8I&+;gtKZ_hZtXPkd|f5GDMz{TOAI<9i~&DCWS=LmU{;O8PC*9wQtRmx8Y zQKCNbE+H$y%sXKX*Tm^yFOJvXO>VX6KDw|^C;s!Kko;;bAs?+GghPEAAqrSN;>~vP zB~^-0F&Ut(uu-k&La<#JUr~D%D;c~VuBe;Mf;fb_{SYCgJ7*G7!1Ea!eo=uM1Ffb@ z`v47g1{g;LF(`=2>@LlFoNI9jdINgTYUT-vjA00>61q}G%?$mih;D$5I<>1;=4u$X zP2{MImNT?lM0da`oqFwlnY)6!jD$pPL`F|C)Fh%8WE2=$Dxx3B=mCcQ3zxts@0LJC zO|`3{RN6uxBQJ}jTShlB^t_0+!}1#Sn(xWn1{UlKBDYCK9n51uM5&B^&d{|Yx?M)U zV(5GkeGVpS)UFYkyURG8$eoeVU*btecoBUIIK6uQd6|2HdHfLv+};Hl-NQUCis&U7 zoyBa=is(N|VXYoGMfMB5o7=6Kju~miL$zCRpz|e_u9qdXGG{_>%hJzgNZ772Nm@`Y zNq>^1S1Tm!X_X{#m6G&+WtAlElci30r_QLoRGnjt)3t+k46UlXvYaa5FLiFN9;ys( zt{d8MJOt0;_*-}#M+N-O&|tJ_$N-=5W93?13LBk;s(l^XAloJFgo^s=P_JOasymN5 zA%LOBK)%FPL@y!HTu3*;+IsW+%WS?v>^|C{B?HAYfFqx=Ft2&iD#i3=L~CBeAULar z9zkW)s-JnrwkOf z!m{CkWgZm&n> zy+{aBaEnigek`H(;zy>p5Tt)+47)P(88OJIn7z_OT3Fv$XJNr>A5Td$%lJdd3{wIp z8dI18qlKk_jUPkMvW$BoyU%i!s~D_hdF((Zp_&IFW}Cz9hSzL5{Yj+kxT(_0IzH2e z7IvH)75X9k-e$M&uVbDJrRZ=5lkEnAP1x^B@9SnAhn!u7b8SQV@(XdmiyFBCYQ*BoG!5U^3DvE!}QTxp~? z;5A26>;W^Y1?{ueQ%R>Rj0yE@OnU{s0*&%*4P zi|QX3#v zqxi)2T{CMAaVtV`SJ_lHA>q;2@%KYzqX%Jyd&i7p4VdMo^xG`s7P#(Sk)KeZohBvp zeeNt2c$>HaaC&RC?Tr`uIczBbIV&Z$!{D=e4$95-}pEE?jbOU&rYS=>!j0V4aiRS`*13#*XZbw^&1CQ zL(jt-KBIzN0xB2yLR$EZ=a&@C#BR_BBa}Z&$tso2mi1MJ`c_*VhQFm zOTg#U!h+By&I<2_s<|8RVJN>9{P{KRXRXi{7FuC-IKLGF`DTHGN z_@F#u#_QJ?F>(iCc_df#p+`k;g@=&}?s=$)?&IFhjzs^!m3?N%w7tiQQaO#*!o=)a zSN}BnIO-q+Lfy{P&YZ-dM~VxAv-NsSr-P8jRV&ESWrS20C7s5L8$|gG-)R(5+mCMbV>|v_SSM9`1uiIwx&;O zuE>t2!&e)-I`_Efk22eK8cxqWSY%H`s3J>?x+P2_kvEnJqjnu`>%D?5_ z!Z2#WoyWm6Y}WAi&t=sk|KZ^25ofmSCkv-NnIKUTCt*B_7-=DX;=}I*2@)>}A$K7tiXJ Ntm?!+xnXb7{{SS!Y>fZ_ diff --git a/build-tools/GeoBlazorBuild.exe b/build-tools/GeoBlazorBuild.exe index a41d208b296e39627e988fe0153d016cab417d14..39a1c6f5ec40986b75766f9db1f6e796d98962b1 100644 GIT binary patch delta 97 zcmZqp!r1^sEsR^3+{%588H^Yb8Il;x8B!T68BBm|2u@;11445KV+ISLOfpd10?dN2 dQyI*Fat1(AQ-&0vcoKslkZrhqaye5j696N~6P*A6 delta 97 zcmZqp!r1^sEsR^3+{%3|7?K&%7|a+_84MVb7>t2DBL)iwL!d|sSlpZ;5h!j5<)t#1 e0C|Z(Ri+Gall runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/src/dymaptic.GeoBlazor.Core.SourceGenerator.Shared/dymaptic.GeoBlazor.Core.SourceGenerator.Shared.csproj b/src/dymaptic.GeoBlazor.Core.SourceGenerator.Shared/dymaptic.GeoBlazor.Core.SourceGenerator.Shared.csproj index 3737db384..4c762f2d3 100644 --- a/src/dymaptic.GeoBlazor.Core.SourceGenerator.Shared/dymaptic.GeoBlazor.Core.SourceGenerator.Shared.csproj +++ b/src/dymaptic.GeoBlazor.Core.SourceGenerator.Shared/dymaptic.GeoBlazor.Core.SourceGenerator.Shared.csproj @@ -12,6 +12,6 @@ - + diff --git a/src/dymaptic.GeoBlazor.Core.SourceGenerator/ESBuildGenerator.cs b/src/dymaptic.GeoBlazor.Core.SourceGenerator/ESBuildGenerator.cs index ca09f2737..e7e8b817e 100644 --- a/src/dymaptic.GeoBlazor.Core.SourceGenerator/ESBuildGenerator.cs +++ b/src/dymaptic.GeoBlazor.Core.SourceGenerator/ESBuildGenerator.cs @@ -18,6 +18,12 @@ public class ESBuildGenerator : IIncrementalGenerator ? null : Path.GetFullPath(Path.Combine(_corePath, "..", "..", "build-tools")); +#if SHOW_SOURCEGEN_DIALOGS + private static bool _showDialog = true; +#else + private static bool _showDialog = false; +#endif + /// public void Initialize(IncrementalGeneratorInitializationContext context) { @@ -163,20 +169,28 @@ private void LaunchESBuild(SourceProductionContext context) bool proBuildSuccess = false; // gets the ESBuild.cs script - string[] coreArgs = + List esBuildArgs = [ "ESBuild.dll", - "-c", _configuration!, // set config for ESBuild - "-d" // show dialog + "-c", _configuration! // set config for ESBuild ]; - if (!_isDesignTimeBuild) + if (_showDialog) { - coreArgs = [..coreArgs, "-v"]; // show verbose output + if (!_isDesignTimeBuild) + { + esBuildArgs = [..esBuildArgs, "-d", "-v"]; // show verbose output + } + else + { + esBuildArgs = [..esBuildArgs, "-d"]; + } } tasks.Add(Task.Run(async () => { + string[] coreArgs = esBuildArgs.ToArray(); + await ProcessHelper.Execute("Core", BuildToolsPath!, "dotnet", coreArgs, logBuilder, context); @@ -187,18 +201,7 @@ await ProcessHelper.Execute("Core", { logBuilder.AppendLine("Starting Pro ESBuild process..."); - string[] proArgs = - [ - "ESBuild.dll", - "-c", _configuration!, // set config for ESBuild - "-d", // show dialog - "--pro" // build for Pro project - ]; - - if (!_isDesignTimeBuild) - { - proArgs = [..proArgs, "-v"]; // show verbose output - } + string[] proArgs = [..esBuildArgs, "--pro"]; tasks.Add(Task.Run(async () => { diff --git a/src/dymaptic.GeoBlazor.Core.SourceGenerator/ProtobufSourceGenerator.cs b/src/dymaptic.GeoBlazor.Core.SourceGenerator/ProtobufSourceGenerator.cs index c1c6e068c..57b1a9dc2 100644 --- a/src/dymaptic.GeoBlazor.Core.SourceGenerator/ProtobufSourceGenerator.cs +++ b/src/dymaptic.GeoBlazor.Core.SourceGenerator/ProtobufSourceGenerator.cs @@ -14,6 +14,12 @@ namespace dymaptic.GeoBlazor.Core.SourceGenerator; [Generator] public class ProtobufSourceGenerator : IIncrementalGenerator { +#if SHOW_SOURCEGEN_DIALOGS + private static bool _showDialog = true; +#else + private static bool _showDialog = false; +#endif + /// public void Initialize(IncrementalGeneratorInitializationContext context) { @@ -55,10 +61,10 @@ private void FilesChanged(SourceProductionContext context, pipeline) { _corePath = pipeline.Options.ProjectDirectory; - var showDialog = pipeline.Options.PipelineBuild != "true"; + bool showDialog = pipeline.Options.PipelineBuild != "true" && _showDialog; // Generate a unique session ID for this build session - var sessionId = $"{nameof(ProtobufSourceGenerator)}_{Guid.NewGuid():N}"; + string sessionId = $"{nameof(ProtobufSourceGenerator)}_{Guid.NewGuid():N}"; if (pipeline.Types.Length > 0) { diff --git a/src/dymaptic.GeoBlazor.Core.SourceGenerator/dymaptic.GeoBlazor.Core.SourceGenerator.csproj b/src/dymaptic.GeoBlazor.Core.SourceGenerator/dymaptic.GeoBlazor.Core.SourceGenerator.csproj index f84022e51..c0e63c031 100644 --- a/src/dymaptic.GeoBlazor.Core.SourceGenerator/dymaptic.GeoBlazor.Core.SourceGenerator.csproj +++ b/src/dymaptic.GeoBlazor.Core.SourceGenerator/dymaptic.GeoBlazor.Core.SourceGenerator.csproj @@ -12,7 +12,7 @@ - diff --git a/src/dymaptic.GeoBlazor.Core/JsModuleManager.cs b/src/dymaptic.GeoBlazor.Core/JsModuleManager.cs index bd750b72e..923e74306 100644 --- a/src/dymaptic.GeoBlazor.Core/JsModuleManager.cs +++ b/src/dymaptic.GeoBlazor.Core/JsModuleManager.cs @@ -8,19 +8,20 @@ public class JsModuleManager /// /// Retrieves the main entry point for the GeoBlazor Core JavaScript module. /// - public async ValueTask GetCoreJsModule(IJSRuntime jsRuntime, IJSObjectReference? proModule, CancellationToken cancellationToken) + public async ValueTask GetCoreJsModule(IJSRuntime jsRuntime, IJSObjectReference? proModule, + CancellationToken cancellationToken) { if (_coreModule is null) { if (proModule is null) { _coreModule = await jsRuntime - .InvokeAsync("import", cancellationToken, + .InvokeAsync("import", cancellationToken, $"./_content/dymaptic.GeoBlazor.Core/js/geoBlazorCore.js?v={_version}"); } else { - _coreModule = await proModule.InvokeAsync("getCore", cancellationToken); + _coreModule = await proModule.InvokeAsync("getCore", cancellationToken); } } @@ -30,7 +31,8 @@ public async ValueTask GetCoreJsModule(IJSRuntime jsRuntime, /// /// Retrieves the main entry point for the optional GeoBlazor Pro JavaScript module. /// - public async ValueTask GetProJsModule(IJSRuntime jsRuntime, CancellationToken cancellationToken) + public async ValueTask GetProJsModule(IJSRuntime jsRuntime, + CancellationToken cancellationToken) { if (_proModule is null && !_proChecked) { @@ -39,28 +41,31 @@ public async ValueTask GetCoreJsModule(IJSRuntime jsRuntime, switch ((int)licenseType) { case >= 100: - + _proModule = await jsRuntime.InvokeAsync("import", cancellationToken, $"./_content/dymaptic.GeoBlazor.Pro/js/geoBlazorPro.js?v={_version}"); + break; default: _proChecked = true; + return null; } } return _proModule; } - + /// /// Retrieves or creates a JavaScript wrapper for a logic component. /// /// The JS runtime to use for module loading. /// The name of the logic component (e.g., "geometryEngine"). + /// The library name (e.g., "Core" or "Pro") /// A cancellation token. /// A JavaScript object reference to the component wrapper. public async ValueTask GetLogicComponent(IJSRuntime jsRuntime, string moduleName, - CancellationToken cancellationToken) + string library, CancellationToken cancellationToken) { if (!_proChecked) { @@ -72,16 +77,30 @@ public async ValueTask GetLogicComponent(IJSRuntime jsRuntim await GetCoreJsModule(jsRuntime, _proModule, cancellationToken); } - string modulePath = $"./_content/dymaptic.GeoBlazor.{(_proModule is null ? "Core" : "Pro")}/js/{moduleName}.js?v={_version}"; - IJSObjectReference module = await jsRuntime.InvokeAsync("import", cancellationToken, modulePath); + string moduleFileName = moduleName; + + if (library == "Core" && _proModule is not null) + { + // we need to append ("pro_") on the module names to look up the correct file for Core logic components in a Pro build + moduleFileName = $"pro_{moduleName}"; + } + + string modulePath = $"./_content/dymaptic.GeoBlazor.{(_proModule is null ? "Core" : "Pro")}/js/{moduleFileName + }.js?v={_version}"; + + IJSObjectReference module = + await jsRuntime.InvokeAsync("import", cancellationToken, modulePath); + // load the default export class from the module - return await _coreModule!.InvokeAsync("getDefaultClassInstanceFromModule", cancellationToken, module); + return await _coreModule!.InvokeAsync("getDefaultClassInstanceFromModule", + cancellationToken, module); } - - private IJSObjectReference? _proModule; - private IJSObjectReference? _coreModule; - private bool _proChecked; + private readonly string _version = Assembly.GetAssembly(typeof(JsModuleManager))! .GetCustomAttribute()! .InformationalVersion; + + private IJSObjectReference? _proModule; + private IJSObjectReference? _coreModule; + private bool _proChecked; } \ No newline at end of file diff --git a/src/dymaptic.GeoBlazor.Core/Model/LogicComponent.cs b/src/dymaptic.GeoBlazor.Core/Model/LogicComponent.cs index 534f9d696..4b58d9247 100644 --- a/src/dymaptic.GeoBlazor.Core/Model/LogicComponent.cs +++ b/src/dymaptic.GeoBlazor.Core/Model/LogicComponent.cs @@ -71,7 +71,7 @@ public virtual async Task Initialize(CancellationToken cancellationToken = defau await authenticationManager.Initialize(); - Component ??= await jsModuleManager.GetLogicComponent(jsRuntime, ComponentName, cancellationToken); + Component ??= await jsModuleManager.GetLogicComponent(jsRuntime, ComponentName, Library, cancellationToken); } /// diff --git a/src/dymaptic.GeoBlazor.Core/badge_fullmethodcoverage.svg b/src/dymaptic.GeoBlazor.Core/badge_fullmethodcoverage.svg index e0166c286..f25f77942 100644 --- a/src/dymaptic.GeoBlazor.Core/badge_fullmethodcoverage.svg +++ b/src/dymaptic.GeoBlazor.Core/badge_fullmethodcoverage.svg @@ -137,8 +137,8 @@ Coverage - 3% - 3% + 23.3% + 23.3% diff --git a/src/dymaptic.GeoBlazor.Core/badge_linecoverage.svg b/src/dymaptic.GeoBlazor.Core/badge_linecoverage.svg index 071b90fed..f92f021f4 100644 --- a/src/dymaptic.GeoBlazor.Core/badge_linecoverage.svg +++ b/src/dymaptic.GeoBlazor.Core/badge_linecoverage.svg @@ -128,8 +128,8 @@ Coverage Coverage - 1.3% - 1.3% + 8.5% + 8.5% diff --git a/src/dymaptic.GeoBlazor.Core/badge_methodcoverage.svg b/src/dymaptic.GeoBlazor.Core/badge_methodcoverage.svg index 5f0a2bfad..77777c53d 100644 --- a/src/dymaptic.GeoBlazor.Core/badge_methodcoverage.svg +++ b/src/dymaptic.GeoBlazor.Core/badge_methodcoverage.svg @@ -132,8 +132,8 @@ Coverage - 3.2% - 3.2% + 26.3% + 26.3% diff --git a/src/dymaptic.GeoBlazor.Core/dymaptic.GeoBlazor.Core.csproj b/src/dymaptic.GeoBlazor.Core/dymaptic.GeoBlazor.Core.csproj index ab06ecc0b..d1e5c6e01 100644 --- a/src/dymaptic.GeoBlazor.Core/dymaptic.GeoBlazor.Core.csproj +++ b/src/dymaptic.GeoBlazor.Core/dymaptic.GeoBlazor.Core.csproj @@ -55,7 +55,7 @@ - + diff --git a/test/dymaptic.GeoBlazor.Core.Test.Automation.SourceGeneration/GenerateTests.cs b/test/dymaptic.GeoBlazor.Core.Test.Automation.SourceGeneration/GenerateTests.cs index 98fb67251..9f449b4cf 100644 --- a/test/dymaptic.GeoBlazor.Core.Test.Automation.SourceGeneration/GenerateTests.cs +++ b/test/dymaptic.GeoBlazor.Core.Test.Automation.SourceGeneration/GenerateTests.cs @@ -28,7 +28,8 @@ private void Generate(SourceProductionContext context, ImmutableArray classAttributes = []; Dictionary> testMethods = []; - bool attributeFound = false; + bool testMethodAttributeFound = false; + string? currentMethodName = null; var inMethod = false; var openingBracketFound = false; int lineNumber = 0; @@ -42,37 +43,46 @@ private void Generate(SourceProductionContext context, ImmutableArray line.Contains($"[{attribute}"))) { @@ -110,7 +120,7 @@ private void Generate(SourceProductionContext context, ImmutableArray 2) { @@ -275,6 +273,11 @@ private async Task RetryOrMarkAsFailure(string testName, Exception ex, int retri Trace.WriteLine($"Retrying {testName} in {backoffMs}ms (attempt {retries + 2}/3)", "TEST"); await Task.Delay(backoffMs); + page.Console -= HandleConsoleMessage; + page.PageError -= HandlePageError; + _consoleMessages.Remove(testName); + _errorMessages.Remove(testName); + await RunTestImplementation(testName, retries + 1); } diff --git a/test/dymaptic.GeoBlazor.Core.Test.Automation/GlobalUsings.cs b/test/dymaptic.GeoBlazor.Core.Test.Automation/GlobalUsings.cs new file mode 100644 index 000000000..a6220f5d8 --- /dev/null +++ b/test/dymaptic.GeoBlazor.Core.Test.Automation/GlobalUsings.cs @@ -0,0 +1,35 @@ +global using dymaptic.GeoBlazor.Core.Attributes; +global using dymaptic.GeoBlazor.Core.Components.Geometries; +global using dymaptic.GeoBlazor.Core.Components.Layers; +global using dymaptic.GeoBlazor.Core.Components.Popups; +global using dymaptic.GeoBlazor.Core.Components.Renderers; +global using dymaptic.GeoBlazor.Core.Components.Symbols; +global using dymaptic.GeoBlazor.Core.Components.Views; +global using dymaptic.GeoBlazor.Core.Components.Widgets; +global using dymaptic.GeoBlazor.Core.Components; +global using dymaptic.GeoBlazor.Core.Enums; +global using dymaptic.GeoBlazor.Core.Events; +global using dymaptic.GeoBlazor.Core.Exceptions; +global using dymaptic.GeoBlazor.Core.Extensions; +global using dymaptic.GeoBlazor.Core.Functions; +global using dymaptic.GeoBlazor.Core.Interfaces; +global using dymaptic.GeoBlazor.Core.Model; +global using dymaptic.GeoBlazor.Core.Options; +global using dymaptic.GeoBlazor.Core.Results; +global using dymaptic.GeoBlazor.Core.Serialization; +global using Microsoft.AspNetCore.Components; +global using Microsoft.Extensions.Configuration; +global using Microsoft.Extensions.DependencyInjection; +global using Microsoft.JSInterop; +global using ProtoBuf; +global using System.Collections; +global using System.Collections.Concurrent; +global using System.ComponentModel; +global using System.Diagnostics; +global using System.Diagnostics.CodeAnalysis; +global using System.Globalization; +global using System.Reflection; +global using System.Runtime.Serialization; +global using System.Text.Json.Serialization; +global using System.Text.Json; +global using System.Text; \ No newline at end of file diff --git a/test/dymaptic.GeoBlazor.Core.Test.Automation/TestConfig.cs b/test/dymaptic.GeoBlazor.Core.Test.Automation/TestConfig.cs index c8d91f724..d0c3998bf 100644 --- a/test/dymaptic.GeoBlazor.Core.Test.Automation/TestConfig.cs +++ b/test/dymaptic.GeoBlazor.Core.Test.Automation/TestConfig.cs @@ -1,11 +1,7 @@ using CliWrap; -using Microsoft.Extensions.Configuration; using Microsoft.Playwright; using Microsoft.VisualStudio.TestPlatform.ObjectModel; -using System.Diagnostics; using System.Net; -using System.Reflection; -using System.Text; [assembly: Parallelize(Scope = ExecutionScope.MethodLevel)] @@ -103,6 +99,12 @@ public static async Task AssemblyInitialize(TestContext testContext) SetupConfiguration(); + if (!IsCI) + { + Utilities.StartConsoleDialog(Path.Combine(CoreRepoRoot, "build-tools"), + "GeoBlazor Unit Tests"); + } + // kill old running test apps and containers Task[] cleanupTasks = [ @@ -185,14 +187,27 @@ public static async Task AssemblyCleanup() await GenerateCoverageReport(); } - await File.WriteAllTextAsync(LogFilePath, logBuilder.ToString()); + Trace.WriteLine("-------------------------------------------------------"); + Trace.WriteLine("Test run complete", "FINAL_SUMMARY"); + int passedTestCount = _filteredTests.Count - FailedTests.Count - InconclusiveTests.Count; + Trace.WriteLine($"{passedTestCount} / {_filteredTests.Count} tests passed.", "FINAL_SUMMARY"); + Trace.WriteLine("Inconclusive Tests:", "FINAL_SUMMARY"); - if (!IsCI) + foreach (string inconclusive in InconclusiveTests) { - await Cli.Wrap("code") - .WithArguments(LogFilePath) - .ExecuteAsync(); + Trace.WriteLine($"- {inconclusive}", "FINAL_SUMMARY"); + } + + Trace.WriteLine("-------------------------------------------------------"); + Trace.WriteLine("Failed Tests:", "FINAL_SUMMARY"); + + foreach (KeyValuePair failedTest in FailedTests) + { + Trace.WriteLine($"- {failedTest.Key}: {Environment.NewLine}{failedTest.Value}", + "FINAL_SUMMARY"); } + + await File.WriteAllTextAsync(LogFilePath, logBuilder.ToString()); } private static void SetupConfiguration() @@ -242,21 +257,7 @@ private static void SetupConfiguration() RenderMode = blazorMode; } - var envArgs = Environment.GetCommandLineArgs(); - - if (_proAvailable) - { - CoreOnly = _configuration.GetValue("CORE_ONLY", false) - || (envArgs.Contains("--filter") && (envArgs[envArgs.IndexOf("--filter") + 1] == "CORE_")); - - ProOnly = _configuration.GetValue("PRO_ONLY", false) - || (envArgs.Contains("--filter") && (envArgs[envArgs.IndexOf("--filter") + 1] == "PRO_")); - } - else - { - CoreOnly = true; - ProOnly = false; - } + ParseFilter(); _useContainer = _configuration.GetValue("USE_CONTAINER", false); @@ -266,12 +267,6 @@ private static void SetupConfiguration() BrowserPoolSize = _configuration.GetValue("BROWSER_POOL_SIZE", defaultPoolSize); Trace.WriteLine($"Browser pool size set to: {BrowserPoolSize} (CI: {IsCI})", "TEST_SETUP"); - _cover = _configuration.GetValue("COVER", false) - - // only run coverage on a full test run or a full CORE or full PRO test - && (!envArgs.Contains("--filter") || (envArgs[envArgs.IndexOf("--filter") + 1] == "CORE_") - || (envArgs[envArgs.IndexOf("--filter") + 1] == "PRO_")); - if (_cover) { _coverageFormat = _configuration.GetValue("COVERAGE_FORMAT", "xml"); @@ -298,6 +293,164 @@ private static void SetupConfiguration() _targetFramework ??= _configuration.GetValue("TARGET_FRAMEWORK", "net10.0"); } + private static void ParseFilter() + { + string[] envArgs = Environment.GetCommandLineArgs(); + + _filter = envArgs.IndexOf("--filter") is var index and > 0 + && envArgs.Length > index + 1 + ? envArgs[envArgs.IndexOf("--filter") + 1].Replace("\\", "") + : null; + + bool filterIncludesCoreTests = false; + bool filterIncludesProTests = false; + + if (_filter is not null) + { + Dictionary operators = new() + { + ["~"] = FilterOperator.Contains, + ["!~"] = FilterOperator.NotContains, + ["="] = FilterOperator.Equals, + ["!="] = FilterOperator.NotEquals + }; + FilterType filterType = FilterType.FullyQualifiedName; + string filterValue = _filter; + FilterOperator filterOp = FilterOperator.Contains; + + if (operators.Keys + .OrderByDescending(k => k.Length) // order to check the negative operators first + .FirstOrDefault(_filter.Contains) is { } op) + { + filterOp = operators[op]; + string[] split = _filter.Split(op); + filterType = Enum.Parse(split[0]); + filterValue = split[1]; + } + + foreach (Type testType in testClasses) + { + string className = testType.Name; + string classFullName = testType.FullName!; + bool isPro = testType.Namespace!.StartsWith("dymaptic.GeoBlazor.Pro"); + List classTests = []; + + string[] classTestCategories = testType + .GetCustomAttributes() + .SelectMany(ca => ca.TestCategories) + .ToArray(); + + MethodInfo[] methods = testType.GetMethods( + BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly); + + string[] methodFullNames = methods.Select(m => $"{classFullName}.{m.Name}").ToArray(); + + string[] methodNames = methods.Select(m => m.Name).ToArray(); + + Dictionary methodTestCategories = methods + .ToDictionary(m => m.Name, + m => m.GetCustomAttributes() + .SelectMany(ca => ca.TestCategories) + .Concat(classTestCategories) + .ToArray()); + + switch (filterType) + { + case FilterType.ClassName: + if (IsMatch(className, filterValue, filterOp)) + { + classTests.AddRange(methodNames); + } + + break; + case FilterType.Name: + foreach (string methodName in methodNames) + { + if (IsMatch(methodName, filterValue, filterOp)) + { + classTests.Add(methodName); + } + } + + break; + case FilterType.FullyQualifiedName: + foreach (string methodFullName in methodFullNames) + { + if (IsMatch(methodFullName, filterValue, filterOp)) + { + classTests.Add(methodFullName); + } + } + + break; + case FilterType.TestCategory: + foreach (KeyValuePair kvp in methodTestCategories) + { + foreach (string testCategory in kvp.Value) + { + if (IsMatch(testCategory, filterValue, filterOp)) + { + classTests.Add(kvp.Key); + + break; + } + } + } + + break; + } + + if (classTests.Count > 0) + { + if (isPro) + { + filterIncludesProTests = true; + } + else + { + filterIncludesCoreTests = true; + } + + _filteredTests.AddRange(classTests); + } + } + } + else + { + _filteredTests = testClasses + .SelectMany(t => t.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly) + .Select(m => m.Name)) + .ToList(); + } + + if (_proAvailable) + { + CoreOnly = _configuration!.GetValue("CORE_ONLY", false) || !filterIncludesProTests; + + ProOnly = _configuration!.GetValue("PRO_ONLY", false) || !filterIncludesCoreTests; + } + else + { + CoreOnly = true; + ProOnly = false; + } + + // only run coverage on a full test run, otherwise it overwrites the test coverage from previous runs + _cover = _configuration!.GetValue("COVER", false) && _filter is null; + } + + private static bool IsMatch(string value, string filterValue, FilterOperator filterOp) + { + return filterOp switch + { + FilterOperator.Contains => value.Contains(filterValue), + FilterOperator.NotContains => !value.Contains(filterValue), + FilterOperator.Equals => value.Equals(filterValue, StringComparison.OrdinalIgnoreCase), + FilterOperator.NotEquals => !value.Equals(filterValue, StringComparison.OrdinalIgnoreCase), + _ => throw new ArgumentOutOfRangeException(nameof(filterOp), filterOp, null) + }; + } + private static async Task RunUnitTests() { var unitTestPath = Path.Combine(_projectFolder, "..", @@ -316,6 +469,11 @@ private static async Task RunUnitTests() "Detailed" ]; + if (!string.IsNullOrEmpty(_filter)) + { + args = [..args, "--filter", _filter]; + } + if (_cover) { Directory.CreateDirectory(UnitCoverageFolderPath); @@ -337,9 +495,10 @@ private static async Task RunUnitTests() .WithArguments(args) .WithStandardOutputPipe(PipeTarget.ToDelegate(output => Trace.WriteLine(output, "UNIT_TEST"))) .WithStandardErrorPipe(PipeTarget.ToDelegate(output => Trace.WriteLine(output, "UNIT_TEST_ERROR"))) + .WithValidation(CommandResultValidation.None) .ExecuteAsync(); - if (result.ExitCode != 0) + if (result.ExitCode != 0 && result.ExitCode != 8) // 8 = 0 tests ran { throw new ProcessExitedException($"Unit test process exited with code {result.ExitCode}"); } @@ -363,6 +522,11 @@ private static async Task RunSourceGeneratorTests() "Detailed" ]; + if (!string.IsNullOrEmpty(_filter)) + { + args = [..args, "--filter", _filter]; + } + if (_cover) { Directory.CreateDirectory(SgenCoverageFolderPath); @@ -388,9 +552,10 @@ private static async Task RunSourceGeneratorTests() .WithArguments(args) .WithStandardOutputPipe(PipeTarget.ToDelegate(output => Trace.WriteLine(output, "SGEN_TEST"))) .WithStandardErrorPipe(PipeTarget.ToDelegate(output => Trace.WriteLine(output, "SGEN_TEST_ERROR"))) + .WithValidation(CommandResultValidation.None) .ExecuteAsync(); - if (result.ExitCode != 0) + if (result.ExitCode != 0 && result.ExitCode != 8) // 8 = 0 tests ran { throw new ProcessExitedException($"Source Generator test process exited with code {result.ExitCode}"); } @@ -660,10 +825,18 @@ private static async Task WaitForHttpResponse() // set this to 60 seconds * 8 = 8 minutes var maxAttempts = 60 * 8; + Exception? lastException = null; + for (var i = 1; i <= maxAttempts; i++) { try { + if (i % 10 == 0) + { + Trace.WriteLine($"Waiting for Test Site at {TestAppHttpUrl}. Attempt {i} out of {maxAttempts}...", + "TEST_SETUP"); + } + HttpResponseMessage response = await httpClient.GetAsync(TestAppHttpUrl, cts.Token); @@ -678,22 +851,13 @@ private static async Task WaitForHttpResponse() catch (Exception ex) { // Log the exception for debugging SSL/connection issues - if (i % 10 == 0) - { - Trace.WriteLine($"Connection attempt {i} failed: {ex.Message}", "TEST_SETUP"); - } - } - - if (i % 10 == 0) - { - Trace.WriteLine($"Waiting for Test Site at {TestAppHttpUrl}. Attempt {i} out of {maxAttempts}...", - "TEST_SETUP"); + lastException = ex; } await Task.Delay(1000, cts.Token); } - throw new ProcessExitedException("Test page was not reachable within the allotted time frame"); + throw new ProcessExitedException("Test page was not reachable within the allotted time frame", lastException); } private static async Task KillProcessById(int? processId) @@ -754,6 +918,7 @@ await Cli.Wrap("/bin/bash") private static async Task GenerateCoverageReport() { var reportDir = Path.Combine(_projectFolder, "coverage-report"); + var historyDir = Path.Combine(_projectFolder, "history"); if (!File.Exists(CoverageFilePath)) { @@ -792,7 +957,8 @@ private static async Task GenerateCoverageReport() [ $"-reports:{CoverageFilePath};{UnitCoverageFilePath};{SgenCoverageFilePath}", $"-targetdir:{reportDir}", - "-reporttypes:Html;HtmlSummary;TextSummary;Badges", + "-reporttypes:Html;TextSummary;Badges", + $"-historydir:{historyDir}", // Include only GeoBlazor Core and Pro assemblies, exclude everything else $"-assemblyfilters:{string.Join(";", assemblyFilters)}", @@ -857,6 +1023,9 @@ await Cli.Wrap("reportgenerator") private static readonly CancellationTokenSource cts = new(); private static readonly CancellationTokenSource gracefulCts = new(); private static readonly StringBuilder logBuilder = new(); + private static readonly Type[] testClasses = typeof(GeoBlazorTestClass).Assembly.GetTypes() + .Where(t => t.IsSubclassOf(typeof(GeoBlazorTestClass))) + .ToArray(); private static IConfiguration? _configuration; private static string? _runConfig; @@ -871,4 +1040,23 @@ await Cli.Wrap("reportgenerator") private static string _coverageFormat = string.Empty; private static string _coverageFileVersion = string.Empty; private static string? _reportGenLicenseKey; + private static string? _filter; + private static List _filteredTests = []; + + private enum FilterOperator + { + Contains, + NotContains, + Equals, + NotEquals + } + + private enum FilterType + { + FullyQualifiedName, + Name, + ClassName, + Priority, + TestCategory + } } \ No newline at end of file diff --git a/test/dymaptic.GeoBlazor.Core.Test.Automation/Utilities.cs b/test/dymaptic.GeoBlazor.Core.Test.Automation/Utilities.cs new file mode 100644 index 000000000..b9c905430 --- /dev/null +++ b/test/dymaptic.GeoBlazor.Core.Test.Automation/Utilities.cs @@ -0,0 +1,117 @@ +namespace dymaptic.GeoBlazor.Core.Test.Automation; + +public static class Utilities +{ + public static Process? StartConsoleDialog(string buildDir, string title) + { + try + { + string consoleDialogPath = Path.Combine(buildDir, "ConsoleDialog.dll"); + + if (!File.Exists(consoleDialogPath)) + { + Trace.WriteLine($"ConsoleDialog.dll not found at {consoleDialogPath}"); + + return null; + } + + string[] args = + [ + "ConsoleDialog.dll", + $"\"{title}\"", + "-w", + "2" + ]; + + var psi = new ProcessStartInfo + { + FileName = "dotnet", + Arguments = string.Join(" ", args), + WorkingDirectory = buildDir, + RedirectStandardInput = true, + RedirectStandardOutput = true, + UseShellExecute = false, + CreateNoWindow = true + }; + + Process? dialog = Process.Start(psi); + + if (dialog?.StandardInput is null) + { + Trace.WriteLine("Failed to start console dialog. Exiting."); + } + else + { + dialog.StandardInput.AutoFlush = true; + Trace.Listeners.Add(new DialogTraceListener(dialog)); + } + + return dialog; + } + catch (Exception ex) + { + Trace.WriteLine($"Failed to start ConsoleDialog: {ex.Message}"); + + return null; + } + } + + public static void KillDialog(Process? dialog) + { + if (dialog is null || dialog.HasExited) + { + return; + } + + try + { + // Flush to ensure all pending messages are sent before exit + dialog.StandardInput.Flush(); + + // Small delay to allow the dialog to display the final message + Thread.Sleep(500); + dialog.StandardInput.WriteLine("exit"); + } + catch + { + dialog.Kill(); + } + } +} + +public class DialogTraceListener(Process? dialog) : TraceListener +{ + public override void Write(string? message) + { + if (dialog?.StandardInput is null || dialog.HasExited) + { + return; + } + + try + { + dialog.StandardInput.Write(message); + } + catch + { + // Dialog may have closed - ignore + } + } + + public override void WriteLine(string? message) + { + if (dialog?.StandardInput is null || dialog.HasExited) + { + return; + } + + try + { + dialog.StandardInput.WriteLine(message); + } + catch + { + // Dialog may have closed - ignore + } + } +} \ No newline at end of file diff --git a/test/dymaptic.GeoBlazor.Core.Test.Automation/dymaptic.GeoBlazor.Core.Test.Automation.csproj b/test/dymaptic.GeoBlazor.Core.Test.Automation/dymaptic.GeoBlazor.Core.Test.Automation.csproj index 5cc2659f1..c1957b032 100644 --- a/test/dymaptic.GeoBlazor.Core.Test.Automation/dymaptic.GeoBlazor.Core.Test.Automation.csproj +++ b/test/dymaptic.GeoBlazor.Core.Test.Automation/dymaptic.GeoBlazor.Core.Test.Automation.csproj @@ -35,6 +35,7 @@ + diff --git a/test/dymaptic.GeoBlazor.Core.Test.Blazor.Shared/Components/ExtentGeometryTests.cs b/test/dymaptic.GeoBlazor.Core.Test.Blazor.Shared/Components/ExtentGeometryTests.cs index 2d35fc173..412d4a257 100644 --- a/test/dymaptic.GeoBlazor.Core.Test.Blazor.Shared/Components/ExtentGeometryTests.cs +++ b/test/dymaptic.GeoBlazor.Core.Test.Blazor.Shared/Components/ExtentGeometryTests.cs @@ -1,13 +1,13 @@ using dymaptic.GeoBlazor.Core.Components; using dymaptic.GeoBlazor.Core.Components.Geometries; +using dymaptic.GeoBlazor.Core.Model; using Microsoft.AspNetCore.Components; -using Microsoft.VisualStudio.TestTools.UnitTesting; namespace dymaptic.GeoBlazor.Core.Test.Blazor.Shared.Components; [TestClass] -public class ExtentGeometryTests: TestRunnerBase +public class ExtentGeometryTests : TestRunnerBase { [Inject] public required GeometryEngine GeometryEngine { get; set; } @@ -35,7 +35,7 @@ public async Task TestGetExtentHeight() double? height = await GeometryEngine.GetExtentHeight(extent); Assert.IsNotNull(height); } - + [TestMethod] public async Task TestGetExtentWidth() { diff --git a/test/dymaptic.GeoBlazor.Core.Test.Blazor.Shared/Components/GeometryEngineTests.cs b/test/dymaptic.GeoBlazor.Core.Test.Blazor.Shared/Components/GeometryEngineTests.cs index 36df8024c..6701e2a81 100644 --- a/test/dymaptic.GeoBlazor.Core.Test.Blazor.Shared/Components/GeometryEngineTests.cs +++ b/test/dymaptic.GeoBlazor.Core.Test.Blazor.Shared/Components/GeometryEngineTests.cs @@ -1,4 +1,4 @@ -using dymaptic.GeoBlazor.Core.Components; +using dymaptic.GeoBlazor.Core.Components; using dymaptic.GeoBlazor.Core.Components.Geometries; using dymaptic.GeoBlazor.Core.Enums; using dymaptic.GeoBlazor.Core.Model; @@ -9,6 +9,7 @@ namespace dymaptic.GeoBlazor.Core.Test.Blazor.Shared.Components; +[TestCategory(nameof(LogicComponent))] [TestClass] public class GeometryEngineTests : TestRunnerBase { @@ -19,7 +20,7 @@ public class GeometryEngineTests : TestRunnerBase public async Task TestBufferWithProjectedPoint() { Point point = new Point(0, 0, spatialReference: new SpatialReference(103002)); - Polygon buffer = await GeometryEngine.Buffer(point, 10.0, GeometryEngineLinearUnit.Feet); + Polygon? buffer = await GeometryEngine.Buffer(point, 10.0, GeometryEngineLinearUnit.Feet); Assert.IsNotNull(buffer); } @@ -59,8 +60,8 @@ public async Task TestBufferWithMultipleProjectedPointsUnioned() [TestMethod] public async Task TestBufferCallAfterDensified() { - MapPath[] mapPaths = new MapPath[] - { + MapPath[] mapPaths = + [ [ new MapPoint(-10424520.3945, 5095465.361299999), new MapPoint(-10424520.3945, 5095465.2124999985), @@ -174,9 +175,9 @@ public async Task TestBufferCallAfterDensified() new MapPoint(-10423444.9369, 5091214.355099998), new MapPoint(-10423442.8218, 5091028.472199999) ] - }; + ]; Polyline polyline = new Polyline(mapPaths, new SpatialReference(102100)); - Polygon buffer = await GeometryEngine.Buffer(polyline, 20, GeometryEngineLinearUnit.Yards); + Polygon? buffer = await GeometryEngine.Buffer(polyline, 20, GeometryEngineLinearUnit.Yards); Assert.IsNotNull(buffer); } @@ -263,7 +264,7 @@ public async Task TestConvexHull() { Point point = new Point(0, 0, spatialReference: new SpatialReference(103002)); - Geometry convexHull = await GeometryEngine.ConvexHull(point); + Geometry? convexHull = await GeometryEngine.ConvexHull(point); Assert.IsInstanceOfType(convexHull); } @@ -277,7 +278,7 @@ public async Task TestConvexHullMultiplePoints() points.Add(new Point(i, i, spatialReference: new SpatialReference(103002))); } - Geometry[] convexHull = await GeometryEngine.ConvexHull(points); + Geometry?[] convexHull = await GeometryEngine.ConvexHull(points); Assert.IsInstanceOfType(convexHull[0]); Assert.HasCount(10, convexHull); } @@ -292,7 +293,7 @@ public async Task TestConvexHullMerged() points.Add(new Point(i, i, spatialReference: new SpatialReference(103002))); } - Geometry[] convexHull = await GeometryEngine.ConvexHull(points, true); + Geometry?[] convexHull = await GeometryEngine.ConvexHull(points, true); Assert.IsInstanceOfType(convexHull[0]); Assert.HasCount(1, convexHull); } @@ -413,7 +414,7 @@ public async Task TestDifference() ] ], new SpatialReference(103002)); - Geometry difference = await GeometryEngine.Difference(boundaryPolygon, subtractor); + Geometry? difference = await GeometryEngine.Difference(boundaryPolygon, subtractor); Assert.AreNotEqual(boundaryPolygon, difference); } @@ -453,7 +454,7 @@ public async Task TestDifferenceMultipleInputs() ] ], new SpatialReference(103002)); - Geometry[] differences = + Geometry?[] differences = await GeometryEngine.Difference([boundaryPolygon1, boundaryPolygon2], subtractor); Assert.HasCount(2, differences); } @@ -594,7 +595,9 @@ public async Task TestExtendedSpatialReferenceInfo() { SpatialReference spatialReference = new SpatialReference(103002); +#pragma warning disable CS0612 // Type or member is obsolete SpatialReferenceInfo spatialReferenceInfo = await GeometryEngine.ExtendedSpatialReferenceInfo(spatialReference); +#pragma warning restore CS0612 // Type or member is obsolete Assert.IsNotNull(spatialReferenceInfo); } @@ -732,7 +735,7 @@ public async Task TestGeodesicBuffer() ] ]); - Polygon bufferedPolygon = await GeometryEngine.GeodesicBuffer(polygon, 10, GeometryEngineLinearUnit.Feet); + Polygon? bufferedPolygon = await GeometryEngine.GeodesicBuffer(polygon, 10, GeometryEngineLinearUnit.Feet); Assert.IsNotNull(bufferedPolygon); @@ -990,7 +993,7 @@ public async Task TestOffset() ] ], new SpatialReference(102100)); - Geometry offset = await GeometryEngine.Offset(polygon, 10, GeometryEngineLinearUnit.Feet, JoinType.Bevel); + Geometry? offset = await GeometryEngine.Offset(polygon, 10, GeometryEngineLinearUnit.Feet, JoinType.Bevel); Assert.IsNotNull(offset); Assert.AreNotEqual(polygon, offset); @@ -1025,12 +1028,13 @@ public async Task TestOffsetMultipleGeometries() Geometry[] geometries = [polygon1, polygon2]; - Geometry[] offset = await GeometryEngine.Offset(geometries, 10, GeometryEngineLinearUnit.Feet, JoinType.Bevel); + Geometry?[] offset = await GeometryEngine.Offset(geometries, 10, GeometryEngineLinearUnit.Feet, JoinType.Bevel); Assert.IsNotNull(offset); - foreach (Geometry geometry in offset) + foreach (Geometry? geometry in offset) { + Assert.IsNotNull(geometry); Assert.AreNotEqual(polygon1, geometry); Assert.AreNotEqual(polygon2, geometry); } @@ -1219,7 +1223,7 @@ public async Task TestSimplify() ] ], new SpatialReference(102100)); - Geometry simplifiedGeometry = await GeometryEngine.Simplify(polygon); + Geometry? simplifiedGeometry = await GeometryEngine.Simplify(polygon); Assert.IsNotNull(simplifiedGeometry); Assert.AreNotEqual(polygon, simplifiedGeometry); @@ -1390,7 +1394,7 @@ public async Task TestUnion() ] ], new SpatialReference(102100)); - Geometry union = await GeometryEngine.Union(polygon1, polygon2); + Geometry? union = await GeometryEngine.Union(polygon1, polygon2); Assert.IsNotNull(union); Assert.AreNotEqual(polygon1, union); @@ -1558,8 +1562,7 @@ public async Task TestIsClockwiseWithPointArray() ]; Polygon polygon = - new Polygon( - [ + new Polygon([ new MapPath(new MapPoint(0, 0), new MapPoint(0, 10), new MapPoint(10, 10), new MapPoint(10, 0), new MapPoint(0, 0)) ], new SpatialReference(102100)); @@ -1594,7 +1597,7 @@ public async Task TestBufferSingleGeometryWithoutUnit() ] ], new SpatialReference(103002)); - Polygon buffer = await GeometryEngine.Buffer(polygon, 10.0); + Polygon? buffer = await GeometryEngine.Buffer(polygon, 10.0); Assert.IsNotNull(buffer); Assert.AreNotEqual(polygon, buffer); } @@ -1679,14 +1682,15 @@ public async Task TestOffsetSingleGeometry() new Polygon([ [ new MapPoint(0, 0), - new MapPoint(0, 10), - new MapPoint(10, 10), - new MapPoint(10, 0), + new MapPoint(0, 1000), + new MapPoint(1000, 1000), + new MapPoint(1000, 0), new MapPoint(0, 0) ] ], new SpatialReference(102100)); - Geometry offset = await GeometryEngine.Offset(polygon, 10); + // Use negative offset for outward expansion (positive offset goes inward for polygons) + Geometry? offset = await GeometryEngine.Offset(polygon, -10); Assert.IsNotNull(offset); Assert.AreNotEqual(polygon, offset); @@ -1706,7 +1710,7 @@ public async Task TestOffsetSingleGeometryWithUnit() ] ], new SpatialReference(102100)); - Geometry offset = await GeometryEngine.Offset(polygon, 10, GeometryEngineLinearUnit.Feet); + Geometry? offset = await GeometryEngine.Offset(polygon, 10, GeometryEngineLinearUnit.Feet); Assert.IsNotNull(offset); Assert.AreNotEqual(polygon, offset); @@ -1726,10 +1730,10 @@ public async Task TestOffsetSingleGeometryWithJoinType() ] ], new SpatialReference(102100)); - Geometry offsetMiter = await GeometryEngine.Offset(polygon, 10, GeometryEngineLinearUnit.Feet, JoinType.Miter); + Geometry? offsetMiter = await GeometryEngine.Offset(polygon, 10, GeometryEngineLinearUnit.Feet, JoinType.Miter); Assert.IsNotNull(offsetMiter); - Geometry offsetRound = await GeometryEngine.Offset(polygon, 10, GeometryEngineLinearUnit.Feet, JoinType.Round); + Geometry? offsetRound = await GeometryEngine.Offset(polygon, 10, GeometryEngineLinearUnit.Feet, JoinType.Round); Assert.IsNotNull(offsetRound); } @@ -1747,7 +1751,7 @@ public async Task TestOffsetSingleGeometryWithBevelRatio() ] ], new SpatialReference(102100)); - Geometry offset = await GeometryEngine.Offset(polygon, 10, GeometryEngineLinearUnit.Feet, JoinType.Miter, 1.5); + Geometry? offset = await GeometryEngine.Offset(polygon, 10, GeometryEngineLinearUnit.Feet, JoinType.Miter, 1.5); Assert.IsNotNull(offset); Assert.AreNotEqual(polygon, offset); @@ -1767,7 +1771,7 @@ public async Task TestOffsetSingleGeometryWithAllParameters() ] ], new SpatialReference(102100)); - Geometry offset = + Geometry? offset = await GeometryEngine.Offset(polygon, 10, GeometryEngineLinearUnit.Feet, JoinType.Round, null, 0.5); Assert.IsNotNull(offset); @@ -1781,9 +1785,9 @@ public async Task TestOffsetMultipleGeometriesWithoutUnit() new Polygon([ [ new MapPoint(0, 0), - new MapPoint(0, 10), - new MapPoint(10, 10), - new MapPoint(10, 0), + new MapPoint(0, 1000), + new MapPoint(1000, 1000), + new MapPoint(1000, 0), new MapPoint(0, 0) ] ], new SpatialReference(102100)); @@ -1791,15 +1795,16 @@ public async Task TestOffsetMultipleGeometriesWithoutUnit() Polygon polygon2 = new Polygon([ [ - new MapPoint(20, 20), - new MapPoint(20, 30), - new MapPoint(30, 30), - new MapPoint(30, 20), - new MapPoint(20, 20) + new MapPoint(2000, 2000), + new MapPoint(2000, 3000), + new MapPoint(3000, 3000), + new MapPoint(3000, 2000), + new MapPoint(2000, 2000) ] ], new SpatialReference(102100)); - Geometry[] offsets = await GeometryEngine.Offset([polygon1, polygon2], 10); + // Use negative offset for outward expansion (positive offset goes inward for polygons) + Geometry?[] offsets = await GeometryEngine.Offset([polygon1, polygon2], -10); Assert.IsNotNull(offsets); Assert.HasCount(2, offsets); @@ -1837,7 +1842,7 @@ public async Task TestGeodesicBufferSingleGeometryWithoutUnit() ] ]); - Polygon bufferedPolygon = await GeometryEngine.GeodesicBuffer(polygon, 10); + Polygon? bufferedPolygon = await GeometryEngine.GeodesicBuffer(polygon, 10); Assert.IsNotNull(bufferedPolygon); Assert.AreNotEqual(polygon, bufferedPolygon); @@ -1924,7 +1929,7 @@ public async Task TestUnionWithParamsArray() ] ], new SpatialReference(102100)); - Geometry union = await GeometryEngine.Union(polygon1, polygon2, polygon3); + Geometry? union = await GeometryEngine.Union(polygon1, polygon2, polygon3); Assert.IsNotNull(union); Assert.AreNotEqual(polygon1, union); @@ -1956,6 +1961,81 @@ public async Task TestBufferMultipleGeometriesWithUnit() Assert.HasCount(2, buffers); } + [TestMethod] + public async Task TestOffsetMultipleGeometriesWithUnit() + { + Polygon polygon1 = + new Polygon([ + [ + new MapPoint(0, 0), + new MapPoint(0, 1000), + new MapPoint(1000, 1000), + new MapPoint(1000, 0), + new MapPoint(0, 0) + ] + ], new SpatialReference(102100)); + + Polygon polygon2 = + new Polygon([ + [ + new MapPoint(2000, 2000), + new MapPoint(2000, 3000), + new MapPoint(3000, 3000), + new MapPoint(3000, 2000), + new MapPoint(2000, 2000) + ] + ], new SpatialReference(102100)); + + // Use negative offset for outward expansion (positive offset goes inward for polygons) + Geometry?[] offsets = await GeometryEngine.Offset([polygon1, polygon2], -10, GeometryEngineLinearUnit.Meters); + + Assert.IsNotNull(offsets); + Assert.HasCount(2, offsets); + + foreach (Geometry? offset in offsets) + { + Assert.IsNotNull(offset); + } + } + + [TestMethod] + public async Task TestOffsetMultipleGeometriesWithJoinTypeAndBevelRatio() + { + Polygon polygon1 = + new Polygon([ + [ + new MapPoint(0, 0), + new MapPoint(0, 1000), + new MapPoint(1000, 1000), + new MapPoint(1000, 0), + new MapPoint(0, 0) + ] + ], new SpatialReference(102100)); + + Polygon polygon2 = + new Polygon([ + [ + new MapPoint(2000, 2000), + new MapPoint(2000, 3000), + new MapPoint(3000, 3000), + new MapPoint(3000, 2000), + new MapPoint(2000, 2000) + ] + ], new SpatialReference(102100)); + + // Test offset with JoinType and bevelRatio parameters + Geometry?[] offsets = await GeometryEngine.Offset([polygon1, polygon2], -10, + GeometryEngineLinearUnit.Meters, JoinType.Miter, 1.5); + + Assert.IsNotNull(offsets); + Assert.HasCount(2, offsets); + + foreach (Geometry? offset in offsets) + { + Assert.IsNotNull(offset); + } + } + private readonly Random _random = new(); [TestMethod] diff --git a/test/dymaptic.GeoBlazor.Core.Test.Blazor.Shared/Components/LocationServiceTests.cs b/test/dymaptic.GeoBlazor.Core.Test.Blazor.Shared/Components/LocationServiceTests.cs index bdc7651e0..cf662cef7 100644 --- a/test/dymaptic.GeoBlazor.Core.Test.Blazor.Shared/Components/LocationServiceTests.cs +++ b/test/dymaptic.GeoBlazor.Core.Test.Blazor.Shared/Components/LocationServiceTests.cs @@ -1,6 +1,8 @@ using dymaptic.GeoBlazor.Core.Components; using dymaptic.GeoBlazor.Core.Components.Geometries; +using dymaptic.GeoBlazor.Core.Enums; using dymaptic.GeoBlazor.Core.Model; +using dymaptic.GeoBlazor.Core.Results; using Microsoft.AspNetCore.Components; @@ -9,6 +11,7 @@ namespace dymaptic.GeoBlazor.Core.Test.Blazor.Shared.Components; +[TestCategory(nameof(LogicComponent))] [TestClass] public class LocationServiceTests : TestRunnerBase { @@ -101,6 +104,85 @@ public async Task TestPerformAddressesToLocationFromString(Action renderHandler) secondAddress.Location.Longitude} Lat: {secondAddress.Location.Latitude}"); } + [TestMethod] + public async Task TestLocationToAddress(Action renderHandler) + { + // Reverse geocode: given a location, find the address + // Using the Esri headquarters location + Point location = new Point(-117.19498, 34.05383, spatialReference: new SpatialReference(4326)); + + AddressCandidate result = await LocationService.LocationToAddress(location); + + Assert.IsNotNull(result); + Assert.IsNotNull(result.Address); + + Assert.IsTrue(result.Address.Contains("New York") || result.Address.Contains("Redlands"), + $"Expected address to contain 'New York' or 'Redlands', got: {result.Address}"); + } + + [TestMethod] + public async Task TestLocationToAddressWithLocationType(Action renderHandler) + { + // Reverse geocode with location type specified + Point location = new Point(-117.19498, 34.05383, spatialReference: new SpatialReference(4326)); + + AddressCandidate result = await LocationService.LocationToAddress(location, LocationType.Rooftop); + + Assert.IsNotNull(result); + Assert.IsNotNull(result.Address); + } + + [TestMethod] + public async Task TestLocationToAddressWithOutSpatialReference(Action renderHandler) + { + // Reverse geocode with output spatial reference + Point location = new Point(-117.19498, 34.05383, spatialReference: new SpatialReference(4326)); + SpatialReference outSpatialReference = new SpatialReference(102100); // Web Mercator + + AddressCandidate result = await LocationService.LocationToAddress(location, null, outSpatialReference); + + Assert.IsNotNull(result); + Assert.IsNotNull(result.Address); + Assert.IsNotNull(result.Location); + + // Location should be in Web Mercator coordinates + Assert.IsNotNull(result.Location.SpatialReference); + Assert.AreEqual(102100, result.Location.SpatialReference.Wkid); + } + + [TestMethod] + public async Task TestSuggestLocations(Action renderHandler) + { + // Get location suggestions based on partial text + Point location = new Point(-117.19498, 34.05383, spatialReference: new SpatialReference(4326)); + string searchText = "Starbucks"; + + List suggestions = await LocationService.SuggestLocations(location, searchText); + + Assert.IsNotNull(suggestions); + Assert.IsGreaterThan(0, suggestions.Count); + + // Each suggestion should have text and a magic key + SuggestionResult firstSuggestion = suggestions[0]; + Assert.IsNotNull(firstSuggestion.Text); + Assert.IsFalse(string.IsNullOrEmpty(firstSuggestion.Text)); + } + + [TestMethod] + public async Task TestSuggestLocationsWithCategories(Action renderHandler) + { + // Get location suggestions with category filter + Point location = new Point(-117.19498, 34.05383, spatialReference: new SpatialReference(4326)); + string searchText = "Coffee"; + List categories = ["Coffee Shop", "Food"]; + + List suggestions = await LocationService.SuggestLocations(location, searchText, categories); + + Assert.IsNotNull(suggestions); + + // Should return results, though exact count depends on what's near the location + } + private bool LocationsMatch(Point loc1, Point loc2) { return (Math.Abs(loc1.Latitude!.Value - loc2.Latitude!.Value) < 0.00001) diff --git a/test/dymaptic.GeoBlazor.Core.Test.Blazor.Shared/Components/ProjectionEngineTests.cs b/test/dymaptic.GeoBlazor.Core.Test.Blazor.Shared/Components/ProjectionEngineTests.cs index bb16f8f24..3cf0f61ee 100644 --- a/test/dymaptic.GeoBlazor.Core.Test.Blazor.Shared/Components/ProjectionEngineTests.cs +++ b/test/dymaptic.GeoBlazor.Core.Test.Blazor.Shared/Components/ProjectionEngineTests.cs @@ -6,6 +6,7 @@ namespace dymaptic.GeoBlazor.Core.Test.Blazor.Shared.Components; +[TestCategory(nameof(LogicComponent))] [TestClass] public class ProjectionEngineTests : TestRunnerBase { diff --git a/test/dymaptic.GeoBlazor.Core.Test.Unit/dymaptic.GeoBlazor.Core.Test.Unit.csproj b/test/dymaptic.GeoBlazor.Core.Test.Unit/dymaptic.GeoBlazor.Core.Test.Unit.csproj index 9f218338b..0bd876cc8 100644 --- a/test/dymaptic.GeoBlazor.Core.Test.Unit/dymaptic.GeoBlazor.Core.Test.Unit.csproj +++ b/test/dymaptic.GeoBlazor.Core.Test.Unit/dymaptic.GeoBlazor.Core.Test.Unit.csproj @@ -11,9 +11,9 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + + From ae2c5ca40dcad1dd7bd34a16a47fe272cf7242dd Mon Sep 17 00:00:00 2001 From: Tim Purdum Date: Sun, 25 Jan 2026 10:41:34 -0600 Subject: [PATCH 09/89] test improvements --- build-scripts/ScriptBuilder.cs | 210 +++++++++++++++++- build-tools/BuildAppSettings.dll | Bin 10240 -> 10240 bytes build-tools/BuildAppSettings.exe | Bin 162304 -> 162304 bytes build-tools/BuildTemplates.dll | Bin 25088 -> 25088 bytes build-tools/BuildTemplates.exe | Bin 162304 -> 162304 bytes build-tools/ConsoleDialog.dll | Bin 11264 -> 11264 bytes build-tools/ConsoleDialog.exe | Bin 162304 -> 162304 bytes build-tools/ESBuild.dll | Bin 19456 -> 19456 bytes build-tools/ESBuild.exe | Bin 162304 -> 162304 bytes build-tools/ESBuildClearLocks.dll | Bin 8192 -> 8192 bytes build-tools/ESBuildClearLocks.exe | Bin 162304 -> 162304 bytes build-tools/ESBuildWaitForCompletion.dll | Bin 9728 -> 9728 bytes build-tools/ESBuildWaitForCompletion.exe | Bin 162816 -> 162816 bytes build-tools/FetchNuGetVersion.dll | Bin 9216 -> 9216 bytes build-tools/FetchNuGetVersion.exe | Bin 162304 -> 162304 bytes build-tools/GeoBlazorBuild.dll | Bin 40448 -> 40448 bytes build-tools/GeoBlazorBuild.exe | Bin 162304 -> 162304 bytes .../dymaptic.GeoBlazor.Core.csproj | 6 +- .../GeoBlazorTestClass.cs | 18 +- .../TestConfig.cs | 58 ++++- 20 files changed, 268 insertions(+), 24 deletions(-) diff --git a/build-scripts/ScriptBuilder.cs b/build-scripts/ScriptBuilder.cs index f08199a17..1232fc95a 100644 --- a/build-scripts/ScriptBuilder.cs +++ b/build-scripts/ScriptBuilder.cs @@ -15,6 +15,7 @@ // // Options: // --exclude When specified, the listed scripts will be skipped instead of included +// --force, -f Force rebuild of all scripts regardless of changes // // Output: // Compiled DLLs are placed in GeoBlazor/build-tools/ directory @@ -23,13 +24,19 @@ using System.Diagnostics; using System.Runtime.CompilerServices; +using System.Text.Json; bool excludeMode = false; HashSet scriptsToProcess = new(); string scriptDir = GetScriptDirectory(); -string outDir = Path.GetFullPath(Path.Combine(scriptDir, "..", "build-tools")); +string coreDir = Path.GetFullPath(Path.Combine(scriptDir, "..")); +string outDir = Path.GetFullPath(Path.Combine(coreDir, "build-tools")); + +Trace.Listeners.Add(new TextWriterTraceListener(Console.Out)); +Trace.WriteLine("Starting ScriptBuilder..."); string[] scripts = Directory.GetFiles(scriptDir, "*.cs"); +bool force = false; for (int i = 0; i < args.Length; i++) { @@ -40,12 +47,26 @@ case "--exclude": excludeMode = true; break; + case "--force": + case "-f": + force = true; + break; default: scriptsToProcess.Add(arg); break; } } +string recordFile = Path.Combine(outDir, ".csbuild-record.json"); +string currentBranch = GetCurrentGitBranch(coreDir); +if (!force) +{ + if (!CheckIfNeedsBuild(recordFile, currentBranch, scriptDir, outDir, scripts)) + { + return 0; + } +} + foreach (string script in scripts) { if (Path.GetFileName(script) == "ScriptBuilder.cs") @@ -57,12 +78,12 @@ { if (excludeMode && scriptsToProcess.Contains(Path.GetFileName(script))) { - Console.WriteLine($"Skipping excluded script: {Path.GetFileName(script)}"); + Trace.WriteLine($"Skipping excluded script: {Path.GetFileName(script)}"); continue; } else if (!excludeMode && !scriptsToProcess.Contains(Path.GetFileName(script))) { - Console.WriteLine($"Skipping unlisted script: {Path.GetFileName(script)}"); + Trace.WriteLine($"Skipping unlisted script: {Path.GetFileName(script)}"); continue; } } @@ -74,6 +95,8 @@ } } +SaveBuildRecord(recordFile, currentBranch); + return 0; @@ -111,7 +134,7 @@ static int BuildScript(string scriptName, string scriptDir, string outDir) using Process? process = Process.Start(psi); if (process is null) { - Console.WriteLine($"Failed to build {scriptName}"); + Trace.WriteLine($"Failed to build {scriptName}"); return 1; } @@ -120,7 +143,7 @@ static int BuildScript(string scriptName, string scriptDir, string outDir) { if (e.Data is not null) { - Console.WriteLine(e.Data); + Trace.WriteLine(e.Data); } }; @@ -128,7 +151,7 @@ static int BuildScript(string scriptName, string scriptDir, string outDir) { if (e.Data is not null) { - Console.WriteLine(e.Data); + Trace.WriteLine(e.Data); } }; @@ -148,4 +171,179 @@ static int BuildScript(string scriptName, string scriptDir, string outDir) static string GetScriptDirectory([CallerFilePath] string? callerFilePath = null) { return Path.GetDirectoryName(callerFilePath) ?? Environment.CurrentDirectory; +} + +/// +/// Gets the current Git branch name for a repository. +/// +/// The directory within the Git repository. +/// The branch name, or "unknown" if Git is unavailable or fails. +static string GetCurrentGitBranch(string workingDirectory) +{ + try + { + var psi = new ProcessStartInfo + { + FileName = "git", + Arguments = "rev-parse --abbrev-ref HEAD", + WorkingDirectory = workingDirectory, + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + CreateNoWindow = true + }; + + using var process = Process.Start(psi); + if (process is null) return "unknown"; + + string output = process.StandardOutput.ReadToEnd().Trim(); + process.WaitForExit(); + + return process.ExitCode == 0 ? output : "unknown"; + } + catch + { + return "unknown"; + } +} + +/// +/// Determines whether a build is needed. +/// A build is needed if: the branch changed, scripts were modified since last build, +/// or the output directory is empty. +/// +/// Path to the build record file. +/// The current Git branch name. +/// Path to the TypeScript source files. +/// Path to the JavaScript output directory. +/// Array of script names to check for changes. +/// True if a build should be performed. +static bool CheckIfNeedsBuild(string recordFilePath, string currentBranch, string scriptsDir, string outputDir, + string[] scripts) +{ + // Check if build is needed + var lastBuild = GetLastBuildRecord(recordFilePath); + bool branchChanged = currentBranch != lastBuild.Branch; + + if (branchChanged) + { + Trace.WriteLine($"Git branch changed from \"{lastBuild.Branch}\" to \"{currentBranch}\". Rebuilding..."); + return true; + } + + if (!GetScriptsModifiedSince(scriptsDir, lastBuild.Timestamp)) + { + Trace.WriteLine("No changes in Scripts folder since last build."); + + // Check output directory for existing files + if (Directory.Exists(outputDir) && Directory.GetFiles(outputDir).Length > 0) + { + // DLLs and runtimeconfig.json files must exist for each script to function in build pipelines + foreach (string script in scripts) + { + string fileName = Path.GetFileNameWithoutExtension(script); + if (fileName == "ScriptBuilder") + { + continue; + } + string outputDll = Path.Combine(outputDir, fileName + ".dll"); + if (!File.Exists(outputDll)) + { + Trace.WriteLine($"Output DLL missing: {outputDll}. Proceeding with build."); + return true; + } + string outputRuntimeJson = Path.Combine(outputDir, fileName + ".runtimeconfig.json"); + if (!File.Exists(outputRuntimeJson)) + { + Trace.WriteLine($"Output runtime config missing: {outputRuntimeJson}. Proceeding with build."); + return true; + } + } + return false; + } + else + { + Trace.WriteLine("Output directory is empty. Proceeding with build."); + return true; + } + } + + Trace.WriteLine("Changes detected in Scripts folder. Proceeding with build."); + return true; +} + +/// +/// Reads the last build record from the JSON file. +/// The record contains the timestamp of the last successful build and the branch name. +/// +/// Path to the .esbuild-record.json file. +/// A tuple containing the Unix timestamp (milliseconds) and branch name. +static (long Timestamp, string Branch) GetLastBuildRecord(string recordFilePath) +{ + if (!File.Exists(recordFilePath)) + { + return (0, "unknown"); + } + + try + { + string json = File.ReadAllText(recordFilePath); + using var doc = JsonDocument.Parse(json); + var root = doc.RootElement; + + long timestamp = root.TryGetProperty("timestamp", out var ts) ? ts.GetInt64() : 0; + string branch = root.TryGetProperty("branch", out var br) ? br.GetString() ?? "unknown" : "unknown"; + + return (timestamp, branch); + } + catch + { + return (0, "unknown"); + } +} + +/// +/// Checks if any files in the build-tools directory have been modified +/// since the given timestamp. +/// +/// Path to the Scripts directory to scan. +/// Unix timestamp (milliseconds) of the last build. +/// True if any files have been modified since the timestamp. +static bool GetScriptsModifiedSince(string scriptsDir, long lastTimestamp) +{ + if (!Directory.Exists(scriptsDir)) + { + return true; // Force rebuild if Scripts folder doesn't exist + } + + var lastBuildTime = DateTimeOffset.FromUnixTimeMilliseconds(lastTimestamp).DateTime; + + foreach (string file in Directory.GetFiles(scriptsDir, "*", SearchOption.AllDirectories)) + { + if (File.GetLastWriteTime(file) > lastBuildTime) + { + return true; + } + } + + return false; +} + +/// +/// Saves the build record to a JSON file after a successful build. +/// Records the current timestamp and branch name for incremental build detection. +/// +/// Path where the record should be saved. +/// The current Git branch name. +static void SaveBuildRecord(string recordFilePath, string branch) +{ + long timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); + // Write JSON manually to avoid reflection-based serialization (not compatible with Native AOT) + string json = $$""" + { + "timestamp": {{timestamp}}, + "branch": "{{branch.Replace("\\", "\\\\").Replace("\"", "\\\"")}}" + } + """; + File.WriteAllText(recordFilePath, json); } \ No newline at end of file diff --git a/build-tools/BuildAppSettings.dll b/build-tools/BuildAppSettings.dll index 7264c3895ade2ae89c65ae78d8ad0487e652043a..70f5293d8aef6471f47576b1d77a5c448728c06c 100644 GIT binary patch delta 232 zcmZn&Xb70l!E(^|#G#En2GRm42hMgYe*EasU}BUe1)dX7V4|%?eP#=ATeOkm^50xsN_u_6ugNW?8WLyxdF{Un7P@1~Ud@h9m|H z1``HLhEyQi6v(p#iY76n0NJKMF-xGFA%hW+HURRCfvi-ZNE(oB#9$7@#z0fj7>qV= IRrY290Ah4XK>z>% delta 232 zcmZn&Xb70l!Qz$d@O)#BfwX{J>84j6Nmj1gGdG<$HG9LJ&1a3IbIJJ`^yxczf!Dd0WoRZrXfaZYGPbDT6VC5kn$F5`#HI zDuX4136KrJNepQ~XbzOO0Lml-#Vx=r2s@R*3@B#+6g6c?0g5Lv7&0Vo-m2`)0sslT BPYVD5 diff --git a/build-tools/BuildAppSettings.exe b/build-tools/BuildAppSettings.exe index f145eb96a8a4b616326bb8f9d24a31e04833d4fd..9e6097dacf23a24a3fe4a5223ca08d9c4184b2d0 100644 GIT binary patch delta 97 zcmZqp!r1^sEsR^3BFcS@7!n!G7>pT`7%Uh}7%Ul5foxMC&k`t_#E=4Hn*zlwfpUfn jMnKvC$TtSEQh_3AK(-NsIS?BIO-W-g+P<`$DVhlY7!nzh7|a<`87vu0fNTg(Vn_o*bD+EhP$n5DZUJUN d*r^O=Ksf`Ts3}7VP&|pjkRfsV(sHI~CIBMt6SDvS diff --git a/build-tools/BuildTemplates.dll b/build-tools/BuildTemplates.dll index febd5b45966c869996edcbe04364b837a1686dd5..382378c1f1f32587a4cbf99d1bb02f0b01146c5c 100644 GIT binary patch delta 250 zcmZoT!q{+xaY6^n@208)8+*>#v!yUtF~l%Te(0yRxy@k`zd(?V6MrUajK}O4i>%W} z9v$AS5fHlxmV@YGiI?oS0@bd3ESX z7BSFL~43-S3K(;B6X9*NdVn_k9O@U&TKsiGOBOq-6#v$-%>F~l%Te(0yRxy@k`zksjfhq+(#c|3a8mnf@Q zU+mbd5fHAKr+?L+#u03#UjZtaq{ZWlPnfL zwI58b2n!JCPj9{F?3CxfdH3(7QZmPcC*KHLuK*R)FNO*NRVPeU+4Q{Wme<5AF{9ee z%fr{Q`kFErGZ-->G9)pWGo&(DGME6_5S+x2288B7c?+OSGEm$C%!05}8O(rk20&3$ Sh7_Q95`!T_;^u|1@hkvitWn(n diff --git a/build-tools/BuildTemplates.exe b/build-tools/BuildTemplates.exe index eb7b1362cb03ab83e5cdaa2830116040e4da264f..6947f9cba68420090110c58b2fd8539769b72b1a 100644 GIT binary patch delta 97 zcmZqp!r1^sEsR^3T*`fo7!n!G7>pT`7%Uh}7%Ul5foxMC&k`t_#E=4Hn*zlwfpUfn jMnKvC$TtSEQh_3AK(-NsIS?BIO-W-g+CH(IDW3@d-Bc0u delta 97 zcmZqp!r1^sEsR^3T*`e-8H^c>7!nzh7|a<`87vu0fNTg(Vn_o*bD+EhP$n5DZUJUN d*r^O=Ksf`Ts3}7VP&|pjkRfsV#B!#5CIB1y6Q2M8 diff --git a/build-tools/ConsoleDialog.dll b/build-tools/ConsoleDialog.dll index ba2bceb2c9a063308f8bf5a16f3f941da7b90b6b..bcb54b8438ed161decf1f62a0c5f80b7bb27fbed 100644 GIT binary patch delta 2028 zcmb_dZERCj7=GV-+k4xtly#M@ql0xH-7Xs&UAt~8OUPgw8;*?;oP4Qd8=ITNOfJrV z8+1t2q2MgXgoq^K{2;^_f(0bF#F_BJuNcv&3B<)=h!_&SVq%Dq_}tr#3Hs9}_c_n= zocBHFJ?EZtJG5?S-K)cv`oV(h@qJnRVf@%yepE0QTD2OYheVFFP^{-dJ|lK>%pYG$ z=C2Vbk(K;KM7DGyhQ_0a^LfEUtJEX+*29sB+_JWa$WbE8#a2B`^pr!3KrggQil!i; zc)nIKtyM$2v`Ry(=(j;n$ORLGNr+Pc^kxNZX>ACa`U>f%--C z_f?zpGN`KOh;vGLjEt(^MWI$KMH-M$zWymn)dLdZs}S$-?Vpd;1=96JutozJ@@T-T zkC;4D84b+Uy)t}lDPmLf8gNxV4Ak%v9L{@-7==Go#A#5Q&N<={6;=3x3iTMT>wl?Y z?sUcDm;@E;6K3nxubFM8?zF&nrq8OnhLzjwu|pn<+oBuM+5@P%WFeH1lT;xnBQvS0 zpp5*aT2kDqd!ewO*b#Hh9;LWN5Blc%ho zF0Ezrl=*@d`W;oP!fJn(bunbm%2y#pUDqdgSE}}ufPESrNF2xlKoVGPoNd({~5W7F|TM$979 z`9w1OZ>`S(-&bB>rW$&QTm2Z7;+W(WQSDj~dyOSJnP+%=s0Bq?$eW*3{ZLj)4J6e; zD22{VsZ&rYHR1vrdEe6^w$ZSbR5z%C9rTr{Cb?f#$U&*NgG7@&B>AC?CBBEvVe*Mz zT)xCD*Td|&Kr7hI2A)(e&`lcW%VJ~dyx3J*&2On!X$L)Ty#ef0`_P-C{zdEg9qS%A z1y+UH`KY8&8Gj+o zOfi<_l{lQ(>XVr_iD$ANlX>VxvDFt!G|tPH+_bprXvxTdhQ_@G;amMbId6->>>nK} zyJY+?DXJ_s__M55V%NMxzkfLwhT1E_9aZJgu5hFyTG6pN66uPTmxZFiNI2A0-W>^5 zhQjULA#o|EZO-jCT22u+w;D@2i)XOLxMR(^8P*DXbt^<~ZeFT)Z0xM9*wS>UZPxL~ zliv)B!?_=Xrp${Qrc4YJ8O}Pfd|lvR>am**$Dc>~^zTiB`{Oz392W=j2IE0z!j&JA zn?ux26?o?zR7K?!r7rLYR1|IpZ3d6Pi=wZLLTH1~VX!VZ-QXdr#1qCS-4sgfFC37h OlVu5a(RGdo)xQBkLw7;| delta 2109 zcmbtVZERCj7=F*W?Y&)F#=2<-baNeL>t$O<*KM=|G+Q^ELkGh-WTIkQHa0;z?=~Y} zZVQ4FSlm2621b&JMlpmRS_p`Sj|3CIkDz=d8c9Z?k|>DLL?Vhl_jbsK{4lZ2dCv1b z@B5zf-nZx6_AcyQIM{F6R_W^>+MLA>488j(YZr`p&1IEDkBD4eB$}C*9T9!3$GxqJ zjL!o=PG&kamFOua5rd5(i^Cp&qJg(bO@U+}spUl0q7*KvGW>;E>U7<Rl+*R|HE>(XAe7G0F&7xUH5wB<9$b%GqX=T`%_9ik)-eK9|9BuC29VE561CROgASwtHBEm}D=w z_cDGf&kID4<3gOmmY&8gt$oGPPLxZ29QQWe-_%H3GKlnx;Hhn@tHHbj_CC>P4?1vH zj2qX0`ECqL;+VaiHMKFBuCf}E>5{w*{7n`IUXwLoyYd|HLqlkr&YJ?}IPVUJmA(z;AOW}#lg8fD#zOcpwVmkH4*>*a1(`Xj!L7BO;(Y2#gq1>@(i zo-hUX1;ng3Uj)XKI5o4k%0zGsTSV$IlOUVVCNh+|K%7G6WdLyu!Vzh(D z4D6+4)Xql?EInjQCbkKZo9T{9U>w*<-Mp6xq~%jTeTCEiJDHi21;n?GlAgsZh?fvf zrVeAD#q1>7d%I@8L)>P(#%_-*z@@ldTW_o<;h(`o5;f8u3_BYj)x?JB;l$ST3l`ox zAWpcPHD0Qv7K&4tmQ#dQfobGMc$~VyV(__*h!tQOBD%&R=E2`Z9R>=P9T1C3WqQybDPIzbmPEwa+$JYoeD&oQiPbmg&E1dDUx^{A zCTE(%RYYO&#AHv}(G8w#Yv#=Rhq?97)&s|j#NgyzfiZDI=9s82^mJ?^{kV5<#>tOg z-!p{t>z|!EcMs({**3AkvnG)`B_P$8?U4}nj$R1VA4Wff}kY{Hf8}1+;OvWu;7!nzh7|a<`87vu0fNTg(Vn_o*bD+EhP$n5DZUJUN d*r^O=Ksf`Ts3}7VP&|pjkRfq-< diff --git a/build-tools/ESBuild.dll b/build-tools/ESBuild.dll index b8d1c909a4906978957bcf6d6d146f148b57301e..119ad3bf1b194344403c4f74e06fadf5e787cc60 100644 GIT binary patch delta 241 zcmZpe!Pqc^aY6?RQ)eOpT3FP0~z_%#Dl_(~KrpI##okbx5C^{KGLo zU{UmT&C}$l3 z$Y91`%#g%j!C=B*$&d3K+zCaC}s(iGh{FV(gr}jF_4uC6iEZJjTp>< Q*cfO^8iUd1K=<`507;-q1poj5 delta 241 zcmZpe!Pqc^aY6@6_zUfS8+$(MFuH7R(Cy$Cm^M|oS?Nxl`-b<|E(F~8b#=3Ztrd&9 zsj*RFl6k77Ng@y?rKOo0Tcjo@T3Dn4$y76QgGAF5izLIu$(4@PEb%MarceIi7$C4e z;FV?Kxw#%~)~vkBf1B7RM>s81fC;ie1wpFkHU>OD{FAAK%{21pW=ZGQ%)X`!#tcRb zi3~{$<_xI}mJB99HUuXzqyeEhP~HM4lMEEM0J9+MR0cDkoB>eOlpzHup2T3tkhnR} HeLV{R=cGF<3B|Fjz9A0@PpsFbQa9HbwDl-sL;3rIRDp+GojT0g-=<>PCrX z#z__?mZ`?3NtR|wDW;~WmL`TqmIkKAMyV!grbgyQ#))Z0lTF2pS)7l5zB@TbEI{D& z?%p8t@(RbsxY^ML-}#nKJ|nhO0V-I?2o(gXj@3J8e59;k^}h`>=EfGO$%z&gsX#K-%-kT+G{qvxFmbY}m@!M@oQoZkbHoA!JUY~! zDx`|MTb}L7y!Bw;l*wnrwkkjcXR|;BfvP#`6>UGse6K6xk(LtLyioi&v#%+GF@q69 zB0~~`IYTOgC4&i&4Z%qaX+UTWl(zuNBm>1Qz$^$mmB9=sX8;s6Wk>;vCovc@ByQd+ H?acxJf)Px; diff --git a/build-tools/ESBuildClearLocks.exe b/build-tools/ESBuildClearLocks.exe index 17ce1d63723245106e344c5924256aeba8a4f6aa..a6dc01c7cd14174fc9e1d5e03de37bdfbc7db865 100644 GIT binary patch delta 97 zcmZqp!r1^sEsR^3BFcS@7!n!G7>pT`7%Uh}7%Ul5foxMC&k`t_#E=4Hn*zlwfpUfn jMnKvC$TtSEQh_3AK(-NsIS?BIO-W-g+P<`$DVhlY7!nzh7|a<`87vu0fNTg(Vn_o*bD+EhP$n5DZUJUN d*r^O=Ksf`Ts3}7VP&|pjkRfsV(sHI~CIBMt6SDvS diff --git a/build-tools/ESBuildWaitForCompletion.dll b/build-tools/ESBuildWaitForCompletion.dll index fa99932f82d3bf60782bfe03ebbd9cf01326f18f..f92155dc14d9fac4d55e1fcd240aa8676dce7f27 100644 GIT binary patch delta 239 zcmZqhY4DlQ!6KM{_~ynQ9}&is%{N7unFSu&D2j9>{BT&Cv}pJ3MQ2?%zY)L4sBV;K zW}IYUVwq}enq+B~lwxX{YH4CFL~43-S3K(;B6X9*NdVn_k9O@U&TKsiGOBOq-6F<3B|Fjz9A0@>BTEBQW201)G*csUBjdz0qsbW(F)YE1%im8vArT<3it%{kZu`}4 zo3x}iG4E|$Hd#Y*s{&N;n-NqHq+0L!mR~-{4VN#`o#?muuVgl>uMtBcgBgP{LlT1p zg9(ErLn@GM3glS=MUxm(fNWEsm?cookiiH@8vyynKvpVHBn`+mVlW3{W1uN%3`U!` I%6qc_04rQZ&j0`b delta 232 zcmZqhXz-ZO!6LNo!}N_kM??fF7Mpu|J-+U;s`=lf=eh}vn-fGIGO3#y8zm;0r&^jM z0%1~Gnz^w>YI355MJkX?H8VFzG)=KcGEAJDArZsU)!h4O@(GCmfz3|MT)$Gb`1W&t zY1GM7)}5>&xm5uwxb{3$5U9E+G;GBw!z;0odDY8yZT>5n&FX8)V9a2|kjRk4V9t=r zV98(tWJ7QgLmCj81LZA%GRZ)33or}9PGv9y${7GfO&L;v;zpT`7%Uh}7%Ul5foxMC&k`t_#E=4Hn*zlwfpUfn jMnKvC$TtSEQh_3AK(-NsIS?BIO-W-g+P<`$DVhlY7!nzh7|a<`87vu0fNTg(Vn_o*bD+EhP$n5DZUJUN d*r^O=Ksf`Ts3}7VP&|pjkRfsV(sHI~CIBMt6SDvS diff --git a/build-tools/GeoBlazorBuild.dll b/build-tools/GeoBlazorBuild.dll index e2f1c69a790d853d7af37ee7132b8858cd0b9f77..bf5e9b47f81a740216715173821f159b21802307 100644 GIT binary patch delta 250 zcmZqJ!_=^cX+j5!;br9w8+)Fov!yUtF~l%TzE!2Qd2vRsqJUCN{G6ms?fx@550+l9 zSiWGfLl?WcQKFe~l7)$7sYco2Qh)&C6K#=&bCNw;1r(frn(cWsS!VKj1*qT+SEwLRH7`fTyzOgv7oI-4 z#PH?j*ON0@eT^6r8O#`r8Il+*7)%%}8B&34Qy|Y0D4N8O0%V&4#Vmnxh73kP+5pHm c2C`CtB56Rj5ra7p8v{*AV=&shaArIU0RJRVZ2$lO delta 250 zcmZqJ!_=^cX+j4}T;h)z8+)Fov$-%>F~l%TzE!2Qd2vRsqQK(!tZ~g3o!z&x9dW&9 z#yf4XLl?Wcsj*RFl6k77Ng@y?rKOo0Tcjo@T3Dn4$y76QgGAF5izLIu&FK>Z8Ci_` z6n9VFGbup8eo4(u`OoeC)AzHk+GXu}ak9+h^$IXSai}0jb-v%TxxOKnrPgr>IBb4B zIg{1bl);$6h#`?7iNTy9mBEt11jvTqB!)B~GzZFC0A-Sa;uc^Qgq_M@29z@ZikdQ{ R0L7CS3>gwPFPs_A0swFjQKSF> diff --git a/build-tools/GeoBlazorBuild.exe b/build-tools/GeoBlazorBuild.exe index 39a1c6f5ec40986b75766f9db1f6e796d98962b1..910f3f7fd029f21631db0cca1c0316d239f27068 100644 GIT binary patch delta 97 zcmZqp!r1^sEsR^3T*`fo7!n!G7>pT`7%Uh}7%Ul5foxMC&k`t_#E=4Hn*zlwfpUfn jMnKvC$TtSEQh_3AK(-NsIS?BIO-W-g+CH(IDW3@d-Bc0u delta 97 zcmZqp!r1^sEsR^3T*`e-8H^c>7!nzh7|a<`87vu0fNTg(Vn_o*bD+EhP$n5DZUJUN d*r^O=Ksf`Ts3}7VP&|pjkRfsV#B!#5CIB1y6Q2M8 diff --git a/src/dymaptic.GeoBlazor.Core/dymaptic.GeoBlazor.Core.csproj b/src/dymaptic.GeoBlazor.Core/dymaptic.GeoBlazor.Core.csproj index d1e5c6e01..7211386f0 100644 --- a/src/dymaptic.GeoBlazor.Core/dymaptic.GeoBlazor.Core.csproj +++ b/src/dymaptic.GeoBlazor.Core/dymaptic.GeoBlazor.Core.csproj @@ -122,9 +122,9 @@ Include="$(BaseIntermediateOutputPath)$(Configuration)\$(TargetFramework)\generated\**\*.g.cs" /> - - - + + + diff --git a/test/dymaptic.GeoBlazor.Core.Test.Automation/GeoBlazorTestClass.cs b/test/dymaptic.GeoBlazor.Core.Test.Automation/GeoBlazorTestClass.cs index ff0a3aba2..d2fceb61e 100644 --- a/test/dymaptic.GeoBlazor.Core.Test.Automation/GeoBlazorTestClass.cs +++ b/test/dymaptic.GeoBlazor.Core.Test.Automation/GeoBlazorTestClass.cs @@ -51,11 +51,6 @@ await BrowserPool.GetInstance(BrowserType, _launchOptions!, TestConfig.BrowserPo } } - protected virtual Task<(string, BrowserTypeConnectOptions?)?> ConnectOptionsAsync() - { - return Task.FromResult<(string, BrowserTypeConnectOptions?)?>(null); - } - protected async Task RunTestImplementation(string testName, int retries = 0) { var page = await Context @@ -112,10 +107,10 @@ protected async Task RunTestImplementation(string testName, int retries = 0) if (!string.IsNullOrWhiteSpace(errors)) { + // these are typically browser console errors, the assertions in the + // test runner web app decides whether to fail the test or not + // so we just log here Trace.WriteLine(errors, "TEST_ERROR"); - await RetryOrMarkAsFailure(page, testName, new Exception(errors), retries); - - return; } Trace.WriteLine($"{testName} Passed", "TEST"); @@ -126,7 +121,7 @@ protected async Task RunTestImplementation(string testName, int retries = 0) var (messages, errors) = CheckMessages(testName); Trace.WriteLine(messages, "TEST_RESPONSE"); Trace.WriteLine(errors, "TEST_ERROR"); - await RetryOrMarkAsFailure(page, testName, ex, retries); + await RetryOrMarkAsFailure(page, testName, ex, retries, messages, errors); } finally { @@ -260,11 +255,12 @@ private void HandlePageError(object? pageObject, string message) _errorMessages[testName].Add(message); } - private async Task RetryOrMarkAsFailure(IPage page, string testName, Exception ex, int retries) + private async Task RetryOrMarkAsFailure(IPage page, string testName, Exception ex, int retries, + string messages, string errors) { if (retries > 2) { - TestConfig.FailedTests[testName] = $"{ex.Message}{Environment.NewLine}{ex.StackTrace}"; + TestConfig.FailedTests[testName] = $"{messages}{Environment.NewLine}{errors}"; Assert.Fail($"{testName} Exceeded the maximum number of retries."); } diff --git a/test/dymaptic.GeoBlazor.Core.Test.Automation/TestConfig.cs b/test/dymaptic.GeoBlazor.Core.Test.Automation/TestConfig.cs index d0c3998bf..80244fc3b 100644 --- a/test/dymaptic.GeoBlazor.Core.Test.Automation/TestConfig.cs +++ b/test/dymaptic.GeoBlazor.Core.Test.Automation/TestConfig.cs @@ -77,6 +77,8 @@ public static async Task AssemblyInitialize(TestContext testContext) Trace.WriteLine("Ctrl-C detected, initiating shutdown...", "TEST_SHUTDOWN"); e.Cancel = true; // Prevent immediate termination to allow cleanup + AssemblyCleanup().Wait(); + // Trigger cancellation if (!cts.IsCancellationRequested) { @@ -493,7 +495,30 @@ private static async Task RunUnitTests() var result = await Cli.Wrap(cmdLineApp) .WithArguments(args) - .WithStandardOutputPipe(PipeTarget.ToDelegate(output => Trace.WriteLine(output, "UNIT_TEST"))) + .WithStandardOutputPipe(PipeTarget.ToDelegate(output => + { + string trimmedLine = output.Trim(); + + if (trimmedLine.StartsWith("failed ", StringComparison.OrdinalIgnoreCase)) + { + string testName = output.Split(" ")[1]; + FailedTests.TryAdd(testName, output); + _filteredTests.Add(testName); + } + else if (trimmedLine.StartsWith("inconclusive ", StringComparison.OrdinalIgnoreCase)) + { + string testName = output.Split(" ")[1]; + InconclusiveTests.Add(testName); + _filteredTests.Add(testName); + } + else if (trimmedLine.StartsWith("passed ", StringComparison.OrdinalIgnoreCase)) + { + string testName = output.Split(" ")[1]; + _filteredTests.Add(testName); + } + + Trace.WriteLine(output, "UNIT_TEST"); + })) .WithStandardErrorPipe(PipeTarget.ToDelegate(output => Trace.WriteLine(output, "UNIT_TEST_ERROR"))) .WithValidation(CommandResultValidation.None) .ExecuteAsync(); @@ -509,6 +534,7 @@ private static async Task RunSourceGeneratorTests() var sgenFilePath = Path.Combine(_projectFolder, "..", "dymaptic.GeoBlazor.Core.SourceGenerator.Tests", "dymaptic.GeoBlazor.Core.SourceGenerator.Tests.csproj"); + var cmdLineApp = "dotnet"; string[] args = @@ -517,7 +543,7 @@ private static async Task RunSourceGeneratorTests() "--project", sgenFilePath, "-c", - "Release", + _runConfig!, "--output", "Detailed" ]; @@ -550,7 +576,30 @@ private static async Task RunSourceGeneratorTests() var result = await Cli.Wrap(cmdLineApp) .WithArguments(args) - .WithStandardOutputPipe(PipeTarget.ToDelegate(output => Trace.WriteLine(output, "SGEN_TEST"))) + .WithStandardOutputPipe(PipeTarget.ToDelegate(output => + { + string trimmedLine = output.Trim(); + + if (trimmedLine.StartsWith("failed ", StringComparison.OrdinalIgnoreCase)) + { + string testName = output.Split(" ")[1]; + FailedTests.TryAdd(testName, output); + _filteredTests.Add(testName); + } + else if (trimmedLine.StartsWith("inconclusive ", StringComparison.OrdinalIgnoreCase)) + { + string testName = output.Split(" ")[1]; + InconclusiveTests.Add(testName); + _filteredTests.Add(testName); + } + else if (trimmedLine.StartsWith("passed ", StringComparison.OrdinalIgnoreCase)) + { + string testName = output.Split(" ")[1]; + _filteredTests.Add(testName); + } + + Trace.WriteLine(output, "SGEN_TEST"); + })) .WithStandardErrorPipe(PipeTarget.ToDelegate(output => Trace.WriteLine(output, "SGEN_TEST_ERROR"))) .WithValidation(CommandResultValidation.None) .ExecuteAsync(); @@ -696,7 +745,8 @@ private static async Task StartTestApp() "/p:GenerateDocs=false", "/p:DebugSymbols=true", "/p:DebugType=portable", - "/p:UsePackageReference=false" + "/p:UsePackageReference=false", + "-v:d" ]; if (_cover) From 9834ef59d0f092c9f4e4f694efb07ef60e0096db Mon Sep 17 00:00:00 2001 From: Tim Purdum Date: Sun, 25 Jan 2026 10:42:40 -0600 Subject: [PATCH 10/89] move prologiccomponents to model folder and add tests --- ...ymaptic.GeoBlazor.Core.Test.Automation.sln | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 test/dymaptic.GeoBlazor.Core.Test.Automation/dymaptic.GeoBlazor.Core.Test.Automation.sln diff --git a/test/dymaptic.GeoBlazor.Core.Test.Automation/dymaptic.GeoBlazor.Core.Test.Automation.sln b/test/dymaptic.GeoBlazor.Core.Test.Automation/dymaptic.GeoBlazor.Core.Test.Automation.sln new file mode 100644 index 000000000..c0969385e --- /dev/null +++ b/test/dymaptic.GeoBlazor.Core.Test.Automation/dymaptic.GeoBlazor.Core.Test.Automation.sln @@ -0,0 +1,24 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.2.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "dymaptic.GeoBlazor.Core.Test.Automation", "dymaptic.GeoBlazor.Core.Test.Automation.csproj", "{3DE986DC-55E7-49C2-6D4C-92505ADBB85E}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {3DE986DC-55E7-49C2-6D4C-92505ADBB85E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3DE986DC-55E7-49C2-6D4C-92505ADBB85E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3DE986DC-55E7-49C2-6D4C-92505ADBB85E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3DE986DC-55E7-49C2-6D4C-92505ADBB85E}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {01D5C58B-3A8D-4DA7-A178-969129B5A64B} + EndGlobalSection +EndGlobal From a144f4f109d5437bd129c222d3115a3275091a75 Mon Sep 17 00:00:00 2001 From: Tim Purdum Date: Sun, 25 Jan 2026 13:26:13 -0600 Subject: [PATCH 11/89] fix tests --- build-scripts/ConsoleDialog.cs | 2 +- build-scripts/ESBuild.cs | 20 +-- build-scripts/ScriptBuilder.cs | 2 +- build-tools/BuildAppSettings.dll | Bin 10240 -> 10240 bytes build-tools/BuildAppSettings.exe | Bin 162304 -> 162304 bytes build-tools/BuildTemplates.dll | Bin 25088 -> 25088 bytes build-tools/BuildTemplates.exe | Bin 162304 -> 162304 bytes build-tools/ConsoleDialog.dll | Bin 11264 -> 11264 bytes build-tools/ConsoleDialog.exe | Bin 162304 -> 162304 bytes build-tools/ESBuild.dll | Bin 19456 -> 19456 bytes build-tools/ESBuild.exe | Bin 162304 -> 162304 bytes build-tools/ESBuildClearLocks.dll | Bin 8192 -> 8192 bytes build-tools/ESBuildClearLocks.exe | Bin 162304 -> 162304 bytes build-tools/ESBuildWaitForCompletion.dll | Bin 9728 -> 9728 bytes build-tools/ESBuildWaitForCompletion.exe | Bin 162816 -> 162816 bytes build-tools/FetchNuGetVersion.dll | Bin 9216 -> 9216 bytes build-tools/FetchNuGetVersion.exe | Bin 162304 -> 162304 bytes build-tools/GeoBlazorBuild.dll | Bin 40448 -> 40448 bytes build-tools/GeoBlazorBuild.exe | Bin 162304 -> 162304 bytes .../Scripts/featureLayer.ts | 10 +- .../Scripts/geoBlazorCore.ts | 6 +- .../Scripts/topFeaturesQuery.gb.ts | 6 +- .../Scripts/topFeaturesQuery.ts | 8 +- .../GenerateTests.cs | 12 +- .../TestConfig.cs | 136 ++++++++++-------- 25 files changed, 112 insertions(+), 90 deletions(-) diff --git a/build-scripts/ConsoleDialog.cs b/build-scripts/ConsoleDialog.cs index 2282349e4..62b910f2f 100644 --- a/build-scripts/ConsoleDialog.cs +++ b/build-scripts/ConsoleDialog.cs @@ -91,7 +91,7 @@ void ShowOrUpdateConsole(string title, string message) { // Append message to the temp file string timestamp = DateTime.Now.ToString("HH:mm:ss"); - string logLine = $"[{timestamp}] {title}: {message}{Environment.NewLine}"; + string logLine = $"[{timestamp}] {message}{Environment.NewLine}"; File.AppendAllText(_consoleTempFile, logLine); } diff --git a/build-scripts/ESBuild.cs b/build-scripts/ESBuild.cs index 17132d36c..379a9d8b0 100644 --- a/build-scripts/ESBuild.cs +++ b/build-scripts/ESBuild.cs @@ -139,23 +139,11 @@ File.Delete(proRecordFilePath); } -string currentBranch = GetCurrentGitBranch(coreSourceDir); - +string currentBranch = GetCurrentGitBranch(sourceDir); bool needsBuild = CheckIfNeedsBuild( - coreRecordFilePath, + recordFilePath, currentBranch, - coreScriptsDir, - coreOutputDir); - -if (pro) -{ - currentBranch = GetCurrentGitBranch(proSourceDir); - needsBuild = CheckIfNeedsBuild( - proRecordFilePath, - currentBranch, - proScriptsDir, - proOutputDir); -} + scriptsDir, outputDir); if (!needsBuild) { @@ -502,7 +490,7 @@ static bool GetScriptsModifiedSince(string scriptsDir, long lastTimestamp) foreach (string file in Directory.GetFiles(scriptsDir, "*", SearchOption.AllDirectories)) { - if (File.GetLastWriteTime(file) > lastBuildTime) + if (File.GetLastWriteTimeUtc(file) > lastBuildTime) { return true; } diff --git a/build-scripts/ScriptBuilder.cs b/build-scripts/ScriptBuilder.cs index 1232fc95a..0662d2fbb 100644 --- a/build-scripts/ScriptBuilder.cs +++ b/build-scripts/ScriptBuilder.cs @@ -320,7 +320,7 @@ static bool GetScriptsModifiedSince(string scriptsDir, long lastTimestamp) foreach (string file in Directory.GetFiles(scriptsDir, "*", SearchOption.AllDirectories)) { - if (File.GetLastWriteTime(file) > lastBuildTime) + if (File.GetLastWriteTimeUtc(file) > lastBuildTime) { return true; } diff --git a/build-tools/BuildAppSettings.dll b/build-tools/BuildAppSettings.dll index 70f5293d8aef6471f47576b1d77a5c448728c06c..8421b5db22803f71dd3e8de2563ad1e7dc50ce32 100644 GIT binary patch delta 235 zcmZn&Xb70l!Ls(?%ZD3#45S6Z#b53-)U$Ei96xEDXq~a$<}=dmj2e~}#wMw0rj{uN zX$F=?$(Cs*sU~S=mO!y21M}20GlNtE152}%q{-E?vMe42)z2o+kqr>|UtKZP@7hZ5 zMLqA1zNl7Cn*2w0vjS9b_9LhuNcFv+*-GkDOZPb)zG1)lyxdF{e@g}n24e;jhE#?$ z22%!0h7<+^Aj<&AGGa&ul4)Q$6CiB{3m; delta 235 zcmZn&Xb70l!E(^|#G#En2GRm42hMgYe*EasU}LK z3kDMgONLY++Z4#N1d1jxqyX8bKru_8oFRh|kTwAFje)FGphy~!ZNy*>#Ku5V(txz# J=IzR!EC4UqN~Qn+ diff --git a/build-tools/BuildAppSettings.exe b/build-tools/BuildAppSettings.exe index 9e6097dacf23a24a3fe4a5223ca08d9c4184b2d0..229401beb13769fcd7c711135ca987aac8a19c6c 100644 GIT binary patch delta 99 zcmZqp!r1^sEsR^3BFgb8PXU`87vu67z}_c10c(YAsI-ff#pnqv>A{G dQI`aiHwWu51F}+q7^L0|s5fc*@^Yp~CIBaH6GH$1 delta 99 zcmZqp!r1^sEsR^3BFg=Z7!n!G7>pT`7%Uh}7%Ul5foxMC&k`t_#E=4Hn*zlwfpUfn kMnKvC$TtSEQh_3AK(-NsIS?BIO-TdNhTE5yGet5100s;aI{*Lx diff --git a/build-tools/BuildTemplates.dll b/build-tools/BuildTemplates.dll index 382378c1f1f32587a4cbf99d1bb02f0b01146c5c..7bb7243861de2a7912434659a4bb2583b59e3c23 100644 GIT binary patch delta 253 zcmZoT!q{+xaY6@+-R>srjP zrgOt)jer36M4e@*wJxfb!;G W9cDmQDiDLzn*sGEZC)H3%K`xEmQkPp delta 253 zcmZoT!q{+xaY6^n@208)8+*>#v!yUtF~l%Te(0yRxy@k`zd(?V6MrUajK}O4i>%W} z9v$AS5fHlxmV@YGiI?oS0^0IC)Lz z2^KNqx-XL}!U6v*NemVYCJdGgsX(?VkY@=LO=3s^vQ2?vmOwc}1|uMC0OT73 bS*bvgG$7lE!5oNb8PXU`87vu67z}_c10c(YAsI-ff#pnqv>A{G dQI`aiHwWu51F}+q7^L0|s5fc*pT`7%Uh}7%Ul5foxMC&k`t_#E=4Hn*zlwfpUfn kMnKvC$TtSEQh_3AK(-NsIS?BIO-TdNhTA8XGvzV?0RG_;CjbBd diff --git a/build-tools/ConsoleDialog.dll b/build-tools/ConsoleDialog.dll index bcb54b8438ed161decf1f62a0c5f80b7bb27fbed..8f6ec866274679ebbdd37256a8c4ba8d93f028dc 100644 GIT binary patch delta 1084 zcmbtSUr19?82`@Qy?1k)rPHRHqRzQC|G{<6ZP%h4l-fgPq^J*p&21J4#W5dx5G^5; z^3OP6AwJaJ#3J)jgb@)@Pzb#w2|lC`K@XAz2|X<8yLVHAdh5b>zTfZn{W;$`_Y8Lq zcTSD+Z-l^d{AMAUj?ZU4vn1icgUBpdh*;O)BG7tjoYsQrW0n8N$tN;WZ zn#f)_C8d=!aDoFV$4+NhSwBBWdf7KVm$TWJUMOeBgl-`d!&WvYToa!HT%ylejc7af z3ZcghV6<>Lk5}_lM|L%8+QYkX*PceZ{=j2liS_LbfA7SEa=y5k^T@$kUo=)HN2Hh}dwb-V zFY1f=Wz@n_AR6;aQAv{hkuY0zv>ixx-n#+hWGjbTE}?Zdps`SCo?yqPV`pbeomrcO zKiZEbe>Fa9Rqh_|*cxY3rT4vQ@$t{Jh>c85+wbSzviZZC4_*f@VEN8BqT-Uc!%7C& zb>}FPxI$%LnOAp8h9J~}526r*I*=g(5+Vta7kY4wX)zyqKeAL_7~=u0!jCA5rsn&x UHmpp#uW;P$8YR18i^L`UZ)l9}DF6Tf delta 1132 zcmbtSUr3Wt6hHUw+qXGa%_%mW>E@i9PRV`SH#hr(LggYeAtenYHk^Z4?m6dP&Uo*5 z@7=1$tc`-q1u@~b52NVkEuqIgo!gQFh6SPDRp z;}-d2SCi63jg-i|Psorfd_yRq_Hu5}*O_!)3BY6(*(+YFAJRMVLOs8)zb3u`xX9k~ zePUVcBSNo74Wqem$_mJq#)`J_mfyFnv2#1{#5NYQr73&KGh(H61wF$Du!@_lAv7>2 z{~$(_S|4tTzA*Ys_S=>CDt9T{kJ7YzuWs6HV-p%K6Q|wf%%(%#+lH})tQE0=P2uUq z|1g>9NJPy)(xRSh8ueVk7YL2aiD|UJ;e%>G>F^-{e#ap9|3?qy`F>wx3GA1B)2xmC z&}`7! zka}1B35=`V8E46f*1X8y8h;a}EEcR0GQ}Sj&Xs)^09-w%AzTb#!O3#9_Z&ZhHmaOA zH`C<23o%@WPIL-jtI4uaWO}f;vn+#5gm*c%fXo0(!mJkG0~iMvz+sE&c_l4+!DBg?L-F%VJwzUNZWG6O%o=X5|Z?Q~Xl(v&NjbayBPo zYB^Q${8+C$W0~1#pS^+C^Xn1sblFLo54lpx?ixvVN`e@KAOLX)Ll7d6KrADRpc;n( s#4>6T^!Y(T+k`xTD1pi#VhNgY2QbSZTE8;kxlHN2Un#0zC+Q}`Uv7=$XaE2J diff --git a/build-tools/ConsoleDialog.exe b/build-tools/ConsoleDialog.exe index 2ab1e2d7b4f72ef5f7ef7e0a124aee55a621186f..1ec1433de9a541d7744f00ce9a805a17617d2965 100644 GIT binary patch delta 99 zcmZqp!r1^sEsR^349fj287vr#8B7>b8PXU`87vu67z}_c10c(YAsI-ff#pnqv>A{G dQI`aiHwWu51F}+q7^L0|s5fbQeK}Jj695@D6B_^k delta 99 zcmZqp!r1^sEsR^349fkD7!n!G7>pT`7%Uh}7%Ul5foxMC&k`t_#E=4Hn*zlwfpUfn jMnKvC$TtSEQh_3AK(-NsIS?BIO-TdNhTH4QnHre@_r($u diff --git a/build-tools/ESBuild.dll b/build-tools/ESBuild.dll index 119ad3bf1b194344403c4f74e06fadf5e787cc60..70d2889cf5721450e928be97a6182d0c248957bd 100644 GIT binary patch delta 3682 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 delta 3727 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 delta 99 zcmZqp!r1^sEsR^3zLfbJF(fjWF&Hx>F<3B|Fjz9A0@j4Yxa&Gwo#p06HlW)&Kwi diff --git a/build-tools/ESBuildClearLocks.dll b/build-tools/ESBuildClearLocks.dll index 93d09cf9d0a7b30b4dcfecd982f21fdab4ec4bea..0db7ca9493d113ead7992123066b240b098a946d 100644 GIT binary patch delta 242 zcmZp0XmFU&!Lo44^)(xNlm!`GHg6CNU=q0b{m62SQ!l;tA6?D1@|o!S%>p9-7&R;{ zj7?J0Of6Fk(hMw(k}cCrQccp#EP-N42Ii@0W(KJS29{~BNiZV zuVA7h%b(NUbCz8Ub~`pFV)7ZWtqM@V-!MUtYLneJ=YJ{B%Tat=b$|0h@#DPpsFbQa9HbwDl-sL;3rIRDp+GojT0g-=<8b*m` z#z__?mZ`?3NtR|wDW;~WmL`TqmIkKAMyV!grbgyQ#))Z0hLg?2j98qHf4)08M=U_# z^zPmu^YRME#<~F-7 z$Y91`%#g%j!C=B*$&d3K+zCaC}s(iGh{FV(gr}jF_4uC6iEZJjTp>< R*cfO^8jv>Jyj|Lp1ptB_NiqNc diff --git a/build-tools/ESBuildClearLocks.exe b/build-tools/ESBuildClearLocks.exe index a6dc01c7cd14174fc9e1d5e03de37bdfbc7db865..659e4ba9774651c14fb24d18c78f854cae11b11b 100644 GIT binary patch delta 99 zcmZqp!r1^sEsR^3BFgb8PXU`87vu67z}_c10c(YAsI-ff#pnqv>A{G dQI`aiHwWu51F}+q7^L0|s5fc*@^Yp~CIBaH6GH$1 delta 99 zcmZqp!r1^sEsR^3BFg=Z7!n!G7>pT`7%Uh}7%Ul5foxMC&k`t_#E=4Hn*zlwfpUfn kMnKvC$TtSEQh_3AK(-NsIS?BIO-TdNhTE5yGet5100s;aI{*Lx diff --git a/build-tools/ESBuildWaitForCompletion.dll b/build-tools/ESBuildWaitForCompletion.dll index f92155dc14d9fac4d55e1fcd240aa8676dce7f27..e613ff689f35756c0692452f431f9c8e749dca46 100644 GIT binary patch delta 242 zcmZqhY4DlQ!Ls`5zs8L{J|c`Rn{SFRGYc#bojv8@qAG`Zi+-&;lJ4+p^BeJtj2e~} z#wMw0rj{uNX$F=?$(Cs*sU~S=mO!y21M}20GlNtE152}%q{&YuwOLAJUN4v|A{8JI zf2UV(quX}x36JxF^ry*WO)il-tN<0<4HE>aR#Zh-mri8yw`8zj zFlI1eNM%T4FlDf0NMSGlvJ8MMBZg!knFf|K0n%nb9z{BT&Cv}pJ3MQ2?%zY)L4s9}_7 zW}IYUVwq}enq+B~lwxX{YH4Cv*NemVYCJdGgsX(?VkY@=LO=3s^vQ2?vmOwc}1|uMC0OT73S*bvgG$7lE!5oN< Pfu^JZX~WIz3O`u^oz+XY diff --git a/build-tools/ESBuildWaitForCompletion.exe b/build-tools/ESBuildWaitForCompletion.exe index 6dbb66ed275cde3bd90c554add1dc70784c2adf7..cfbaa34be684e8d1d7fffb67dc698307887a7187 100644 GIT binary patch delta 99 zcmZqp!Px*rEsR^3R+Rf&GFUJeGng=>GNdt>GFURCFc<(?20)e(Lo$#|1Iw8JX)_=X dqAm$2Zw}UB24tlIF-W}`P;b)q@8wLKEC5F26P*A6 delta 99 zcmZqp!Px*rEsR^3R+Rf2F(fjWF&Hx>F<3B|Fjz9A0@j4Yz+UXX0c5040JFl>h($ diff --git a/build-tools/FetchNuGetVersion.dll b/build-tools/FetchNuGetVersion.dll index 522817a984fbd29bd1d4d6b16a8b7c85be3eb2d4..a2829b8ce5de30a116f16332e1240d11c1a9ade2 100644 GIT binary patch delta 235 zcmZqhXz-ZO!IFDR;nc>SBO(Gj_e}}>IA^2hs%3KXf|TYw+ngZ!kV(VR!q_A=&D1i* zAkDziDA_X2B-JF%%n~S;WMH0}W@eCTU|?yMk~BF}BAR8ld-v(dCnN#{zTNWk`F~N- zab@M+?!{+r&YP?uxm5uw7?=qa1gSow`9d*Z_a`0yy?XYW|4L@F`dczsFc>qKFr+f1 zF_w CSWnsj delta 235 zcmZqhXz-ZO!D3*}*0r(ch={>BTEBQW201)G*csUBjdz0Bg4s=645NdjLY9oJ|Phxu!`|`<8J%a zZkx2EH!<&RTsB!la;pMV@S71-5Tsh~`IcWk#|@V+(VghG`LAR)tG^LLB7+%&F+&oA z1%nBLB||EZZ3^UB0!5P;Qh;nzpqM34&XB5wX+YX= J^LBYp769;SM>GHc diff --git a/build-tools/FetchNuGetVersion.exe b/build-tools/FetchNuGetVersion.exe index d444d46d43398e10c336db0548e10eef69d8db3b..47f543af2d796eaa3ece54dd6b320e5519b0ac68 100644 GIT binary patch delta 99 zcmZqp!r1^sEsR^3BFgb8PXU`87vu67z}_c10c(YAsI-ff#pnqv>A{G dQI`aiHwWu51F}+q7^L0|s5fc*@^Yp~CIBaH6GH$1 delta 99 zcmZqp!r1^sEsR^3BFg=Z7!n!G7>pT`7%Uh}7%Ul5foxMC&k`t_#E=4Hn*zlwfpUfn kMnKvC$TtSEQh_3AK(-NsIS?BIO-TdNhTE5yGet5100s;aI{*Lx diff --git a/build-tools/GeoBlazorBuild.dll b/build-tools/GeoBlazorBuild.dll index bf5e9b47f81a740216715173821f159b21802307..f20dd00a74cdcc9e4c0257567a944d8f7988a510 100644 GIT binary patch delta 253 zcmZqJ!_=^cX+j6fqrMqeHugMEXLDh&Vu)dwe5*=p^WuzNMFG1qKFr+f1F_g>#ZUS_KrGf12%iG+AcydIhN94OgfjP&F?{#=Pxoco&{N zy2SA1=GT)mS^bR|5*f@Gj2V&`EEr4}EE!UPY*Qf55-6I)kOE|z0>vzWa)t~>K-vJv eHwLm&fg))@wh@Cl5E}zcNdwY`n-|ZFWdQ))@KDkK diff --git a/build-tools/GeoBlazorBuild.exe b/build-tools/GeoBlazorBuild.exe index 910f3f7fd029f21631db0cca1c0316d239f27068..9d38ea789c9a6725ca9e20f7111e49cb9049e92c 100644 GIT binary patch delta 99 zcmZqp!r1^sEsR^3T+00|87vr#8B7>b8PXU`87vu67z}_c10c(YAsI-ff#pnqv>A{G dQI`aiHwWu51F}+q7^L0|s5fc*pT`7%Uh}7%Ul5foxMC&k`t_#E=4Hn*zlwfpUfn kMnKvC$TtSEQh_3AK(-NsIS?BIO-TdNhTA8XGvzV?0RG_;CjbBd diff --git a/src/dymaptic.GeoBlazor.Core/Scripts/featureLayer.ts b/src/dymaptic.GeoBlazor.Core/Scripts/featureLayer.ts index f114546ac..96ed6d5ed 100644 --- a/src/dymaptic.GeoBlazor.Core/Scripts/featureLayer.ts +++ b/src/dymaptic.GeoBlazor.Core/Scripts/featureLayer.ts @@ -23,8 +23,8 @@ import { } from './geoBlazorCore'; import Graphic from "@arcgis/core/Graphic"; import {buildDotNetPopupTemplate} from './popupTemplate'; -import CreatePopupTemplateOptions = __esri.CreatePopupTemplateOptions; import {buildJsGraphic} from "./graphic"; +import CreatePopupTemplateOptions = __esri.CreatePopupTemplateOptions; export default class FeatureLayerWrapper extends FeatureLayerGenerated { @@ -141,7 +141,7 @@ export default class FeatureLayerWrapper extends FeatureLayerGenerated { queryId: string): Promise { try { let { buildJsTopFeaturesQuery} = await import('./topFeaturesQuery'); - let jsQuery = await buildJsTopFeaturesQuery(query, this.layerId, this.viewId); + let jsQuery = await buildJsTopFeaturesQuery(query); let featureSet = await this.layer.queryTopFeatures(jsQuery, options); let {buildDotNetFeatureSet} = await import('./featureSet'); let dotNetFeatureSet = await buildDotNetFeatureSet(featureSet, this.geoBlazorId, this.viewId); @@ -159,20 +159,20 @@ export default class FeatureLayerWrapper extends FeatureLayerGenerated { async queryTopFeatureCount(query: DotNetTopFeaturesQuery, options: any): Promise { let { buildJsTopFeaturesQuery} = await import('./topFeaturesQuery'); - let jsQuery = await buildJsTopFeaturesQuery(query, this.layerId, this.viewId); + let jsQuery = await buildJsTopFeaturesQuery(query); return await this.layer.queryTopFeatureCount(jsQuery, options); } async queryTopObjectIds(query: DotNetTopFeaturesQuery, options: any): Promise { let { buildJsTopFeaturesQuery} = await import('./topFeaturesQuery'); - let jsQuery = await buildJsTopFeaturesQuery(query, this.layerId, this.viewId); + let jsQuery = await buildJsTopFeaturesQuery(query); let result = await this.layer.queryTopObjectIds(jsQuery, options); return result; } async queryTopFeaturesExtent(query: DotNetTopFeaturesQuery, options: any): Promise { let { buildJsTopFeaturesQuery} = await import('./topFeaturesQuery'); - let jsQuery = await buildJsTopFeaturesQuery(query, this.layerId, this.viewId); + let jsQuery = await buildJsTopFeaturesQuery(query); let result = await this.layer.queryTopFeaturesExtent(jsQuery, options); let {buildDotNetExtent} = await import('./extent'); return { diff --git a/src/dymaptic.GeoBlazor.Core/Scripts/geoBlazorCore.ts b/src/dymaptic.GeoBlazor.Core/Scripts/geoBlazorCore.ts index 88d7fb70a..84a81d06a 100644 --- a/src/dymaptic.GeoBlazor.Core/Scripts/geoBlazorCore.ts +++ b/src/dymaptic.GeoBlazor.Core/Scripts/geoBlazorCore.ts @@ -1,10 +1,10 @@ import { + actionHandlers, addArcGisLayer, - graphicsRefs, buildArcGisMapView, - popupTemplateRefs, - actionHandlers, esriConfig, + graphicsRefs, + popupTemplateRefs, resetMapComponent } from './arcGisJsInterop'; import AuthenticationManager from "./authenticationManager"; diff --git a/src/dymaptic.GeoBlazor.Core/Scripts/topFeaturesQuery.gb.ts b/src/dymaptic.GeoBlazor.Core/Scripts/topFeaturesQuery.gb.ts index 5fdaeaeae..d045e6983 100644 --- a/src/dymaptic.GeoBlazor.Core/Scripts/topFeaturesQuery.gb.ts +++ b/src/dymaptic.GeoBlazor.Core/Scripts/topFeaturesQuery.gb.ts @@ -1,8 +1,8 @@ // File auto-generated by dymaptic tooling. Any changes made here will be lost on future generation. To override functionality, use the relevant root .ts file. import TopFeaturesQuery from '@arcgis/core/rest/support/TopFeaturesQuery'; -import { arcGisObjectRefs, jsObjectRefs, hasValue, removeCircularReferences } from './geoBlazorCore'; +import {arcGisObjectRefs, hasValue, jsObjectRefs, removeCircularReferences} from './geoBlazorCore'; -export async function buildJsTopFeaturesQueryGenerated(dotNetObject: any, layerId: string | null, viewId: string | null): Promise { +export async function buildJsTopFeaturesQueryGenerated(dotNetObject: any): Promise { if (!hasValue(dotNetObject)) { return null; } @@ -78,7 +78,7 @@ export async function buildJsTopFeaturesQueryGenerated(dotNetObject: any, layerI } -export async function buildDotNetTopFeaturesQueryGenerated(jsObject: any, layerId: string | null, viewId: string | null): Promise { +export async function buildDotNetTopFeaturesQueryGenerated(jsObject: any): Promise { if (!hasValue(jsObject)) { return null; } diff --git a/src/dymaptic.GeoBlazor.Core/Scripts/topFeaturesQuery.ts b/src/dymaptic.GeoBlazor.Core/Scripts/topFeaturesQuery.ts index 3d0e4c7ae..3e54ba388 100644 --- a/src/dymaptic.GeoBlazor.Core/Scripts/topFeaturesQuery.ts +++ b/src/dymaptic.GeoBlazor.Core/Scripts/topFeaturesQuery.ts @@ -1,11 +1,11 @@ // override generated code in this file -export async function buildJsTopFeaturesQuery(dotNetObject: any, layerId: string | null, viewId: string | null): Promise { +export async function buildJsTopFeaturesQuery(dotNetObject: any): Promise { let {buildJsTopFeaturesQueryGenerated} = await import('./topFeaturesQuery.gb'); - return await buildJsTopFeaturesQueryGenerated(dotNetObject, layerId, viewId); + return await buildJsTopFeaturesQueryGenerated(dotNetObject); } -export async function buildDotNetTopFeaturesQuery(jsObject: any, layerId: string | null, viewId: string | null): Promise { +export async function buildDotNetTopFeaturesQuery(jsObject: any): Promise { let {buildDotNetTopFeaturesQueryGenerated} = await import('./topFeaturesQuery.gb'); - return await buildDotNetTopFeaturesQueryGenerated(jsObject, layerId, viewId); + return await buildDotNetTopFeaturesQueryGenerated(jsObject); } diff --git a/test/dymaptic.GeoBlazor.Core.Test.Automation.SourceGeneration/GenerateTests.cs b/test/dymaptic.GeoBlazor.Core.Test.Automation.SourceGeneration/GenerateTests.cs index 9f449b4cf..3c3f8e62c 100644 --- a/test/dymaptic.GeoBlazor.Core.Test.Automation.SourceGeneration/GenerateTests.cs +++ b/test/dymaptic.GeoBlazor.Core.Test.Automation.SourceGeneration/GenerateTests.cs @@ -109,7 +109,15 @@ private void Generate(SourceProductionContext context, ImmutableArray[A-Za-z0-9_]*.*?)$", RegexOptions.Compiled); private static readonly Regex classDeclarationRegex = new(@"^public class (?[A-Za-z0-9_]+)\s*?:?.*?$", RegexOptions.Compiled); + private static readonly Regex nameofRegex = + new(@"nameof\((?[A-Za-z0-9_]+)\)", RegexOptions.Compiled); } \ No newline at end of file diff --git a/test/dymaptic.GeoBlazor.Core.Test.Automation/TestConfig.cs b/test/dymaptic.GeoBlazor.Core.Test.Automation/TestConfig.cs index 80244fc3b..057eaa469 100644 --- a/test/dymaptic.GeoBlazor.Core.Test.Automation/TestConfig.cs +++ b/test/dymaptic.GeoBlazor.Core.Test.Automation/TestConfig.cs @@ -74,10 +74,26 @@ public static async Task AssemblyInitialize(TestContext testContext) // Handle Ctrl-C gracefully Console.CancelKeyPress += (sender, e) => { - Trace.WriteLine("Ctrl-C detected, initiating shutdown...", "TEST_SHUTDOWN"); e.Cancel = true; // Prevent immediate termination to allow cleanup + Trace.WriteLine("Ctrl-C detected, initiating shutdown...", "TEST_SHUTDOWN"); + + _ = Task.Run(AssemblyCleanup); + + var timeoutSeconds = 30; + + while (!_cleanupComplete && (timeoutSeconds > 0)) + { + Thread.Sleep(1000); + timeoutSeconds--; + } - AssemblyCleanup().Wait(); + if (_cleanupComplete) + { + Trace.WriteLine("Shutdown complete", "TEST_SHUTDOWN"); + Environment.Exit(1); + + return; + } // Trigger cancellation if (!cts.IsCancellationRequested) @@ -90,10 +106,10 @@ public static async Task AssemblyInitialize(TestContext testContext) gracefulCts.Cancel(); } - // Force exit after timeout if cleanup hangs - Task.Run(async () => + _ = Task.Run(async () => { - await Task.Delay(15000); // 15 second timeout for cleanup + // Force exit after timeout if cleanup hangs + await Task.Delay(15000); // an extra 15 second timeout for cleanup Trace.WriteLine("Cleanup timeout - forcing exit", "TEST_SHUTDOWN"); Environment.Exit(1); }); @@ -138,78 +154,85 @@ public static async Task AssemblyInitialize(TestContext testContext) [AssemblyCleanup] public static async Task AssemblyCleanup() { - var isCancelled = cts.IsCancellationRequested; - - // Dispose browser pool first - if (BrowserPool.TryGetInstance(out var pool) && pool is not null) + try { - Trace.WriteLine("Disposing browser pool...", "TEST_CLEANUP"); + var isCancelled = cts.IsCancellationRequested; - try + // Dispose browser pool first + if (BrowserPool.TryGetInstance(out var pool) && pool is not null) { - using var timeoutCts = new CancellationTokenSource(TimeSpan.FromSeconds(isCancelled ? 3 : 10)); - await pool.DisposeAsync().ConfigureAwait(false); - Trace.WriteLine("Browser pool disposed", "TEST_CLEANUP"); + Trace.WriteLine("Disposing browser pool...", "TEST_CLEANUP"); + + try + { + using var timeoutCts = new CancellationTokenSource(TimeSpan.FromSeconds(isCancelled ? 3 : 10)); + await pool.DisposeAsync().ConfigureAwait(false); + Trace.WriteLine("Browser pool disposed", "TEST_CLEANUP"); + } + catch (Exception ex) + { + Trace.WriteLine($"Browser pool disposal error: {ex.Message}", "TEST_CLEANUP"); + } } - catch (Exception ex) + + if (!isCancelled) { - Trace.WriteLine($"Browser pool disposal error: {ex.Message}", "TEST_CLEANUP"); + cts.CancelAfter(5000); } - } - if (!isCancelled) - { - cts.CancelAfter(5000); - } + if (!gracefulCts.IsCancellationRequested) + { + await gracefulCts.CancelAsync(); + } - if (!gracefulCts.IsCancellationRequested) - { - await gracefulCts.CancelAsync(); - } + // Shorter delay if already cancelled + await Task.Delay(isCancelled ? 1000 : 5000); - // Shorter delay if already cancelled - await Task.Delay(isCancelled ? 1000 : 5000); + if (_useContainer) + { + await StopContainer(ComposeFilePath); - if (_useContainer) - { - await StopContainer(ComposeFilePath); + if (_cover) + { + CopyCoverageFromContainer(); + } + } + else + { + await StopTestApp(); + } if (_cover) { - CopyCoverageFromContainer(); + await GenerateCoverageReport(); } - } - else - { - await StopTestApp(); - } - if (_cover) - { - await GenerateCoverageReport(); - } + Trace.WriteLine("-------------------------------------------------------"); + Trace.WriteLine("Test run complete", "FINAL_SUMMARY"); + var passedTestCount = _filteredTests.Count - FailedTests.Count - InconclusiveTests.Count; + Trace.WriteLine($"{passedTestCount} / {_filteredTests.Count} tests passed.", "FINAL_SUMMARY"); + Trace.WriteLine("Inconclusive Tests:", "FINAL_SUMMARY"); - Trace.WriteLine("-------------------------------------------------------"); - Trace.WriteLine("Test run complete", "FINAL_SUMMARY"); - int passedTestCount = _filteredTests.Count - FailedTests.Count - InconclusiveTests.Count; - Trace.WriteLine($"{passedTestCount} / {_filteredTests.Count} tests passed.", "FINAL_SUMMARY"); - Trace.WriteLine("Inconclusive Tests:", "FINAL_SUMMARY"); + foreach (var inconclusive in InconclusiveTests) + { + Trace.WriteLine($"- {inconclusive}", "FINAL_SUMMARY"); + } - foreach (string inconclusive in InconclusiveTests) - { - Trace.WriteLine($"- {inconclusive}", "FINAL_SUMMARY"); - } + Trace.WriteLine("-------------------------------------------------------"); + Trace.WriteLine("Failed Tests:", "FINAL_SUMMARY"); - Trace.WriteLine("-------------------------------------------------------"); - Trace.WriteLine("Failed Tests:", "FINAL_SUMMARY"); + foreach (var failedTest in FailedTests) + { + Trace.WriteLine($"- {failedTest.Key}: {Environment.NewLine}{failedTest.Value}", + "FINAL_SUMMARY"); + } - foreach (KeyValuePair failedTest in FailedTests) + await File.WriteAllTextAsync(LogFilePath, logBuilder.ToString()); + } + finally { - Trace.WriteLine($"- {failedTest.Key}: {Environment.NewLine}{failedTest.Value}", - "FINAL_SUMMARY"); + _cleanupComplete = true; } - - await File.WriteAllTextAsync(LogFilePath, logBuilder.ToString()); } private static void SetupConfiguration() @@ -1087,6 +1110,7 @@ await Cli.Wrap("reportgenerator") private static int? _testProcessId; private static bool _useContainer; private static bool _cover; + private static bool _cleanupComplete; private static string _coverageFormat = string.Empty; private static string _coverageFileVersion = string.Empty; private static string? _reportGenLicenseKey; From 52bb81e8909d3f9205c4c617228818811ba2e343 Mon Sep 17 00:00:00 2001 From: Tim Purdum Date: Wed, 28 Jan 2026 14:30:12 -0600 Subject: [PATCH 12/89] Fix/update pro logic components --- build-scripts/GeoBlazorCover.cs | 58 +++ .../ProcessHelper.cs | 109 ++++-- .../ProtobufDefinitionsGenerator.cs | 8 +- .../SerializationGenerator.cs | 12 +- .../ESBuildGenerator.cs | 169 ++++----- .../ProtobufSourceGenerator.cs | 62 ++-- .../Components/ColorVariable.gb.cs | 2 +- .../Components/Layers/Layer.cs | 341 +++++++++--------- .../Components/LocationService.gb.cs | 15 - .../Components/MapComponent.razor.cs | 3 +- .../Components/OpacityVariable.gb.cs | 2 +- .../Components/Portal.gb.cs | 99 ----- .../Components/PortalFeaturedGroups.cs | 6 - .../Components/PortalFeaturedGroups.gb.cs | 229 ------------ .../Components/RotationVariable.gb.cs | 2 +- .../Components/SizeVariable.cs | 157 +++++++- .../Components/SizeVariable.gb.cs | 121 ------- .../Components/VisualVariable.gb.cs | 8 +- .../Model/GeometryEngine.cs | 302 +++++++++------- .../{Components => Model}/LocationService.cs | 35 +- .../Model/LogicComponent.cs | 20 +- src/dymaptic.GeoBlazor.Core/Model/MapColor.cs | 81 ++++- .../Model/PortalFeaturedGroups.cs | 16 + .../Model/ProjectionEngine.cs | 25 +- .../Scripts/arcGisJsInterop.ts | 2 +- .../Scripts/cSVLayer.gb.ts | 4 +- .../Scripts/featureLayer.gb.ts | 4 +- .../Scripts/geoJSONLayer.gb.ts | 4 +- .../Scripts/graphic.ts | 2 +- .../Scripts/label.gb.ts | 256 ------------- src/dymaptic.GeoBlazor.Core/Scripts/label.ts | 143 +++++++- .../Scripts/layerSearchSource.gb.ts | 2 +- .../Scripts/locatorSearchSource.gb.ts | 2 +- .../Scripts/portal.gb.ts | 8 +- src/dymaptic.GeoBlazor.Core/Scripts/portal.ts | 1 - .../Scripts/portalFeaturedGroups.gb.ts | 22 +- .../Scripts/portalFeaturedGroups.ts | 6 +- .../Scripts/portalProperties.gb.ts | 2 +- .../Scripts/portalProperties.ts | 6 +- .../searchViewModelDefaultSymbols.gb.ts | 6 +- .../Scripts/simpleRenderer.gb.ts | 2 +- .../Scripts/sizeRampStop.gb.ts | 2 +- .../Scripts/sublayer.gb.ts | 4 +- src/dymaptic.GeoBlazor.Core/Scripts/symbol.ts | 4 +- .../Scripts/symbolTableElementInfo.gb.ts | 2 +- .../Scripts/trackPartInfo.gb.ts | 2 +- .../Scripts/uniqueValueClass.gb.ts | 2 +- .../Scripts/uniqueValueInfo.gb.ts | 2 +- .../Scripts/uniqueValueRenderer.gb.ts | 4 +- .../Scripts/wFSLayer.gb.ts | 4 +- .../Scripts/webStyleSymbol.ts | 4 +- .../Serialization/CoreSerializationData.cs | 4 +- .../IJSStreamReferenceExtensions.cs | 22 +- .../Serialization/JsSyncManager.cs | 129 ++++--- .../Serialization/SizeVariableConverter.cs | 221 ++++++++++++ .../Serialization/VisualVariableConverter.cs | 28 +- .../dymaptic.GeoBlazor.Core.csproj | 7 +- .../CoreSourceGeneratorTests.cs | 4 +- .../GenerateTests.cs | 8 +- 59 files changed, 1395 insertions(+), 1412 deletions(-) create mode 100644 build-scripts/GeoBlazorCover.cs delete mode 100644 src/dymaptic.GeoBlazor.Core/Components/LocationService.gb.cs delete mode 100644 src/dymaptic.GeoBlazor.Core/Components/PortalFeaturedGroups.cs delete mode 100644 src/dymaptic.GeoBlazor.Core/Components/PortalFeaturedGroups.gb.cs rename src/dymaptic.GeoBlazor.Core/{Components => Model}/LocationService.cs (99%) create mode 100644 src/dymaptic.GeoBlazor.Core/Model/PortalFeaturedGroups.cs delete mode 100644 src/dymaptic.GeoBlazor.Core/Scripts/label.gb.ts create mode 100644 src/dymaptic.GeoBlazor.Core/Serialization/SizeVariableConverter.cs diff --git a/build-scripts/GeoBlazorCover.cs b/build-scripts/GeoBlazorCover.cs new file mode 100644 index 000000000..656586009 --- /dev/null +++ b/build-scripts/GeoBlazorCover.cs @@ -0,0 +1,58 @@ +#!/usr/bin/env dotnet + + + +/// +/// Runs a dotnet command and captures both stdout and stderr output. +/// Output is also written to the console in real-time. +/// +/// The working directory for the command. +/// The dotnet command (e.g., "build", "restore"). +/// Additional arguments to pass to the command. +/// A tuple containing the exit code and a list of all output lines. +static async Task<(int ExitCode, List Output)> RunDotnetCommandWithOutputAsync(string workingDirectory, + string command, params string[] args) +{ + var output = new List(); + + var psi = new ProcessStartInfo + { + FileName = "dotnet", + Arguments = $"{command} {string.Join(" ", args.Where(a => !string.IsNullOrWhiteSpace(a)))}", + WorkingDirectory = workingDirectory, + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + CreateNoWindow = true + }; + + using var process = Process.Start(psi); + if (process == null) + { + return (1, ["Failed to start dotnet"]); + } + + process.OutputDataReceived += (_, e) => + { + if (e.Data != null) + { + Console.WriteLine(e.Data); + output.Add(e.Data); + } + }; + + process.ErrorDataReceived += (_, e) => + { + if (e.Data != null) + { + Console.WriteLine(e.Data); + output.Add(e.Data); + } + }; + + process.BeginOutputReadLine(); + process.BeginErrorReadLine(); + await process.WaitForExitAsync(); + + return (process.ExitCode, output); +} \ No newline at end of file diff --git a/src/dymaptic.GeoBlazor.Core.SourceGenerator.Shared/ProcessHelper.cs b/src/dymaptic.GeoBlazor.Core.SourceGenerator.Shared/ProcessHelper.cs index 5b03afc2f..89b25777e 100644 --- a/src/dymaptic.GeoBlazor.Core.SourceGenerator.Shared/ProcessHelper.cs +++ b/src/dymaptic.GeoBlazor.Core.SourceGenerator.Shared/ProcessHelper.cs @@ -22,12 +22,17 @@ public static class ProcessHelper /// The working directory for the script execution. /// The name of the PowerShell script file to execute. /// Command-line arguments to pass to the script. - /// A StringBuilder to accumulate log output. /// The SourceProductionContext for diagnostic reporting. + /// + /// Whether to show console output for the process. If true, verbose output is shown. + /// + /// + /// The session ID to use for logging. + /// /// Optional environment variables to set for the process. public static async Task RunPowerShellScript(string processName, string workingDirectory, - string powershellScriptName, string[] arguments, StringBuilder logBuilder, SourceProductionContext context, - Dictionary? environmentVariables = null) + string powershellScriptName, string[] arguments, SourceProductionContext context, bool showConsole, + string? sessionId, Dictionary? environmentVariables = null) { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { @@ -50,7 +55,7 @@ public static async Task RunPowerShellScript(string processName, string workingD ]; } - await Execute(processName, workingDirectory, "pwsh", arguments, logBuilder, context, + await Execute(processName, workingDirectory, "pwsh", arguments, context, showConsole, sessionId, environmentVariables); } @@ -60,11 +65,16 @@ await Execute(processName, workingDirectory, "pwsh", arguments, logBuilder, cont /// A descriptive name for the process, used in logging. /// The working directory for the command execution. /// The PowerShell command to execute. - /// A StringBuilder to accumulate log output. /// The SourceProductionContext for diagnostic reporting. + /// + /// Whether to show console output for the process. If true, verbose output is shown. + /// + /// + /// The session ID to use for logging. + /// /// Optional environment variables to set for the process. public static async Task RunPowerShellCommand(string processName, string workingDirectory, - string[] arguments, StringBuilder logBuilder, SourceProductionContext context, + string[] arguments, SourceProductionContext context, bool showConsole, string? sessionId, Dictionary? environmentVariables = null) { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) @@ -81,7 +91,7 @@ public static async Task RunPowerShellCommand(string processName, string working ]; } - await Execute(processName, workingDirectory, "pwsh", arguments, logBuilder, context, + await Execute(processName, workingDirectory, "pwsh", arguments, context, showConsole, sessionId, environmentVariables); } @@ -92,25 +102,32 @@ await Execute(processName, workingDirectory, "pwsh", arguments, logBuilder, cont /// The working directory for the process execution. /// The executable file name. If null, uses the platform-specific shell command. /// Command-line arguments to pass to the process. - /// A StringBuilder to accumulate log output. /// The SourceProductionContext for diagnostic reporting. + /// + /// Whether to show console output for the process. If true, verbose output is shown. + /// + /// + /// The session ID to use for logging. + /// /// Optional environment variables to set for the process. /// Thrown when the process exits with a non-zero exit code. public static async Task Execute(string processName, string workingDirectory, string? fileName, - string[] shellArguments, StringBuilder logBuilder, SourceProductionContext context, + string[] shellArguments, SourceProductionContext context, bool showConsole, string? sessionId, Dictionary? environmentVariables = null) { fileName ??= shellCommand; - StringBuilder outputBuilder = new(); + Log(processName, $"Starting process execution: {processName} {string.Join(" ", shellArguments)}", + DiagnosticSeverity.Info, context, showConsole, sessionId); + int? processId = null; int? exitCode = null; context.CancellationToken.Register(() => { - logBuilder.AppendLine($"{processName}: Command execution cancelled."); - logBuilder.AppendLine(outputBuilder.ToString()); - outputBuilder.Clear(); + Log(processName, "Command execution cancelled.", + DiagnosticSeverity.Info, context, + showConsole, sessionId); }); Command cmd = Cli.Wrap(fileName) @@ -119,49 +136,70 @@ public static async Task Execute(string processName, string workingDirectory, st .WithValidation(CommandResultValidation.None) .WithEnvironmentVariables(environmentVariables ?? new Dictionary()); + bool retry = false; + + // build up the input in case we need it for writing to an error + StringBuilder errorBuilder = new(); + bool errorFound = false; + await foreach (var cmdEvent in cmd.ListenAsync(context.CancellationToken)) { switch (cmdEvent) { case StartedCommandEvent started: processId = started.ProcessId; - outputBuilder.AppendLine($" - {processName} Process started: {started.ProcessId}"); - outputBuilder.AppendLine($" - {processName} - PID {processId}: Executing command: {fileName} { - string.Join(" ", shellArguments)}"); + Log(processName, $"Process {processId} started.", + DiagnosticSeverity.Info, context, + showConsole, sessionId); break; case StandardOutputCommandEvent stdOut: string line = stdOut.Text.Trim(); - outputBuilder.AppendLine($" - {processName} - PID {processId}: [stdout] {line}"); + + Log(processName, $"Process {processId} - [stdout] {line}", + DiagnosticSeverity.Info, context, + showConsole, sessionId); + + if (line.Contains("error", StringComparison.OrdinalIgnoreCase) || errorFound) + { + errorBuilder.AppendLine(line); + errorFound = true; + } + + if (fileName == "dotnet" && stdOut.Text.Contains("The process cannot access the file")) + { + retry = true; + } break; case StandardErrorCommandEvent stdErr: - outputBuilder.AppendLine($" - {processName} - PID {processId}: [stderr] {stdErr.Text}"); + Log(processName, $"Process {processId} - [stderr] {stdErr.Text}", + DiagnosticSeverity.Warning, context, + showConsole, sessionId); + + errorBuilder.AppendLine(stdErr.Text); + + if (fileName == "dotnet" && stdErr.Text.Contains("The process cannot access the file")) + { + retry = true; + } break; case ExitedCommandEvent exited: exitCode = exited.ExitCode; - outputBuilder.AppendLine($" - {processName} - PID {processId}: Process exited with code: { - exited.ExitCode}"); + Log(processName, $"Process {processId} exited with code: {exited.ExitCode}", + DiagnosticSeverity.Info, context, + showConsole, sessionId); break; } } - // Append any accumulated output to the log - if (outputBuilder.Length > 0) - { - logBuilder.AppendLine(outputBuilder.ToString()); - } - if (exitCode != 0) { - var response = logBuilder.ToString(); - Log(processName, response, DiagnosticSeverity.Info, context); - - if (response.Contains("The process cannot access the file") && (fileName == "dotnet")) + if (retry) { var programName = shellArguments[1]; // dotnet[fileName] run[arg[0]] ESBuild.cs[arg[1]] @@ -183,24 +221,23 @@ public static async Task Execute(string processName, string workingDirectory, st await Task.Delay(500); - await Execute(processName, workingDirectory, fileName, shellArguments, logBuilder, context, + await Execute(processName, workingDirectory, fileName, shellArguments, context, showConsole, sessionId, environmentVariables); return; } Log(processName, - $"Command '{fileName} {string.Join(" ", shellArguments)}' failed with exit code {exitCode}.", + $"Command '{fileName} {string.Join(" ", shellArguments)}' failed with exit code {exitCode}. { + errorBuilder}", DiagnosticSeverity.Error, context); return; } - // Return the standard output if the process completed normally - logBuilder.AppendLine($" - {processName}: Command '{string.Join(" ", shellArguments) - }' completed successfully on process {processId - }."); + Log(processName, $"Command '{fileName} {string.Join(" ", shellArguments)}' completed successfully.", + DiagnosticSeverity.Info, context, showConsole, sessionId); } /// diff --git a/src/dymaptic.GeoBlazor.Core.SourceGenerator.Shared/ProtobufDefinitionsGenerator.cs b/src/dymaptic.GeoBlazor.Core.SourceGenerator.Shared/ProtobufDefinitionsGenerator.cs index 76b3318dd..96b444222 100644 --- a/src/dymaptic.GeoBlazor.Core.SourceGenerator.Shared/ProtobufDefinitionsGenerator.cs +++ b/src/dymaptic.GeoBlazor.Core.SourceGenerator.Shared/ProtobufDefinitionsGenerator.cs @@ -36,7 +36,6 @@ public static Dictionary UpdateProtobufDefinitio "\n" => "\\n", _ => match.Value }); - StringBuilder logBuilder = new(); var scriptPath = Path.Combine(corePath, "copyProtobuf.ps1"); @@ -45,15 +44,10 @@ public static Dictionary UpdateProtobufDefinitio ProcessHelper.RunPowerShellScript("Copy Protobuf Definitions", corePath, scriptPath, ["-Content", encoded], - logBuilder, context) + context, showDialog, sessionId) .GetAwaiter() .GetResult(); - ProcessHelper.Log(nameof(ProtobufDefinitionsGenerator), - logBuilder.ToString(), - DiagnosticSeverity.Info, - context, sessionId: sessionId); - ProcessHelper.Log(nameof(ProtobufDefinitionsGenerator), "Protobuf definitions updated successfully.", DiagnosticSeverity.Info, diff --git a/src/dymaptic.GeoBlazor.Core.SourceGenerator.Shared/SerializationGenerator.cs b/src/dymaptic.GeoBlazor.Core.SourceGenerator.Shared/SerializationGenerator.cs index ae457e128..f5bb67b21 100644 --- a/src/dymaptic.GeoBlazor.Core.SourceGenerator.Shared/SerializationGenerator.cs +++ b/src/dymaptic.GeoBlazor.Core.SourceGenerator.Shared/SerializationGenerator.cs @@ -123,9 +123,10 @@ private static string GenerateExtensionMethods(Dictionary to a specific type via protobuf. /// public static partial async Task ReadJsStreamReferenceAsProtobuf(this IJSStreamReference jsStreamReference, - Type returnType, long maxAllowedSize) + Type returnType, long? maxAllowedSize, CancellationToken cancellationToken) { - await using Stream stream = await jsStreamReference.OpenReadStreamAsync(maxAllowedSize); + maxAllowedSize ??= 1_000_000_000L; + await using Stream stream = await jsStreamReference.OpenReadStreamAsync(maxAllowedSize.Value); using MemoryStream memoryStream = new(); await stream.CopyToAsync(memoryStream); memoryStream.Seek(0, SeekOrigin.Begin); @@ -139,12 +140,13 @@ private static string GenerateExtensionMethods(Dictionary - /// Convenience method to deserialize an to a specific coolection type via protobuf. + /// Convenience method to deserialize an to a specific collection type via protobuf. /// public static partial async Task ReadJsStreamReferenceAsProtobufCollection(this IJSStreamReference jsStreamReference, - Type returnType, long maxAllowedSize) + Type returnType, long? maxAllowedSize, CancellationToken cancellationToken) { - await using Stream stream = await jsStreamReference.OpenReadStreamAsync(maxAllowedSize); + maxAllowedSize ??= 1_000_000_000L; + await using Stream stream = await jsStreamReference.OpenReadStreamAsync(maxAllowedSize.Value); using MemoryStream memoryStream = new(); await stream.CopyToAsync(memoryStream); memoryStream.Seek(0, SeekOrigin.Begin); diff --git a/src/dymaptic.GeoBlazor.Core.SourceGenerator/ESBuildGenerator.cs b/src/dymaptic.GeoBlazor.Core.SourceGenerator/ESBuildGenerator.cs index e7e8b817e..f8924b4e8 100644 --- a/src/dymaptic.GeoBlazor.Core.SourceGenerator/ESBuildGenerator.cs +++ b/src/dymaptic.GeoBlazor.Core.SourceGenerator/ESBuildGenerator.cs @@ -2,7 +2,6 @@ using Microsoft.CodeAnalysis; using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; -using System.Text; namespace dymaptic.GeoBlazor.Core.SourceGenerator; @@ -18,39 +17,54 @@ public class ESBuildGenerator : IIncrementalGenerator ? null : Path.GetFullPath(Path.Combine(_corePath, "..", "..", "build-tools")); -#if SHOW_SOURCEGEN_DIALOGS - private static bool _showDialog = true; -#else - private static bool _showDialog = false; -#endif - /// public void Initialize(IncrementalGeneratorInitializationContext context) { // Tracks all TypeScript source files in the Scripts directories of Core and Pro. // This will trigger the build any time a TypeScript file is added, removed, or changed. - IncrementalValueProvider> tsFilesProvider = context.AdditionalTextsProvider + IncrementalValueProvider> tsFilesProvider = context + .AdditionalTextsProvider .Where(static text => text.Path.Contains("Scripts") && text.Path.EndsWith(".ts")) .Collect(); // Reads the MSBuild properties to get the project directory and configuration. - IncrementalValueProvider<(string?, string?, string?, string?)> optionsProvider = + IncrementalValueProvider> optionsProvider = context.AnalyzerConfigOptionsProvider.Select((configProvider, _) => { - configProvider.GlobalOptions.TryGetValue("build_property.CoreProjectPath", - out string? projectDirectory); + Dictionary options = []; - configProvider.GlobalOptions.TryGetValue("build_property.Configuration", - out string? configuration); + if (configProvider.GlobalOptions.TryGetValue("build_property.CoreProjectPath", + out string? projectDirectory)) + { + options["CoreProjectPath"] = projectDirectory; + } - configProvider.GlobalOptions.TryGetValue("build_property.PipelineBuild", - out string? pipelineBuild); + if (configProvider.GlobalOptions.TryGetValue("build_property.Configuration", + out string? configuration)) + { + options["Configuration"] = configuration; + } - configProvider.GlobalOptions.TryGetValue("build_property.DesignTimeBuild", - out var designTimeBuild); + if (configProvider.GlobalOptions.TryGetValue("build_property.PipelineBuild", + out string? pipelineBuild)) + { + options["PipelineBuild"] = pipelineBuild; + } - return (projectDirectory, configuration, pipelineBuild, designTimeBuild); + if (configProvider.GlobalOptions.TryGetValue("build_property.DesignTimeBuild", + out string? designTimeBuild)) + { + options["DesignTimeBuild"] = designTimeBuild; + } + + if (configProvider.GlobalOptions.TryGetValue("build_property.ShowSourceGenDialogs", + out string? showDialog)) + { + options["ShowSourceGenDialogs"] = showDialog; + } + + return options; }); var @@ -61,21 +75,16 @@ public void Initialize(IncrementalGeneratorInitializationContext context) } private void FilesChanged(SourceProductionContext context, - (ImmutableArray Files, - (string? ProjectDirectory, string? Configuration, string? PipelineBuild, string? DesignTimeBuild) Options) - pipeline) + (ImmutableArray Files, Dictionary Options) pipeline) { if (!SetProjectDirectoryAndConfiguration(pipeline.Options, context)) { return; } - ProcessHelper.Log(nameof(ESBuildGenerator), - "ESBuild Source Generation triggered.", - DiagnosticSeverity.Info, - context); - - if (pipeline.Options.PipelineBuild == "true") + if (pipeline.Options.TryGetValue("PipelineBuild", out string? pipelineBuild) + && bool.TryParse(pipelineBuild, out bool pipelineBuildBool) + && pipelineBuildBool) { // If the pipeline build is enabled, we skip the ESBuild process. // This is to avoid race conditions where the files are not ready on time, and we do the build separately. @@ -87,26 +96,33 @@ private void FilesChanged(SourceProductionContext context, return; } + ProcessHelper.Log(nameof(ESBuildGenerator), + "ESBuild Source Generation triggered.", + DiagnosticSeverity.Info, + context, _showDialog, _sessionId); + if (pipeline.Files.Length > 0) { LaunchESBuild(context); } + + if (_showDialog) + { + ProcessHelper.CloseDialog(_sessionId); + } } - private bool SetProjectDirectoryAndConfiguration( - (string? ProjectDirectory, string? Configuration, string? _, string? DesignTimeBuild) options, + private bool SetProjectDirectoryAndConfiguration(Dictionary options, SourceProductionContext context) { - string? projectDirectory = options.ProjectDirectory; - - if (projectDirectory is not null) + if (options.TryGetValue("CoreProjectPath", out string? projectDirectory)) { _corePath = Path.GetFullPath(projectDirectory); ProcessHelper.Log(nameof(ESBuildGenerator), $"Project directory set to {_corePath}", DiagnosticSeverity.Info, - context); + context, _showDialog, _sessionId); if (_corePath.Contains("GeoBlazor.Pro")) { @@ -128,15 +144,26 @@ private bool SetProjectDirectoryAndConfiguration( ProcessHelper.Log(nameof(ESBuildGenerator), "Invalid project directory.", DiagnosticSeverity.Error, - context); + context, _showDialog, _sessionId); return false; } - if (options.Configuration is { } configuration) + if (options.TryGetValue("Configuration", out string? configuration)) { _configuration = configuration; - _isDesignTimeBuild = options.DesignTimeBuild == "true"; + + if (options.TryGetValue("DesignTimeBuild", out string? designTimeBuild) + && bool.TryParse(designTimeBuild, out bool designTimeBuildBool)) + { + _isDesignTimeBuild = designTimeBuildBool; + } + + if (options.TryGetValue("ShowSourceGenDialogs", out string? showDialog) + && bool.TryParse(showDialog, out bool showDialogBool)) + { + _showDialog = showDialogBool; + } return true; } @@ -144,7 +171,7 @@ private bool SetProjectDirectoryAndConfiguration( ProcessHelper.Log(nameof(ESBuildGenerator), "Could not parse configuration setting, invalid configuration.", DiagnosticSeverity.Error, - context); + context, _showDialog, _sessionId); return false; } @@ -156,11 +183,7 @@ private void LaunchESBuild(SourceProductionContext context) ProcessHelper.Log(nameof(ESBuildGenerator), "Starting Core ESBuild process...", DiagnosticSeverity.Info, - context); - - StringBuilder logBuilder = new StringBuilder(DateTime.Now.ToLongTimeString()); - logBuilder.AppendLine(); - logBuilder.AppendLine("Starting Core ESBuild process..."); + context, _showDialog, _sessionId); try { @@ -175,31 +198,22 @@ private void LaunchESBuild(SourceProductionContext context) "-c", _configuration! // set config for ESBuild ]; - if (_showDialog) - { - if (!_isDesignTimeBuild) - { - esBuildArgs = [..esBuildArgs, "-d", "-v"]; // show verbose output - } - else - { - esBuildArgs = [..esBuildArgs, "-d"]; - } - } - tasks.Add(Task.Run(async () => { string[] coreArgs = esBuildArgs.ToArray(); await ProcessHelper.Execute("Core", BuildToolsPath!, "dotnet", - coreArgs, logBuilder, context); + coreArgs, context, _showDialog, _sessionId); buildSuccess = true; })); if (_proPath is not null) { - logBuilder.AppendLine("Starting Pro ESBuild process..."); + ProcessHelper.Log(nameof(ESBuildGenerator), + "Starting Pro ESBuild process...", + DiagnosticSeverity.Info, + context, _showDialog, _sessionId); string[] proArgs = [..esBuildArgs, "--pro"]; @@ -207,7 +221,7 @@ await ProcessHelper.Execute("Core", { await ProcessHelper.Execute("Pro", BuildToolsPath!, "dotnet", - proArgs, logBuilder, context); + proArgs, context, _showDialog, _sessionId); proBuildSuccess = true; })); } @@ -217,65 +231,56 @@ await ProcessHelper.Execute("Pro", if (!buildSuccess) { ProcessHelper.Log(nameof(ESBuildGenerator), - $"Core ESBuild process failed\r\n{logBuilder}", + "Core ESBuild process failed", DiagnosticSeverity.Error, - context); + context, _showDialog, _sessionId); return; } - logBuilder.AppendLine("Core ESBuild process completed successfully."); - logBuilder.AppendLine(); - if (_proPath is not null) { if (!proBuildSuccess) { ProcessHelper.Log(nameof(ESBuildGenerator), - $"Pro ESBuild process failed\r\n{logBuilder}", + "Pro ESBuild process failed", DiagnosticSeverity.Error, - context); - - return; + context, _showDialog, _sessionId); } - - logBuilder.AppendLine("Pro ESBuild process completed successfully."); - logBuilder.AppendLine(); } - - ProcessHelper.Log(nameof(ESBuildGenerator), - logBuilder.ToString(), - DiagnosticSeverity.Info, - context); } catch (Exception ex) { + ClearESBuildLocks(context); + ProcessHelper.Log(nameof(ESBuildGenerator), $"An error occurred while running ESBuild: {ex.Message}\r\n{ex.StackTrace}", DiagnosticSeverity.Error, - context); - - ClearESBuildLocks(context); + context, _showDialog, _sessionId); } } private void ClearESBuildLocks(SourceProductionContext context) { - StringBuilder logBuilder = new(); - - _ = Task.Run(async () => await ProcessHelper.Execute("Clear Locks", + Task clearTask = Task.Run(async () => await ProcessHelper.Execute("Clear Locks", BuildToolsPath!, "dotnet", ["ESBuildClearLocks.dll"], - logBuilder, context)); + context, _showDialog, _sessionId)); + + clearTask.GetAwaiter().GetResult(); ProcessHelper.Log(nameof(ESBuildGenerator), "Cleared ESBuild Process Locks", DiagnosticSeverity.Info, - context); + context, _showDialog, _sessionId); } private static string? _corePath; private static string? _proPath; private static string? _configuration; private static bool _isDesignTimeBuild; + private static bool _showDialog; + + // Generate a unique session ID for this build session + private readonly string _sessionId = $"{nameof(ESBuildGenerator)}_{Guid.NewGuid():N}"; } \ No newline at end of file diff --git a/src/dymaptic.GeoBlazor.Core.SourceGenerator/ProtobufSourceGenerator.cs b/src/dymaptic.GeoBlazor.Core.SourceGenerator/ProtobufSourceGenerator.cs index 57b1a9dc2..4478948c0 100644 --- a/src/dymaptic.GeoBlazor.Core.SourceGenerator/ProtobufSourceGenerator.cs +++ b/src/dymaptic.GeoBlazor.Core.SourceGenerator/ProtobufSourceGenerator.cs @@ -14,12 +14,6 @@ namespace dymaptic.GeoBlazor.Core.SourceGenerator; [Generator] public class ProtobufSourceGenerator : IIncrementalGenerator { -#if SHOW_SOURCEGEN_DIALOGS - private static bool _showDialog = true; -#else - private static bool _showDialog = false; -#endif - /// public void Initialize(IncrementalGeneratorInitializationContext context) { @@ -28,40 +22,66 @@ public void Initialize(IncrementalGeneratorInitializationContext context) context.SyntaxProvider.CreateSyntaxProvider(static (syntaxNode, _) => syntaxNode is ClassDeclarationSyntax or StructDeclarationSyntax or RecordDeclarationSyntax && (((BaseTypeDeclarationSyntax)syntaxNode).AttributeLists.SelectMany(a => a.Attributes) - .Any(a => a.Name.ToString() is ProtoContractAttribute or ProtoSerializableAttribute) + .Any(a => a.Name.ToString() is PROTO_CONTRACT_ATTRIBUTE or PROTO_SERIALIZABLE_ATTRIBUTE) || syntaxNode.ChildNodes() .OfType() .Any(m => m.AttributeLists .SelectMany(a => a.Attributes) - .Any(attr => attr.Name.ToString() == SerializedMethodAttributeName))), + .Any(attr => attr.Name.ToString() == SERIALIZED_METHOD_ATTRIBUTE_NAME))), static (context, _) => (BaseTypeDeclarationSyntax)context.Node) .Collect(); // Reads the MSBuild properties to get the project directory. - IncrementalValueProvider<(string?, string?)> optionsProvider = + IncrementalValueProvider> optionsProvider = context.AnalyzerConfigOptionsProvider.Select((configProvider, _) => { - configProvider.GlobalOptions.TryGetValue("build_property.CoreProjectPath", - out var projectDirectory); + Dictionary options = []; + + if (configProvider.GlobalOptions.TryGetValue("build_property.CoreProjectPath", + out var projectDirectory)) + { + options["CoreProjectPath"] = projectDirectory; + } + + if (configProvider.GlobalOptions.TryGetValue("build_property.PipelineBuild", + out var pipelineBuild)) + { + options["PipelineBuild"] = pipelineBuild; + } - configProvider.GlobalOptions.TryGetValue("build_property.PipelineBuild", - out var pipelineBuild); + if (configProvider.GlobalOptions.TryGetValue("build_property.ShowSourceGenDialogs", + out var showDialog)) + { + options["ShowSourceGenDialogs"] = showDialog; + } - return (projectDirectory, pipelineBuild); + return options; }); - IncrementalValueProvider<(ImmutableArray Left, (string?, string?) Right)> combined = + IncrementalValueProvider<(ImmutableArray Left, + Dictionary Right)> combined = typeProvider.Combine(optionsProvider); context.RegisterSourceOutput(combined, FilesChanged); } private void FilesChanged(SourceProductionContext context, - (ImmutableArray Types, (string? ProjectDirectory, string? PipelineBuild) Options) + (ImmutableArray Types, Dictionary Options) pipeline) { - _corePath = pipeline.Options.ProjectDirectory; - bool showDialog = pipeline.Options.PipelineBuild != "true" && _showDialog; + pipeline.Options.TryGetValue("CoreProjectPath", out _corePath); + pipeline.Options.TryGetValue("PipelineBuild", out string? pipelineBuildString); + + bool pipelineBuild = pipelineBuildString is not null + && bool.TryParse(pipelineBuildString, out bool pipelineBuildBool) + && pipelineBuildBool; + + pipeline.Options.TryGetValue("ShowSourceGenDialogs", out string? showDialogString); + + bool showDialog = !pipelineBuild + && showDialogString is not null + && bool.TryParse(showDialogString, out bool showDialogBool) + && showDialogBool; // Generate a unique session ID for this build session string sessionId = $"{nameof(ProtobufSourceGenerator)}_{Guid.NewGuid():N}"; @@ -106,7 +126,7 @@ private void FilesChanged(SourceProductionContext context, } private static string? _corePath; - private const string ProtoContractAttribute = "ProtoContract"; - private const string ProtoSerializableAttribute = "ProtobufSerializable"; - private const string SerializedMethodAttributeName = "SerializedMethod"; + private const string PROTO_CONTRACT_ATTRIBUTE = "ProtoContract"; + private const string PROTO_SERIALIZABLE_ATTRIBUTE = "ProtobufSerializable"; + private const string SERIALIZED_METHOD_ATTRIBUTE_NAME = "SerializedMethod"; } \ No newline at end of file diff --git a/src/dymaptic.GeoBlazor.Core/Components/ColorVariable.gb.cs b/src/dymaptic.GeoBlazor.Core/Components/ColorVariable.gb.cs index 7006d1b43..c4af2f7f0 100644 --- a/src/dymaptic.GeoBlazor.Core/Components/ColorVariable.gb.cs +++ b/src/dymaptic.GeoBlazor.Core/Components/ColorVariable.gb.cs @@ -50,7 +50,7 @@ public ColorVariable() /// Arcade expression as defined in the valueExpression property. /// ArcGIS Maps SDK for JavaScript /// - public ColorVariable(string field, + public ColorVariable(string? field, string? normalizationField = null, IReadOnlyList? stops = null, VisualVariableLegendOptions? legendOptions = null, diff --git a/src/dymaptic.GeoBlazor.Core/Components/Layers/Layer.cs b/src/dymaptic.GeoBlazor.Core/Components/Layers/Layer.cs index c60becd34..eafe7685d 100644 --- a/src/dymaptic.GeoBlazor.Core/Components/Layers/Layer.cs +++ b/src/dymaptic.GeoBlazor.Core/Components/Layers/Layer.cs @@ -7,8 +7,7 @@ public abstract partial class Layer : MapComponent /// Used internally to identify the sub type of Layer /// public abstract LayerType Type { get; } - - + /// /// For layers that are public and throw an error when given an ApiKey or Token, /// this setting allows you to exclude the ApiKey or Token from this layer's request. @@ -17,7 +16,7 @@ public abstract partial class Layer : MapComponent [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [CodeGenerationIgnore] public bool? ExcludeApiKey { get; set; } - + /// /// Indicates whether the layer's resources have loaded. /// default false @@ -28,7 +27,7 @@ public abstract partial class Layer : MapComponent [JsonInclude] [CodeGenerationIgnore] public bool? Loaded { get; internal set; } - + /// /// The opacity of the layer. /// default 1 @@ -52,7 +51,6 @@ public abstract partial class Layer : MapComponent [JsonIgnore] public LayerView? LayerView { get; internal set; } - /// /// Indicates how the layer should display in the [LayerList](https://developers.arcgis.com/javascript/latest/api-reference/esri-widgets-LayerList.html) widget. /// default "show" @@ -61,7 +59,7 @@ public abstract partial class Layer : MapComponent [Parameter] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public ListMode? ListMode { get; set; } - + /// /// If the layer is added to the , this flag identifies the layer as a reference layer, which will sit on top of other layers to add labels. /// ArcGIS Maps SDK for JavaScript @@ -78,7 +76,7 @@ public abstract partial class Layer : MapComponent [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonConverter(typeof(FullExtentConverter))] public Extent? FullExtent { get; set; } - + /// /// Enable persistence of the layer in a [WebMap](https://developers.arcgis.com/javascript/latest/api-reference/esri-WebMap.html) or [WebScene](https://developers.arcgis.com/javascript/latest/api-reference/esri-WebScene.html). /// default true @@ -102,80 +100,55 @@ public abstract partial class Layer : MapComponent /// public bool Imported { get; set; } -#region PropertySetters - - /// - /// Asynchronously set the value of the FullExtent property after render. - /// - public virtual async Task SetFullExtent(Extent? value) + /// + public override async ValueTask DisposeAsync() { - FullExtent = value; - ModifiedParameters["FullExtent"] = value; - - if (JsComponentReference is null) + try { - return; - } - - await JsComponentReference!.InvokeVoidAsync("setFullExtent", - CancellationTokenSource.Token, - value); - } - - /// - /// Asynchronously set the value of the Opacity property after render. - /// - public async Task SetOpacity(double value) - { - Opacity = value; - ModifiedParameters["Opacity"] = value; + if (AbortManager is not null) + { + await AbortManager.DisposeAsync(); + } - if (JsComponentReference is null) + if (LayerView is not null) + { + await LayerView.DisposeAsync(); + } + + if (JsComponentReference is not null) + { + try + { + await JsComponentReference.DisposeAsync(); + } + catch (JSDisconnectedException) + { + // ignore, we have disconnected from the JS runtime + } + } + } + catch (TaskCanceledException) { - return; + // user cancelled } - - await JsComponentReference!.InvokeVoidAsync("setProperty", - CancellationTokenSource.Token, - "opacity", - value); - } - -#endregion - -#region Property Getters - - /// - /// Asynchronously retrieve the current value of the FullExtent property. - /// - public async Task GetFullExtent() - { - if (JsComponentReference is null) + catch (JSDisconnectedException) { - return null; + // lost connection (page navigation) } - - return await JsComponentReference!.InvokeAsync("getFullExtent", - CancellationTokenSource.Token); - } - - /// - /// Asynchronously retrieve the current value of the Opacity property. - /// - public async Task GetOpacity() - { - if (JsComponentReference is null) + catch (JSException) { - return null; + // instance already destroyed } - - return await JsComponentReference!.InvokeAsync("getProperty", - CancellationTokenSource.Token, - "opacity"); - } + await base.DisposeAsync(); + } -#endregion + /// + public override void ValidateRequiredChildren() + { + FullExtent?.ValidateRequiredChildren(); + base.ValidateRequiredChildren(); + } /// public override async Task RegisterChildComponent(MapComponent child) @@ -186,6 +159,7 @@ public override async Task RegisterChildComponent(MapComponent child) if (!extent.Equals(FullExtent)) { FullExtent = extent; + if (MapRendered) { await UpdateLayer(); @@ -195,10 +169,12 @@ public override async Task RegisterChildComponent(MapComponent child) break; default: await base.RegisterChildComponent(child); + if (MapRendered) { await UpdateLayer(); } + break; } } @@ -210,7 +186,7 @@ public override async Task UnregisterChildComponent(MapComponent child) { case Extent _: FullExtent = null; - + break; default: @@ -220,49 +196,6 @@ public override async Task UnregisterChildComponent(MapComponent child) } } - /// - public override async ValueTask DisposeAsync() - { - try - { - if (AbortManager is not null) - { - await AbortManager.DisposeAsync(); - } - - if (LayerView is not null) - { - await LayerView.DisposeAsync(); - } - - if (JsComponentReference is not null) - { - try - { - await JsComponentReference.DisposeAsync(); - } - catch (JSDisconnectedException) - { - // ignore, we have disconnected from the JS runtime - } - } - } - catch (TaskCanceledException) - { - // user cancelled - } - catch (JSDisconnectedException) - { - // lost connection (page navigation) - } - catch (JSException) - { - // instance already destroyed - } - - await base.DisposeAsync(); - } - /// /// Loads the resources referenced by this class. This method automatically executes for a View and all of the resources it references in Map if the view is constructed with a map instance. /// This method must be called by the developer when accessing a resource that will not be loaded in a View. @@ -323,7 +256,7 @@ public virtual async Task Load(CancellationToken cancellationToken) "Layers not defined in a Map or in Blazor Markup must be loaded with a JsModuleManager and IJSRuntime. Use the Load overload which takes these parameters."); } } - + AbortManager = new AbortManager(CoreJsModule!); IJSObjectReference abortSignal = await AbortManager!.CreateAbortSignal(cancellationToken); @@ -331,20 +264,25 @@ public virtual async Task Load(CancellationToken cancellationToken) await CoreJsModule!.InvokeVoidAsync("loadProtobuf", cancellationToken); await CoreJsModule.InvokeAsync("buildJsLayer", + // ReSharper disable once RedundantCast cancellationToken, (object)this, Id, View?.Id); - - JsComponentReference ??= await CoreJsModule.InvokeAsync( - "getJsComponent", cancellationToken, Id); - - IJSStreamReference streamRef = await JsComponentReference!.InvokeAsync("load", + + JsComponentReference ??= + await CoreJsModule.InvokeAsync("getJsComponent", cancellationToken, Id); + + IJSStreamReference streamRef = await JsComponentReference!.InvokeAsync("load", cancellationToken, abortSignal); Type type = GetType(); - Layer? deserializedLayer = await streamRef.ReadJsStreamReferenceAsJSON(type) as Layer; + + Layer? deserializedLayer = await streamRef.ReadJsStreamReferenceAsJSON(type, + cancellationToken: cancellationToken) as Layer; + if (deserializedLayer is null) { throw new InvalidOperationException($"Could not load layer of type {type.Name}"); } + CopyProperties(deserializedLayer); await UpdateFromJavaScript(deserializedLayer); await AbortManager.DisposeAbortController(cancellationToken); @@ -357,14 +295,8 @@ public override async ValueTask Refresh() { await UpdateLayer(); } - await base.Refresh(); - } - /// - public override void ValidateRequiredChildren() - { - FullExtent?.ValidateRequiredChildren(); - base.ValidateRequiredChildren(); + await base.Refresh(); } /// @@ -382,19 +314,6 @@ public override void ValidateRequiredChildren() return renderedLayer; } - /// - /// Copies values from the rendered JavaScript layer back to the .NET implementation. - /// - /// - /// The layer deserialized from JavaScript - /// - internal virtual Task UpdateFromJavaScript(Layer renderedLayer) - { - // This is called after MapComponent.CopyProperties, so it should only be used for properties - // that would fail to be copied by regular reflection-based copying. - return Task.CompletedTask; - } - /// public override async Task SetParametersAsync(ParameterView parameters) { @@ -402,6 +321,7 @@ public override async Task SetParametersAsync(ParameterView parameters) await base.SetParametersAsync(parameters); bool layerChanged = false; + if (PreviousParameters is not null && MapRendered) { foreach (KeyValuePair kvp in dictionary) @@ -411,7 +331,7 @@ public override async Task SetParametersAsync(ParameterView parameters) { continue; } - + if (!PreviousParameters.TryGetValue(kvp.Key, out object? previousValue)) { if (MapRendered) @@ -428,16 +348,17 @@ public override async Task SetParametersAsync(ParameterView parameters) { Array prevArray = (Array)previousValue; Array currArray = (Array)kvp.Value!; - + if (prevArray.Length != currArray.Length) { if (MapRendered) { await UpdateLayer(); } + break; } - + for (int i = 0; i < prevArray.Length; i++) { if (!Equals(prevArray.GetValue(i), currArray.GetValue(i))) @@ -447,30 +368,33 @@ public override async Task SetParametersAsync(ParameterView parameters) await UpdateLayer(); layerChanged = true; } + break; } } - + if (layerChanged) break; } else if (paramType.IsGenericType) { ICollection prevCollection = (ICollection)previousValue; ICollection currCollection = (ICollection)kvp.Value!; - + if (prevCollection.Count != currCollection.Count) { if (MapRendered) { await UpdateLayer(); } + break; } - + IEnumerator prevEnumerator = prevCollection.GetEnumerator(); using var prevEnumerator1 = prevEnumerator as IDisposable; IEnumerator currEnumerator = currCollection.GetEnumerator(); using var currEnumerator1 = currEnumerator as IDisposable; + while (prevEnumerator.MoveNext() && currEnumerator.MoveNext()) { if (!Equals(prevEnumerator.Current, currEnumerator.Current)) @@ -479,6 +403,7 @@ public override async Task SetParametersAsync(ParameterView parameters) { await UpdateLayer(); } + break; } } @@ -489,22 +414,13 @@ public override async Task SetParametersAsync(ParameterView parameters) { await UpdateLayer(); } + break; } } } - - PreviousParameters = dictionary.ToDictionary(kvp => kvp.Key, kvp => kvp.Value); - } - /// - protected override async Task OnAfterRenderAsync(bool firstRender) - { - await base.OnAfterRenderAsync(firstRender); - if (_delayedUpdate) - { - await UpdateLayer(); - } + PreviousParameters = dictionary.ToDictionary(kvp => kvp.Key, kvp => kvp.Value); } /// @@ -517,7 +433,7 @@ public async Task UpdateLayer() // don't update until the component has been returned from JavaScript return; } - + if (MapRendered && !_delayedUpdate) { // for components added after the map has rendered, wait one render cycle to get all children before updating @@ -526,12 +442,113 @@ public async Task UpdateLayer() return; } - + _delayedUpdate = false; // ReSharper disable once RedundantCast - await JsComponentReference!.InvokeAsync("updateComponent", CancellationTokenSource.Token, (object)this); + await JsComponentReference!.InvokeAsync("updateComponent", CancellationTokenSource.Token, + (object)this); + } + + /// + protected override async Task OnAfterRenderAsync(bool firstRender) + { + await base.OnAfterRenderAsync(firstRender); + + if (_delayedUpdate) + { + await UpdateLayer(); + } + } + + /// + /// Copies values from the rendered JavaScript layer back to the .NET implementation. + /// + /// + /// The layer deserialized from JavaScript + /// + internal virtual Task UpdateFromJavaScript(Layer renderedLayer) + { + // This is called after MapComponent.CopyProperties, so it should only be used for properties + // that would fail to be copied by regular reflection-based copying. + return Task.CompletedTask; } - + private bool _delayedUpdate; + + +#region PropertySetters + + /// + /// Asynchronously set the value of the FullExtent property after render. + /// + public virtual async Task SetFullExtent(Extent? value) + { + FullExtent = value; + ModifiedParameters["FullExtent"] = value; + + if (JsComponentReference is null) + { + return; + } + + await JsComponentReference!.InvokeVoidAsync("setFullExtent", + CancellationTokenSource.Token, + value); + } + + /// + /// Asynchronously set the value of the Opacity property after render. + /// + public async Task SetOpacity(double value) + { + Opacity = value; + ModifiedParameters["Opacity"] = value; + + if (JsComponentReference is null) + { + return; + } + + await JsComponentReference!.InvokeVoidAsync("setProperty", + CancellationTokenSource.Token, + "opacity", + value); + } + +#endregion + + +#region Property Getters + + /// + /// Asynchronously retrieve the current value of the FullExtent property. + /// + public async Task GetFullExtent() + { + if (JsComponentReference is null) + { + return null; + } + + return await JsComponentReference!.InvokeAsync("getFullExtent", + CancellationTokenSource.Token); + } + + /// + /// Asynchronously retrieve the current value of the Opacity property. + /// + public async Task GetOpacity() + { + if (JsComponentReference is null) + { + return null; + } + + return await JsComponentReference!.InvokeAsync("getProperty", + CancellationTokenSource.Token, + "opacity"); + } + +#endregion } \ No newline at end of file diff --git a/src/dymaptic.GeoBlazor.Core/Components/LocationService.gb.cs b/src/dymaptic.GeoBlazor.Core/Components/LocationService.gb.cs deleted file mode 100644 index da0f6c326..000000000 --- a/src/dymaptic.GeoBlazor.Core/Components/LocationService.gb.cs +++ /dev/null @@ -1,15 +0,0 @@ -// File auto-generated by dymaptic tooling. Any changes made here will be lost on future generation. To override functionality, use the relevant root .cs file. - -namespace dymaptic.GeoBlazor.Core.Components; - - -/// -/// GeoBlazor Docs -/// A convenience module for importing locator functions when developing with -/// TypeScript. -/// ArcGIS Maps SDK for JavaScript -/// -public partial class LocationService -{ - -} diff --git a/src/dymaptic.GeoBlazor.Core/Components/MapComponent.razor.cs b/src/dymaptic.GeoBlazor.Core/Components/MapComponent.razor.cs index 34732f1c8..c2ae50183 100644 --- a/src/dymaptic.GeoBlazor.Core/Components/MapComponent.razor.cs +++ b/src/dymaptic.GeoBlazor.Core/Components/MapComponent.razor.cs @@ -802,7 +802,8 @@ public Task SetVisible(bool visible) try { - if (await jsonStreamReference.ReadJsStreamReferenceAsJSON(MapComponentType) is MapComponent deserialized) + if (await jsonStreamReference.ReadJsStreamReferenceAsJSON(MapComponentType, View?.QueryResultsMaxSizeLimit) + is MapComponent deserialized) { if (IsDisposed) { diff --git a/src/dymaptic.GeoBlazor.Core/Components/OpacityVariable.gb.cs b/src/dymaptic.GeoBlazor.Core/Components/OpacityVariable.gb.cs index af43156eb..a5f969b2e 100644 --- a/src/dymaptic.GeoBlazor.Core/Components/OpacityVariable.gb.cs +++ b/src/dymaptic.GeoBlazor.Core/Components/OpacityVariable.gb.cs @@ -50,7 +50,7 @@ public OpacityVariable() /// Arcade expression as defined in the valueExpression property. /// ArcGIS Maps SDK for JavaScript /// - public OpacityVariable(string field, + public OpacityVariable(string? field, string? normalizationField = null, IReadOnlyList? stops = null, VisualVariableLegendOptions? legendOptions = null, diff --git a/src/dymaptic.GeoBlazor.Core/Components/Portal.gb.cs b/src/dymaptic.GeoBlazor.Core/Components/Portal.gb.cs index 6ad26389b..da3d48322 100644 --- a/src/dymaptic.GeoBlazor.Core/Components/Portal.gb.cs +++ b/src/dymaptic.GeoBlazor.Core/Components/Portal.gb.cs @@ -427,15 +427,6 @@ public Portal(PortalAccess? access = null, public override void ValidateRequiredGeneratedChildren() { DefaultExtent?.ValidateRequiredGeneratedChildren(); - - if (FeaturedGroups is not null) - { - foreach (PortalFeaturedGroups child in FeaturedGroups) - { - child.ValidateRequiredGeneratedChildren(); - } - } - User?.ValidateRequiredGeneratedChildren(); base.ValidateRequiredGeneratedChildren(); } @@ -452,16 +443,6 @@ protected override async ValueTask RegisterGeneratedChildComponent(MapComp ModifiedParameters[nameof(DefaultExtent)] = DefaultExtent; } - return true; - case PortalFeaturedGroups featuredGroups: - FeaturedGroups ??= []; - - if (!FeaturedGroups.Contains(featuredGroups)) - { - FeaturedGroups = [..FeaturedGroups, featuredGroups]; - ModifiedParameters[nameof(FeaturedGroups)] = FeaturedGroups; - } - return true; case PortalUser user: if (user != User) @@ -485,11 +466,6 @@ protected override async ValueTask UnregisterGeneratedChildComponent(MapCo DefaultExtent = null; ModifiedParameters[nameof(DefaultExtent)] = DefaultExtent; - return true; - case PortalFeaturedGroups featuredGroups: - FeaturedGroups = FeaturedGroups?.Where(f => f != featuredGroups).ToList(); - ModifiedParameters[nameof(FeaturedGroups)] = FeaturedGroups; - return true; case PortalUser _: User = null; @@ -4910,51 +4886,6 @@ await CoreJsModule.InvokeVoidAsync("setProperty", CancellationTokenSource.Token, JsComponentReference, "eueiEnabled", value); } - /// - /// Asynchronously set the value of the FeaturedGroups property after render. - /// - /// - /// The value to set. - /// - public async Task SetFeaturedGroups(IReadOnlyList? value) - { - if (value is not null) - { - foreach (PortalFeaturedGroups item in value) - { - item.UpdateGeoBlazorReferences(CoreJsModule!, ProJsModule, View, this, Layer); - } - } - -#pragma warning disable BL0005 - FeaturedGroups = value; -#pragma warning restore BL0005 - ModifiedParameters[nameof(FeaturedGroups)] = value; - - if (CoreJsModule is null) - { - return; - } - - try - { - JsComponentReference ??= await CoreJsModule.InvokeAsync( - "getJsComponent", CancellationTokenSource.Token, Id); - } - catch (JSException) - { - // this is expected if the component is not yet built - } - - if (JsComponentReference is null) - { - return; - } - - await JsComponentReference.InvokeVoidAsync("setFeaturedGroups", - CancellationTokenSource.Token, value); - } - /// /// Asynchronously set the value of the FeaturedItemsGroupQuery property after render. /// @@ -6269,20 +6200,6 @@ public async Task AddToAuthorizedCrossOriginDomains(params string[] values) await SetAuthorizedCrossOriginDomains(join); } - /// - /// Asynchronously adds elements to the FeaturedGroups property. - /// - /// - /// The elements to add. - /// - public async Task AddToFeaturedGroups(params PortalFeaturedGroups[] values) - { - PortalFeaturedGroups[] join = FeaturedGroups is null - ? values - : [..FeaturedGroups, ..values]; - await SetFeaturedGroups(join); - } - /// /// Asynchronously adds elements to the RotatorPanels property. /// @@ -6318,22 +6235,6 @@ public async Task RemoveFromAuthorizedCrossOriginDomains(params string[] values) await SetAuthorizedCrossOriginDomains(AuthorizedCrossOriginDomains.Except(values).ToArray()); } - /// - /// Asynchronously remove an element from the FeaturedGroups property. - /// - /// - /// The elements to remove. - /// - public async Task RemoveFromFeaturedGroups(params PortalFeaturedGroups[] values) - { - if (FeaturedGroups is null) - { - return; - } - - await SetFeaturedGroups(FeaturedGroups.Except(values).ToArray()); - } - /// /// Asynchronously remove an element from the RotatorPanels property. /// diff --git a/src/dymaptic.GeoBlazor.Core/Components/PortalFeaturedGroups.cs b/src/dymaptic.GeoBlazor.Core/Components/PortalFeaturedGroups.cs deleted file mode 100644 index 3ade21e31..000000000 --- a/src/dymaptic.GeoBlazor.Core/Components/PortalFeaturedGroups.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace dymaptic.GeoBlazor.Core.Components; - -public partial class PortalFeaturedGroups -{ - // Add custom code to this file to override generated code -} \ No newline at end of file diff --git a/src/dymaptic.GeoBlazor.Core/Components/PortalFeaturedGroups.gb.cs b/src/dymaptic.GeoBlazor.Core/Components/PortalFeaturedGroups.gb.cs deleted file mode 100644 index ccf1fb289..000000000 --- a/src/dymaptic.GeoBlazor.Core/Components/PortalFeaturedGroups.gb.cs +++ /dev/null @@ -1,229 +0,0 @@ -// File auto-generated by dymaptic tooling. Any changes made here will be lost on future generation. To override functionality, use the relevant root .cs file. - -namespace dymaptic.GeoBlazor.Core.Components; - - -/// -/// GeoBlazor Docs -/// The featured groups for the portal. -/// ArcGIS Maps SDK for JavaScript -/// -public partial class PortalFeaturedGroups : MapComponent -{ - - /// - /// Parameterless constructor for use as a Razor Component. - /// - [ActivatorUtilitiesConstructor] - public PortalFeaturedGroups() - { - } - - /// - /// Constructor for use in C# code. Use named parameters (e.g., item1: value1, item2: value2) to set properties in any order. - /// - /// - /// Name of the group owner. - /// ArcGIS Maps SDK for JavaScript - /// - /// - /// Group title. - /// ArcGIS Maps SDK for JavaScript - /// - public PortalFeaturedGroups( - string? owner = null, - string? title = null) - { - AllowRender = false; -#pragma warning disable BL0005 - Owner = owner; - Title = title; -#pragma warning restore BL0005 - } - - -#region Public Properties / Blazor Parameters - - /// - /// GeoBlazor Docs - /// Name of the group owner. - /// ArcGIS Maps SDK for JavaScript - /// - [ArcGISProperty] - [Parameter] - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - public string? Owner { get; set; } - - /// - /// GeoBlazor Docs - /// Group title. - /// ArcGIS Maps SDK for JavaScript - /// - [ArcGISProperty] - [Parameter] - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - public string? Title { get; set; } - -#endregion - -#region Property Getters - - /// - /// Asynchronously retrieve the current value of the Owner property. - /// - public async Task GetOwner() - { - if (CoreJsModule is null) - { - return Owner; - } - - try - { - JsComponentReference ??= await CoreJsModule.InvokeAsync( - "getJsComponent", CancellationTokenSource.Token, Id); - } - catch (JSException) - { - // this is expected if the component is not yet built - } - - if (JsComponentReference is null) - { - return Owner; - } - - // get the property value - string? result = await JsComponentReference!.InvokeAsync("getProperty", - CancellationTokenSource.Token, "owner"); - if (result is not null) - { -#pragma warning disable BL0005 - Owner = result; -#pragma warning restore BL0005 - ModifiedParameters[nameof(Owner)] = Owner; - } - - return Owner; - } - - /// - /// Asynchronously retrieve the current value of the Title property. - /// - public async Task GetTitle() - { - if (CoreJsModule is null) - { - return Title; - } - - try - { - JsComponentReference ??= await CoreJsModule.InvokeAsync( - "getJsComponent", CancellationTokenSource.Token, Id); - } - catch (JSException) - { - // this is expected if the component is not yet built - } - - if (JsComponentReference is null) - { - return Title; - } - - // get the property value - string? result = await JsComponentReference!.InvokeAsync("getProperty", - CancellationTokenSource.Token, "title"); - if (result is not null) - { -#pragma warning disable BL0005 - Title = result; -#pragma warning restore BL0005 - ModifiedParameters[nameof(Title)] = Title; - } - - return Title; - } - -#endregion - -#region Property Setters - - /// - /// Asynchronously set the value of the Owner property after render. - /// - /// - /// The value to set. - /// - public async Task SetOwner(string? value) - { -#pragma warning disable BL0005 - Owner = value; -#pragma warning restore BL0005 - ModifiedParameters[nameof(Owner)] = value; - - if (CoreJsModule is null) - { - return; - } - - try - { - JsComponentReference ??= await CoreJsModule.InvokeAsync( - "getJsComponent", CancellationTokenSource.Token, Id); - } - catch (JSException) - { - // this is expected if the component is not yet built - } - - if (JsComponentReference is null) - { - return; - } - - await CoreJsModule.InvokeVoidAsync("setProperty", CancellationTokenSource.Token, - JsComponentReference, "owner", value); - } - - /// - /// Asynchronously set the value of the Title property after render. - /// - /// - /// The value to set. - /// - public async Task SetTitle(string? value) - { -#pragma warning disable BL0005 - Title = value; -#pragma warning restore BL0005 - ModifiedParameters[nameof(Title)] = value; - - if (CoreJsModule is null) - { - return; - } - - try - { - JsComponentReference ??= await CoreJsModule.InvokeAsync( - "getJsComponent", CancellationTokenSource.Token, Id); - } - catch (JSException) - { - // this is expected if the component is not yet built - } - - if (JsComponentReference is null) - { - return; - } - - await CoreJsModule.InvokeVoidAsync("setProperty", CancellationTokenSource.Token, - JsComponentReference, "title", value); - } - -#endregion - -} diff --git a/src/dymaptic.GeoBlazor.Core/Components/RotationVariable.gb.cs b/src/dymaptic.GeoBlazor.Core/Components/RotationVariable.gb.cs index 86a5486f1..881e8a2dd 100644 --- a/src/dymaptic.GeoBlazor.Core/Components/RotationVariable.gb.cs +++ b/src/dymaptic.GeoBlazor.Core/Components/RotationVariable.gb.cs @@ -55,7 +55,7 @@ public RotationVariable() /// Arcade expression as defined in the valueExpression property. /// ArcGIS Maps SDK for JavaScript /// - public RotationVariable(string field, + public RotationVariable(string? field, Axis? axis = null, RotationType? rotationType = null, VisualVariableLegendOptions? legendOptions = null, diff --git a/src/dymaptic.GeoBlazor.Core/Components/SizeVariable.cs b/src/dymaptic.GeoBlazor.Core/Components/SizeVariable.cs index db3d61bbf..9fbef123f 100644 --- a/src/dymaptic.GeoBlazor.Core/Components/SizeVariable.cs +++ b/src/dymaptic.GeoBlazor.Core/Components/SizeVariable.cs @@ -1,8 +1,129 @@ namespace dymaptic.GeoBlazor.Core.Components; +[JsonConverter(typeof(SizeVariableConverter))] public partial class SizeVariable : VisualVariable, IMapComponent { + /// + /// Parameterless constructor for use as a Razor Component. + /// + [ActivatorUtilitiesConstructor] + [CodeGenerationIgnore] + public SizeVariable() + { + } + /// + /// Constructor for use in C# code. Use named parameters (e.g., item1: value1, item2: value2) to set properties in any order. + /// + /// + /// The name of the numeric attribute field that contains the data + /// values used to determine the color/opacity/size/rotation of each feature. + /// ArcGIS Maps SDK for JavaScript + /// + /// + /// The size used to render a feature containing the minimum data value. + /// ArcGIS Maps SDK for JavaScript + /// + /// + /// The size used to render a feature containing the maximum data value. + /// ArcGIS Maps SDK for JavaScript + /// + /// + /// The minimum data value used in the size ramp. + /// ArcGIS Maps SDK for JavaScript + /// + /// + /// The maximum data value used in the size ramp. + /// ArcGIS Maps SDK for JavaScript + /// + /// + /// Specifies how to apply the data value when mapping real-world sizes. + /// ArcGIS Maps SDK for JavaScript + /// + /// + /// Indicates the unit of measurement used to interpret the value returned by field or valueExpression. + /// ArcGIS Maps SDK for JavaScript + /// + /// + /// The name of the numeric attribute field used to normalize + /// the data in the given field. + /// ArcGIS Maps SDK for JavaScript + /// + /// + /// This value must be `outline` when scaling polygon outline widths + /// based on the view scale. + /// ArcGIS Maps SDK for JavaScript + /// + /// + /// When setting a size visual variable on a renderer using an + /// ObjectSymbol3DLayer, this property indicates whether to apply the value + /// defined by the height, + /// width, or + /// depth properties to the corresponding axis of + /// this visual variable instead of proportionally scaling this axis' value. + /// ArcGIS Maps SDK for JavaScript + /// + /// + /// Only applicable when working in a SceneView. + /// default "all" + /// ArcGIS Maps SDK for JavaScript + /// + /// + /// An Arcade expression following the specification + /// defined by the Arcade Visualization Profile. + /// ArcGIS Maps SDK for JavaScript + /// + /// + /// The title identifying and describing the associated + /// Arcade expression as defined in the valueExpression property. + /// ArcGIS Maps SDK for JavaScript + /// + /// + /// An object providing options for displaying the visual variable in + /// the Legend. + /// ArcGIS Maps SDK for JavaScript + /// + /// + /// An array of objects that defines the mapping of data values returned from field or + /// valueExpression to icon sizes. + /// ArcGIS Maps SDK for JavaScript + /// + [CodeGenerationIgnore] + public SizeVariable(string? field, + Dimension? minSize = null, + Dimension? maxSize = null, + double? minDataValue = null, + double? maxDataValue = null, + VisualValueRepresentation? valueRepresentation = null, + VisualValueUnit? valueUnit = null, + string? normalizationField = null, + string? target = null, + bool? useSymbolValue = null, + VisualAxis? axis = null, + string? valueExpression = null, + string? valueExpressionTitle = null, + VisualVariableLegendOptions? legendOptions = null, + IReadOnlyList? stops = null) + { + AllowRender = false; +#pragma warning disable BL0005 + Field = field; + MinSize = minSize; + MaxSize = maxSize; + MinDataValue = minDataValue; + MaxDataValue = maxDataValue; + ValueRepresentation = valueRepresentation; + ValueUnit = valueUnit; + NormalizationField = normalizationField; + Target = target; + UseSymbolValue = useSymbolValue; + Axis = axis; + ValueExpression = valueExpression; + ValueExpressionTitle = valueExpressionTitle; + LegendOptions = legendOptions; + Stops = stops; +#pragma warning restore BL0005 + } /// public override VisualVariableType Type => VisualVariableType.Size; @@ -13,7 +134,7 @@ public partial class SizeVariable : VisualVariable, IMapComponent [Parameter] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public VisualAxis? Axis { get; set; } - + /// /// The minimum data value used in the size ramp. /// @@ -35,6 +156,7 @@ public partial class SizeVariable : VisualVariable, IMapComponent /// [Parameter] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [CodeGenerationIgnore] public Dimension? MinSize { get; set; } /// @@ -44,36 +166,55 @@ public partial class SizeVariable : VisualVariable, IMapComponent /// [Parameter] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [CodeGenerationIgnore] public Dimension? MaxSize { get; set; } - + + /// + /// The size used to render a feature containing the minimum data value. + /// When a SizeVariable is used, the size of features whose data value (defined in field or valueExpression) is greater than or equal to the minDataValue for the given view scale. + /// + [Parameter] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [CodeGenerationIgnore] + public SizeVariable? MinSizeVariable { get; set; } + + /// + /// The size used to render a feature containing the maximum data value. + /// When a SizeVariable is used, the size of features whose data value (defined in field or valueExpression) is greater than or equal to the maxDataValue for the given view scale. + /// + [Parameter] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [CodeGenerationIgnore] + public SizeVariable? MaxSizeVariable { get; set; } + /// /// The name of the numeric attribute field used to normalize the data in the given field. If this field is used, then the values in maxDataValue and minDataValue or stops should be normalized as percentages or ratios. /// [Parameter] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public string? NormalizationField { get; set; } - + /// /// This value must be outline when scaling polygon outline widths based on the view scale. If scale-dependent icons are desired, then this property should be ignored. /// [Parameter] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public string? Target { get; set; } - + /// /// When setting a size visual variable on a renderer using an ObjectSymbol3DLayer, this property indicates whether to apply the value defined by the height, width, or depth properties to the corresponding axis of this visual variable instead of proportionally scaling this axis' value. /// [Parameter] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public bool? UseSymbolValue { get; set; } - + /// /// Specifies how to apply the data value when mapping real-world sizes. /// [Parameter] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public VisualValueRepresentation? ValueRepresentation { get; set; } - + /// /// Indicates the unit of measurement used to interpret the value returned by field or valueExpression. For 3D volumetric symbols the default is meters. This property should not be used if the data value represents a thematic quantity (e.g. traffic count, census data, etc.). /// @@ -81,7 +222,6 @@ public partial class SizeVariable : VisualVariable, IMapComponent [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public VisualValueUnit? ValueUnit { get; set; } - /// public override async Task RegisterChildComponent(MapComponent child) { @@ -98,7 +238,7 @@ public override async Task RegisterChildComponent(MapComponent child) break; } } - + /// public override async Task UnregisterChildComponent(MapComponent child) { @@ -114,5 +254,4 @@ public override async Task UnregisterChildComponent(MapComponent child) break; } } - } \ No newline at end of file diff --git a/src/dymaptic.GeoBlazor.Core/Components/SizeVariable.gb.cs b/src/dymaptic.GeoBlazor.Core/Components/SizeVariable.gb.cs index 4969a056f..6d9e8e03c 100644 --- a/src/dymaptic.GeoBlazor.Core/Components/SizeVariable.gb.cs +++ b/src/dymaptic.GeoBlazor.Core/Components/SizeVariable.gb.cs @@ -10,127 +10,6 @@ namespace dymaptic.GeoBlazor.Core.Components; /// public partial class SizeVariable { - /// - /// Parameterless constructor for use as a Razor Component. - /// - [ActivatorUtilitiesConstructor] - public SizeVariable() - { - } - - /// - /// Constructor for use in C# code. Use named parameters (e.g., item1: value1, item2: value2) to set properties in any order. - /// - /// - /// The name of the numeric attribute field that contains the data - /// values used to determine the color/opacity/size/rotation of each feature. - /// ArcGIS Maps SDK for JavaScript - /// - /// - /// The size used to render a feature containing the minimum data value. - /// ArcGIS Maps SDK for JavaScript - /// - /// - /// The size used to render a feature containing the maximum data value. - /// ArcGIS Maps SDK for JavaScript - /// - /// - /// The minimum data value used in the size ramp. - /// ArcGIS Maps SDK for JavaScript - /// - /// - /// The maximum data value used in the size ramp. - /// ArcGIS Maps SDK for JavaScript - /// - /// - /// Specifies how to apply the data value when mapping real-world sizes. - /// ArcGIS Maps SDK for JavaScript - /// - /// - /// Indicates the unit of measurement used to interpret the value returned by field or valueExpression. - /// ArcGIS Maps SDK for JavaScript - /// - /// - /// The name of the numeric attribute field used to normalize - /// the data in the given field. - /// ArcGIS Maps SDK for JavaScript - /// - /// - /// This value must be `outline` when scaling polygon outline widths - /// based on the view scale. - /// ArcGIS Maps SDK for JavaScript - /// - /// - /// When setting a size visual variable on a renderer using an - /// ObjectSymbol3DLayer, this property indicates whether to apply the value - /// defined by the height, - /// width, or - /// depth properties to the corresponding axis of - /// this visual variable instead of proportionally scaling this axis' value. - /// ArcGIS Maps SDK for JavaScript - /// - /// - /// Only applicable when working in a SceneView. - /// default "all" - /// ArcGIS Maps SDK for JavaScript - /// - /// - /// An Arcade expression following the specification - /// defined by the Arcade Visualization Profile. - /// ArcGIS Maps SDK for JavaScript - /// - /// - /// The title identifying and describing the associated - /// Arcade expression as defined in the valueExpression property. - /// ArcGIS Maps SDK for JavaScript - /// - /// - /// An object providing options for displaying the visual variable in - /// the Legend. - /// ArcGIS Maps SDK for JavaScript - /// - /// - /// An array of objects that defines the mapping of data values returned from field or - /// valueExpression to icon sizes. - /// ArcGIS Maps SDK for JavaScript - /// - public SizeVariable(string field, - Dimension? minSize = null, - Dimension? maxSize = null, - double? minDataValue = null, - double? maxDataValue = null, - VisualValueRepresentation? valueRepresentation = null, - VisualValueUnit? valueUnit = null, - string? normalizationField = null, - string? target = null, - bool? useSymbolValue = null, - VisualAxis? axis = null, - string? valueExpression = null, - string? valueExpressionTitle = null, - VisualVariableLegendOptions? legendOptions = null, - IReadOnlyList? stops = null) - { - AllowRender = false; -#pragma warning disable BL0005 - Field = field; - MinSize = minSize; - MaxSize = maxSize; - MinDataValue = minDataValue; - MaxDataValue = maxDataValue; - ValueRepresentation = valueRepresentation; - ValueUnit = valueUnit; - NormalizationField = normalizationField; - Target = target; - UseSymbolValue = useSymbolValue; - Axis = axis; - ValueExpression = valueExpression; - ValueExpressionTitle = valueExpressionTitle; - LegendOptions = legendOptions; - Stops = stops; -#pragma warning restore BL0005 - } - - #region Public Properties / Blazor Parameters /// diff --git a/src/dymaptic.GeoBlazor.Core/Components/VisualVariable.gb.cs b/src/dymaptic.GeoBlazor.Core/Components/VisualVariable.gb.cs index dcc44fbdd..eb732fc46 100644 --- a/src/dymaptic.GeoBlazor.Core/Components/VisualVariable.gb.cs +++ b/src/dymaptic.GeoBlazor.Core/Components/VisualVariable.gb.cs @@ -12,11 +12,6 @@ public abstract partial class VisualVariable /// public override void ValidateRequiredGeneratedChildren() { - if (Field is null) - { - throw new MissingRequiredChildElementException(nameof(VisualVariable), nameof(Field)); - } - LegendOptions?.ValidateRequiredGeneratedChildren(); base.ValidateRequiredGeneratedChildren(); } @@ -65,9 +60,8 @@ protected override async ValueTask UnregisterGeneratedChildComponent(MapCo /// [ArcGISProperty] [Parameter] - [RequiredProperty] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - public string Field { get; set; } = null!; + public string? Field { get; set; } /// /// GeoBlazor Docs diff --git a/src/dymaptic.GeoBlazor.Core/Model/GeometryEngine.cs b/src/dymaptic.GeoBlazor.Core/Model/GeometryEngine.cs index 6257766a2..8ffd8984f 100644 --- a/src/dymaptic.GeoBlazor.Core/Model/GeometryEngine.cs +++ b/src/dymaptic.GeoBlazor.Core/Model/GeometryEngine.cs @@ -12,17 +12,13 @@ namespace dymaptic.GeoBlazor.Core.Model; /// /// [CodeGenerationIgnore] -public class GeometryEngine : LogicComponent +public class GeometryEngine( + IAppValidator appValidator, + IJSRuntime jsRuntime, + JsModuleManager jsModuleManager, + AuthenticationManager authenticationManager) + : LogicComponent(appValidator, jsRuntime, jsModuleManager, authenticationManager) { - /// - /// Default Constructor - /// - public GeometryEngine(IAppValidator appValidator, IJSRuntime jsRuntime, JsModuleManager jsModuleManager, - AuthenticationManager authenticationManager) - : base(appValidator, jsRuntime, jsModuleManager, authenticationManager) - { - } - /// protected override string ComponentName => nameof(GeometryEngine).ToLowerFirstChar(); @@ -127,7 +123,7 @@ public async Task Buffer(IEnumerable geometries, IEnumerabl GeometryEngineLinearUnit? unit, bool? unionResults, CancellationToken cancellationToken = default) { return await InvokeAsync(nameof(ProjectionEngine), nameof(Buffer), - cancellationToken, geometries, distances, unit, unionResults); + QueryResultsMaxSizeLimit, cancellationToken, geometries, distances, unit, unionResults); } /// @@ -183,7 +179,7 @@ public async Task Buffer(IEnumerable geometries, IEnumerabl CancellationToken cancellationToken = default) { return await InvokeAsync(nameof(ProjectionEngine), nameof(Buffer), - cancellationToken, geometry, distance, unit); + QueryResultsMaxSizeLimit, cancellationToken, geometry, distance, unit); } /// @@ -203,7 +199,7 @@ public async Task Buffer(IEnumerable geometries, IEnumerabl public async Task Clip(Geometry geometry, Extent extent, CancellationToken cancellationToken = default) { return await InvokeAsync(nameof(ProjectionEngine), nameof(Clip), - cancellationToken, geometry, extent); + QueryResultsMaxSizeLimit, cancellationToken, geometry, extent); } /// @@ -225,7 +221,7 @@ public async Task Contains(Geometry containerGeometry, Geometry insideGeom CancellationToken cancellationToken = default) { return await InvokeAsync(nameof(ProjectionEngine), nameof(Contains), - cancellationToken, containerGeometry, insideGeometry); + QueryResultsMaxSizeLimit, cancellationToken, containerGeometry, insideGeometry); } /// @@ -249,7 +245,7 @@ public async Task Contains(Geometry containerGeometry, Geometry insideGeom CancellationToken cancellationToken = default) { return await InvokeAsync(nameof(ProjectionEngine), nameof(ConvexHull), - cancellationToken, geometries, merge); + QueryResultsMaxSizeLimit, cancellationToken, geometries, merge); } /// @@ -269,7 +265,7 @@ public async Task Contains(Geometry containerGeometry, Geometry insideGeom public async Task ConvexHull(Geometry geometry, CancellationToken cancellationToken = default) { return await InvokeAsync(nameof(ProjectionEngine), nameof(ConvexHull), - cancellationToken, geometry); + QueryResultsMaxSizeLimit, cancellationToken, geometry); } /// @@ -290,7 +286,7 @@ public async Task Crosses(Geometry geometry1, Geometry geometry2, CancellationToken cancellationToken = default) { return await InvokeAsync(nameof(ProjectionEngine), nameof(Crosses), - cancellationToken, geometry1, geometry2); + QueryResultsMaxSizeLimit, cancellationToken, geometry1, geometry2); } /// @@ -316,7 +312,7 @@ public async Task Crosses(Geometry geometry1, Geometry geometry2, public async Task Cut(Geometry geometry, Polyline cutter, CancellationToken cancellationToken = default) { return await InvokeAsync(nameof(ProjectionEngine), nameof(Cut), - cancellationToken, geometry, cutter); + QueryResultsMaxSizeLimit, cancellationToken, geometry, cutter); } /// @@ -340,7 +336,7 @@ public async Task Densify(Geometry geometry, double maxSegmentLength, GeometryEngineLinearUnit? maxSegmentLengthUnit = null, CancellationToken cancellationToken = default) { return await InvokeAsync(nameof(ProjectionEngine), nameof(Densify), - cancellationToken, geometry, maxSegmentLength, maxSegmentLengthUnit); + QueryResultsMaxSizeLimit, cancellationToken, geometry, maxSegmentLength, maxSegmentLengthUnit); } /// @@ -362,7 +358,7 @@ public async Task Densify(Geometry geometry, double maxSegmentLength, CancellationToken cancellationToken = default) { return await InvokeAsync(nameof(ProjectionEngine), nameof(Difference), - cancellationToken, geometries, subtractor); + QueryResultsMaxSizeLimit, cancellationToken, geometries, subtractor); } /// @@ -384,7 +380,7 @@ public async Task Densify(Geometry geometry, double maxSegmentLength, CancellationToken cancellationToken = default) { return await InvokeAsync(nameof(ProjectionEngine), nameof(Difference), - cancellationToken, geometry, subtractor); + QueryResultsMaxSizeLimit, cancellationToken, geometry, subtractor); } /// @@ -405,7 +401,7 @@ public async Task Disjoint(Geometry geometry1, Geometry geometry2, CancellationToken cancellationToken = default) { return await InvokeAsync(nameof(ProjectionEngine), nameof(Disjoint), - cancellationToken, geometry1, geometry2); + QueryResultsMaxSizeLimit, cancellationToken, geometry1, geometry2); } /// @@ -430,7 +426,7 @@ public async Task Distance(Geometry geometry1, Geometry geometry2, GeometryEngineLinearUnit? distanceUnit = null, CancellationToken cancellationToken = default) { return await InvokeAsync(nameof(ProjectionEngine), nameof(Distance), - cancellationToken, geometry1, geometry2, distanceUnit); + QueryResultsMaxSizeLimit, cancellationToken, geometry1, geometry2, distanceUnit); } /// @@ -455,7 +451,7 @@ public async Task AreEqual(Geometry geometry1, Geometry geometry2, CancellationToken cancellationToken = default) { return await InvokeAsync(nameof(ProjectionEngine), nameof(AreEqual), - cancellationToken, geometry1, geometry2); + QueryResultsMaxSizeLimit, cancellationToken, geometry1, geometry2); } /// @@ -474,7 +470,7 @@ public async Task ExtendedSpatialReferenceInfo(SpatialRefe CancellationToken cancellationToken = default) { return await InvokeAsync(nameof(ProjectionEngine), - nameof(ExtendedSpatialReferenceInfo), cancellationToken, spatialReference); + nameof(ExtendedSpatialReferenceInfo), QueryResultsMaxSizeLimit, cancellationToken, spatialReference); } /// @@ -495,7 +491,7 @@ public async Task FlipHorizontal(Geometry geometry, Point? flipOrigin CancellationToken cancellationToken = default) { return await InvokeAsync(nameof(ProjectionEngine), nameof(FlipHorizontal), - cancellationToken, geometry, flipOrigin); + QueryResultsMaxSizeLimit, cancellationToken, geometry, flipOrigin); } /// @@ -516,7 +512,7 @@ public async Task FlipVertical(Geometry geometry, Point? flipOrigin = CancellationToken cancellationToken = default) { return await InvokeAsync(nameof(ProjectionEngine), nameof(FlipVertical), - cancellationToken, geometry, flipOrigin); + QueryResultsMaxSizeLimit, cancellationToken, geometry, flipOrigin); } /// @@ -544,7 +540,8 @@ public async Task FlipVertical(Geometry geometry, Point? flipOrigin = GeometryEngineLinearUnit? maxDeviationUnit = null, CancellationToken cancellationToken = default) { return await InvokeAsync(nameof(ProjectionEngine), nameof(Generalize), - cancellationToken, geometry, maxDeviation, removeDegenerateParts, maxDeviationUnit); + QueryResultsMaxSizeLimit, cancellationToken, geometry, maxDeviation, removeDegenerateParts, + maxDeviationUnit); } /// @@ -572,7 +569,7 @@ public async Task GeodesicArea(Polygon geometry, GeometryEngineAreaUnit? CancellationToken cancellationToken = default) { return await InvokeAsync(nameof(ProjectionEngine), nameof(GeodesicArea), - cancellationToken, geometry, unit); + QueryResultsMaxSizeLimit, cancellationToken, geometry, unit); } /// @@ -685,7 +682,7 @@ public async Task GeodesicBuffer(IEnumerable geometries, IE GeometryEngineLinearUnit? unit, bool? unionResults, CancellationToken cancellationToken = default) { return await InvokeAsync(nameof(ProjectionEngine), nameof(GeodesicBuffer), - cancellationToken, geometries, distances, unit, unionResults); + QueryResultsMaxSizeLimit, cancellationToken, geometries, distances, unit, unionResults); } /// @@ -702,7 +699,7 @@ public async Task GeodesicBuffer(IEnumerable geometries, IE /// than WGS84 (wkid: 4326), use geometryService.buffer(). /// /// - /// The buffer input geometru + /// The buffer input geometry /// /// /// The specified distance for buffering. @@ -731,7 +728,7 @@ public async Task GeodesicBuffer(IEnumerable geometries, IE /// than WGS84 (wkid: 4326), use geometryService.buffer(). /// /// - /// The buffer input geometru + /// The buffer input geometry /// /// /// The specified distance for buffering. @@ -748,7 +745,7 @@ public async Task GeodesicBuffer(IEnumerable geometries, IE CancellationToken cancellationToken = default) { return await InvokeAsync(nameof(ProjectionEngine), nameof(GeodesicBuffer), - cancellationToken, geometry, distance, unit); + QueryResultsMaxSizeLimit, cancellationToken, geometry, distance, unit); } /// @@ -795,7 +792,7 @@ public async Task GeodesicBuffer(IEnumerable geometries, IE GeometryEngineLinearUnit? maxSegmentLengthUnit, CancellationToken cancellationToken = default) { return await InvokeAsync(nameof(ProjectionEngine), nameof(GeodesicDensify), - cancellationToken, geometry, maxSegmentLength, maxSegmentLengthUnit); + QueryResultsMaxSizeLimit, cancellationToken, geometry, maxSegmentLength, maxSegmentLengthUnit); } /// @@ -823,7 +820,7 @@ public async Task GeodesicLength(Geometry geometry, GeometryEngineLinear CancellationToken cancellationToken = default) { return await InvokeAsync(nameof(ProjectionEngine), nameof(GeodesicLength), - cancellationToken, geometry, unit); + QueryResultsMaxSizeLimit, cancellationToken, geometry, unit); } /// @@ -846,7 +843,7 @@ public async Task Intersect(IEnumerable geometries1, Geome CancellationToken cancellationToken = default) { return await InvokeAsync(nameof(ProjectionEngine), nameof(Intersect), - cancellationToken, geometries1, geometry2); + QueryResultsMaxSizeLimit, cancellationToken, geometries1, geometry2); } /// @@ -869,7 +866,7 @@ public async Task Intersect(IEnumerable geometries1, Geome CancellationToken cancellationToken = default) { return await InvokeAsync(nameof(ProjectionEngine), nameof(Intersect), - cancellationToken, geometry1, geometry2); + QueryResultsMaxSizeLimit, cancellationToken, geometry1, geometry2); } /// @@ -890,7 +887,7 @@ public async Task Intersects(Geometry geometry1, Geometry geometry2, CancellationToken cancellationToken = default) { return await InvokeAsync(nameof(ProjectionEngine), nameof(Intersects), - cancellationToken, geometry1, geometry2); + QueryResultsMaxSizeLimit, cancellationToken, geometry1, geometry2); } /// @@ -908,7 +905,7 @@ public async Task Intersects(Geometry geometry1, Geometry geometry2, public async Task IsSimple(Geometry geometry, CancellationToken cancellationToken = default) { return await InvokeAsync(nameof(ProjectionEngine), nameof(IsSimple), - cancellationToken, geometry); + QueryResultsMaxSizeLimit, cancellationToken, geometry); } /// @@ -929,7 +926,7 @@ public async Task NearestCoordinate(Geometry geometry, Point in CancellationToken cancellationToken = default) { return await InvokeAsync(nameof(ProjectionEngine), nameof(NearestCoordinate), - cancellationToken, geometry, inputPoint); + QueryResultsMaxSizeLimit, cancellationToken, geometry, inputPoint); } /// @@ -950,7 +947,7 @@ public async Task NearestVertex(Geometry geometry, Point inputP CancellationToken cancellationToken = default) { return await InvokeAsync(nameof(ProjectionEngine), nameof(NearestVertex), - cancellationToken, geometry, inputPoint); + QueryResultsMaxSizeLimit, cancellationToken, geometry, inputPoint); } /// @@ -978,7 +975,7 @@ public async Task NearestVertices(Geometry geometry, Point in int maxVertexCountToReturn, CancellationToken cancellationToken = default) { return await InvokeAsync(nameof(ProjectionEngine), nameof(NearestVertices), - cancellationToken, geometry, inputPoint, searchRadius, maxVertexCountToReturn); + QueryResultsMaxSizeLimit, cancellationToken, geometry, inputPoint, searchRadius, maxVertexCountToReturn); } /// @@ -989,7 +986,8 @@ public async Task NearestVertices(Geometry geometry, Point in /// The geometries to offset. /// /// - /// The distance to offset the input geometry. Unless the unit option is set, the default is the geometry's spatial reference unit. + /// The distance to offset the input geometry. Unless the unit option is set, the default is the geometry's spatial + /// reference unit. /// /// /// The offset geometries. @@ -1010,10 +1008,12 @@ public async Task NearestVertices(Geometry geometry, Point in /// The geometries to offset. /// /// - /// The distance to offset the input geometry. Unless the unit option is set, the default is the geometry's spatial reference unit. + /// The distance to offset the input geometry. Unless the unit option is set, the default is the geometry's spatial + /// reference unit. /// /// - /// The length unit of the offset distance. The default is the input geometries spatial reference unit. An error will be thrown if this is set for Geographic Coordinate Systems. + /// The length unit of the offset distance. The default is the input geometries spatial reference unit. An error will + /// be thrown if this is set for Geographic Coordinate Systems. /// /// /// The offset geometries. @@ -1034,18 +1034,20 @@ public async Task NearestVertices(Geometry geometry, Point in /// The geometries to offset. /// /// - /// The distance to offset the input geometry. Unless the unit option is set, the default is the geometry's spatial reference unit. + /// The distance to offset the input geometry. Unless the unit option is set, the default is the geometry's spatial + /// reference unit. /// /// - /// The length unit of the offset distance. The default is the input geometries spatial reference unit. An error will be thrown if this is set for Geographic Coordinate Systems. + /// The length unit of the offset distance. The default is the input geometries spatial reference unit. An error will + /// be thrown if this is set for Geographic Coordinate Systems. /// /// /// The . Defines the join type of the offset geometry. Inner corners are always mitered. - /// Default value: "round" - /// - Round - a circular arc that is tangent to the ends of both offset line segments. - /// - Miter - the offset line segments are extended to their intersection point forming a sharp angle, unless that extension exceeds the miterLimit, in which case the result is a bevel. - /// - Bevel - the offset line segments are not extended; their endpoints are joined by a straight line. - /// - Square - same as miter for minor arcs greater than 90 degrees. For all other minor arcs, the offset line segments are extended by an extra distance before their endpoints are joined. + /// Default value: "round" - Round - a circular arc that is tangent to the ends of both offset line segments. - Miter - + /// the offset line segments are extended to their intersection point forming a sharp angle, unless that extension + /// exceeds the miterLimit, in which case the result is a bevel. - Bevel - the offset line segments are not extended; + /// their endpoints are joined by a straight line. - Square - same as miter for minor arcs greater than 90 degrees. For + /// all other minor arcs, the offset line segments are extended by an extra distance before their endpoints are joined. /// /// /// The offset geometries. @@ -1066,21 +1068,25 @@ public async Task NearestVertices(Geometry geometry, Point in /// The geometries to offset. /// /// - /// The distance to offset the input geometry. Unless the unit option is set, the default is the geometry's spatial reference unit. + /// The distance to offset the input geometry. Unless the unit option is set, the default is the geometry's spatial + /// reference unit. /// /// - /// The length unit of the offset distance. The default is the input geometries spatial reference unit. An error will be thrown if this is set for Geographic Coordinate Systems. + /// The length unit of the offset distance. The default is the input geometries spatial reference unit. An error will + /// be thrown if this is set for Geographic Coordinate Systems. /// /// /// The . Defines the join type of the offset geometry. Inner corners are always mitered. - /// Default value: "round" - /// - Round - a circular arc that is tangent to the ends of both offset line segments. - /// - Miter - the offset line segments are extended to their intersection point forming a sharp angle, unless that extension exceeds the miterLimit, in which case the result is a bevel. - /// - Bevel - the offset line segments are not extended; their endpoints are joined by a straight line. - /// - Square - same as miter for minor arcs greater than 90 degrees. For all other minor arcs, the offset line segments are extended by an extra distance before their endpoints are joined. + /// Default value: "round" - Round - a circular arc that is tangent to the ends of both offset line segments. - Miter - + /// the offset line segments are extended to their intersection point forming a sharp angle, unless that extension + /// exceeds the miterLimit, in which case the result is a bevel. - Bevel - the offset line segments are not extended; + /// their endpoints are joined by a straight line. - Square - same as miter for minor arcs greater than 90 degrees. For + /// all other minor arcs, the offset line segments are extended by an extra distance before their endpoints are joined. /// /// - /// Applicable only to mitered joins. Controls the appearance of angled lines to avoid excessively long and pointy corners. The miterLimit is multiplied by the offset distance and the result is the maximum mitered offset: joins which would result in a larger mitered offset will be beveled instead. + /// Applicable only to mitered joins. Controls the appearance of angled lines to avoid excessively long and pointy + /// corners. The miterLimit is multiplied by the offset distance and the result is the maximum mitered offset: joins + /// which would result in a larger mitered offset will be beveled instead. /// /// /// The offset geometries. @@ -1102,24 +1108,29 @@ public async Task NearestVertices(Geometry geometry, Point in /// The geometries to offset. /// /// - /// The distance to offset the input geometry. Unless the unit option is set, the default is the geometry's spatial reference unit. + /// The distance to offset the input geometry. Unless the unit option is set, the default is the geometry's spatial + /// reference unit. /// /// - /// The length unit of the offset distance. The default is the input geometries spatial reference unit. An error will be thrown if this is set for Geographic Coordinate Systems. + /// The length unit of the offset distance. The default is the input geometries spatial reference unit. An error will + /// be thrown if this is set for Geographic Coordinate Systems. /// /// /// The . Defines the join type of the offset geometry. Inner corners are always mitered. - /// Default value: "round" - /// - Round - a circular arc that is tangent to the ends of both offset line segments. - /// - Miter - the offset line segments are extended to their intersection point forming a sharp angle, unless that extension exceeds the miterLimit, in which case the result is a bevel. - /// - Bevel - the offset line segments are not extended; their endpoints are joined by a straight line. - /// - Square - same as miter for minor arcs greater than 90 degrees. For all other minor arcs, the offset line segments are extended by an extra distance before their endpoints are joined. + /// Default value: "round" - Round - a circular arc that is tangent to the ends of both offset line segments. - Miter - + /// the offset line segments are extended to their intersection point forming a sharp angle, unless that extension + /// exceeds the miterLimit, in which case the result is a bevel. - Bevel - the offset line segments are not extended; + /// their endpoints are joined by a straight line. - Square - same as miter for minor arcs greater than 90 degrees. For + /// all other minor arcs, the offset line segments are extended by an extra distance before their endpoints are joined. /// /// - /// Applicable only to mitered joins. Controls the appearance of angled lines to avoid excessively long and pointy corners. The miterLimit is multiplied by the offset distance and the result is the maximum mitered offset: joins which would result in a larger mitered offset will be beveled instead. + /// Applicable only to mitered joins. Controls the appearance of angled lines to avoid excessively long and pointy + /// corners. The miterLimit is multiplied by the offset distance and the result is the maximum mitered offset: joins + /// which would result in a larger mitered offset will be beveled instead. /// /// - /// The maximum distance of the resulting segments compared to the true circular arc (used only when joins is round). The algorithm never produces more than around 180 vertices for each round join. + /// The maximum distance of the resulting segments compared to the true circular arc (used only when joins is round). + /// The algorithm never produces more than around 180 vertices for each round join. /// /// /// The offset geometries. @@ -1131,7 +1142,8 @@ public async Task NearestVertices(Geometry geometry, Point in double? flattenError, CancellationToken cancellationToken = default) { return await InvokeAsync(nameof(ProjectionEngine), nameof(Offset), - cancellationToken, geometries, offsetDistance, offsetUnit, joinType, miterLimit, flattenError); + QueryResultsMaxSizeLimit, cancellationToken, geometries, offsetDistance, offsetUnit, joinType, miterLimit, + flattenError); } /// @@ -1142,7 +1154,8 @@ public async Task NearestVertices(Geometry geometry, Point in /// The geometry to offset. /// /// - /// The distance to offset the input geometry. Unless the unit option is set, the default is the geometry's spatial reference unit. + /// The distance to offset the input geometry. Unless the unit option is set, the default is the geometry's spatial + /// reference unit. /// /// /// The offset geometry. @@ -1163,10 +1176,12 @@ public async Task NearestVertices(Geometry geometry, Point in /// The geometry to offset. /// /// - /// The distance to offset the input geometry. Unless the unit option is set, the default is the geometry's spatial reference unit. + /// The distance to offset the input geometry. Unless the unit option is set, the default is the geometry's spatial + /// reference unit. /// /// - /// The length unit of the offset distance. The default is the input geometries spatial reference unit. An error will be thrown if this is set for Geographic Coordinate Systems. + /// The length unit of the offset distance. The default is the input geometries spatial reference unit. An error will + /// be thrown if this is set for Geographic Coordinate Systems. /// /// /// The offset geometry. @@ -1187,18 +1202,20 @@ public async Task NearestVertices(Geometry geometry, Point in /// The geometry to offset. /// /// - /// The distance to offset the input geometry. Unless the unit option is set, the default is the geometry's spatial reference unit. + /// The distance to offset the input geometry. Unless the unit option is set, the default is the geometry's spatial + /// reference unit. /// /// - /// The length unit of the offset distance. The default is the input geometries spatial reference unit. An error will be thrown if this is set for Geographic Coordinate Systems. + /// The length unit of the offset distance. The default is the input geometries spatial reference unit. An error will + /// be thrown if this is set for Geographic Coordinate Systems. /// /// /// The . Defines the join type of the offset geometry. Inner corners are always mitered. - /// Default value: "round" - /// - Round - a circular arc that is tangent to the ends of both offset line segments. - /// - Miter - the offset line segments are extended to their intersection point forming a sharp angle, unless that extension exceeds the miterLimit, in which case the result is a bevel. - /// - Bevel - the offset line segments are not extended; their endpoints are joined by a straight line. - /// - Square - same as miter for minor arcs greater than 90 degrees. For all other minor arcs, the offset line segments are extended by an extra distance before their endpoints are joined. + /// Default value: "round" - Round - a circular arc that is tangent to the ends of both offset line segments. - Miter - + /// the offset line segments are extended to their intersection point forming a sharp angle, unless that extension + /// exceeds the miterLimit, in which case the result is a bevel. - Bevel - the offset line segments are not extended; + /// their endpoints are joined by a straight line. - Square - same as miter for minor arcs greater than 90 degrees. For + /// all other minor arcs, the offset line segments are extended by an extra distance before their endpoints are joined. /// /// /// The offset geometry. @@ -1219,21 +1236,25 @@ public async Task NearestVertices(Geometry geometry, Point in /// The geometry to offset. /// /// - /// The distance to offset the input geometry. Unless the unit option is set, the default is the geometry's spatial reference unit. + /// The distance to offset the input geometry. Unless the unit option is set, the default is the geometry's spatial + /// reference unit. /// /// - /// The length unit of the offset distance. The default is the input geometries spatial reference unit. An error will be thrown if this is set for Geographic Coordinate Systems. + /// The length unit of the offset distance. The default is the input geometries spatial reference unit. An error will + /// be thrown if this is set for Geographic Coordinate Systems. /// /// /// The . Defines the join type of the offset geometry. Inner corners are always mitered. - /// Default value: "round" - /// - Round - a circular arc that is tangent to the ends of both offset line segments. - /// - Miter - the offset line segments are extended to their intersection point forming a sharp angle, unless that extension exceeds the miterLimit, in which case the result is a bevel. - /// - Bevel - the offset line segments are not extended; their endpoints are joined by a straight line. - /// - Square - same as miter for minor arcs greater than 90 degrees. For all other minor arcs, the offset line segments are extended by an extra distance before their endpoints are joined. + /// Default value: "round" - Round - a circular arc that is tangent to the ends of both offset line segments. - Miter - + /// the offset line segments are extended to their intersection point forming a sharp angle, unless that extension + /// exceeds the miterLimit, in which case the result is a bevel. - Bevel - the offset line segments are not extended; + /// their endpoints are joined by a straight line. - Square - same as miter for minor arcs greater than 90 degrees. For + /// all other minor arcs, the offset line segments are extended by an extra distance before their endpoints are joined. /// /// - /// Applicable only to mitered joins. Controls the appearance of angled lines to avoid excessively long and pointy corners. The miterLimit is multiplied by the offset distance and the result is the maximum mitered offset: joins which would result in a larger mitered offset will be beveled instead. + /// Applicable only to mitered joins. Controls the appearance of angled lines to avoid excessively long and pointy + /// corners. The miterLimit is multiplied by the offset distance and the result is the maximum mitered offset: joins + /// which would result in a larger mitered offset will be beveled instead. /// /// /// The offset geometry. @@ -1255,24 +1276,29 @@ public async Task NearestVertices(Geometry geometry, Point in /// The geometry to offset. /// /// - /// The distance to offset the input geometry. Unless the unit option is set, the default is the geometry's spatial reference unit. + /// The distance to offset the input geometry. Unless the unit option is set, the default is the geometry's spatial + /// reference unit. /// /// - /// The length unit of the offset distance. The default is the input geometries spatial reference unit. An error will be thrown if this is set for Geographic Coordinate Systems. + /// The length unit of the offset distance. The default is the input geometries spatial reference unit. An error will + /// be thrown if this is set for Geographic Coordinate Systems. /// /// /// The . Defines the join type of the offset geometry. Inner corners are always mitered. - /// Default value: "round" - /// - Round - a circular arc that is tangent to the ends of both offset line segments. - /// - Miter - the offset line segments are extended to their intersection point forming a sharp angle, unless that extension exceeds the miterLimit, in which case the result is a bevel. - /// - Bevel - the offset line segments are not extended; their endpoints are joined by a straight line. - /// - Square - same as miter for minor arcs greater than 90 degrees. For all other minor arcs, the offset line segments are extended by an extra distance before their endpoints are joined. + /// Default value: "round" - Round - a circular arc that is tangent to the ends of both offset line segments. - Miter - + /// the offset line segments are extended to their intersection point forming a sharp angle, unless that extension + /// exceeds the miterLimit, in which case the result is a bevel. - Bevel - the offset line segments are not extended; + /// their endpoints are joined by a straight line. - Square - same as miter for minor arcs greater than 90 degrees. For + /// all other minor arcs, the offset line segments are extended by an extra distance before their endpoints are joined. /// /// - /// Applicable only to mitered joins. Controls the appearance of angled lines to avoid excessively long and pointy corners. The miterLimit is multiplied by the offset distance and the result is the maximum mitered offset: joins which would result in a larger mitered offset will be beveled instead. + /// Applicable only to mitered joins. Controls the appearance of angled lines to avoid excessively long and pointy + /// corners. The miterLimit is multiplied by the offset distance and the result is the maximum mitered offset: joins + /// which would result in a larger mitered offset will be beveled instead. /// /// - /// The maximum distance of the resulting segments compared to the true circular arc (used only when joins is round). The algorithm never produces more than around 180 vertices for each round join. + /// The maximum distance of the resulting segments compared to the true circular arc (used only when joins is round). + /// The algorithm never produces more than around 180 vertices for each round join. /// /// /// The offset geometry. @@ -1284,7 +1310,8 @@ public async Task NearestVertices(Geometry geometry, Point in double? flattenError, CancellationToken cancellationToken = default) { return await InvokeAsync(nameof(ProjectionEngine), nameof(Offset), - cancellationToken, geometry, offsetDistance, offsetUnit, joinType, miterLimit, flattenError); + QueryResultsMaxSizeLimit, cancellationToken, geometry, offsetDistance, offsetUnit, joinType, miterLimit, + flattenError); } /// @@ -1305,7 +1332,7 @@ public async Task Overlaps(Geometry geometry1, Geometry geometry2, CancellationToken cancellationToken = default) { return await InvokeAsync(nameof(ProjectionEngine), nameof(Overlaps), - cancellationToken, geometry1, geometry2); + QueryResultsMaxSizeLimit, cancellationToken, geometry1, geometry2); } /// @@ -1346,7 +1373,7 @@ public async Task PlanarArea(Polygon geometry, GeometryEngineAreaUnit? u CancellationToken cancellationToken = default) { return await InvokeAsync(nameof(ProjectionEngine), nameof(PlanarArea), - cancellationToken, geometry, unit); + QueryResultsMaxSizeLimit, cancellationToken, geometry, unit); } /// @@ -1367,7 +1394,7 @@ public async Task PlanarArea(Polygon geometry, GeometryEngineAreaUnit? u public async Task PlanarLength(Geometry geometry, CancellationToken cancellationToken = default) { return await InvokeAsync(nameof(ProjectionEngine), nameof(PlanarLength), - cancellationToken, geometry); + QueryResultsMaxSizeLimit, cancellationToken, geometry); } /// @@ -1392,7 +1419,7 @@ public async Task PlanarLength(Geometry geometry, GeometryEngineLinearUn CancellationToken cancellationToken = default) { return await InvokeAsync(nameof(ProjectionEngine), nameof(PlanarLength), - cancellationToken, geometry, unit); + QueryResultsMaxSizeLimit, cancellationToken, geometry, unit); } /// @@ -1429,11 +1456,12 @@ public async Task Relate(Geometry geometry1, Geometry geometry2, string re CancellationToken cancellationToken = default) { return await InvokeAsync(nameof(ProjectionEngine), nameof(Relate), - cancellationToken, geometry1, geometry2, relation); + QueryResultsMaxSizeLimit, cancellationToken, geometry1, geometry2, relation); } /// - /// Rotates a geometry counterclockwise by the specified number of degrees. Rotation is around the given rotation point, or the centroid if not specified. + /// Rotates a geometry counterclockwise by the specified number of degrees. Rotation is around the given rotation + /// point, or the centroid if not specified. /// /// /// The geometry to rotate. @@ -1453,7 +1481,7 @@ public async Task Rotate(Geometry geometry, double angle, Point? rotat CancellationToken cancellationToken = default) { return await InvokeAsync(nameof(ProjectionEngine), nameof(Rotate), - cancellationToken, geometry, angle, rotationOrigin); + QueryResultsMaxSizeLimit, cancellationToken, geometry, angle, rotationOrigin); } /// @@ -1472,7 +1500,7 @@ public async Task Rotate(Geometry geometry, double angle, Point? rotat public async Task Simplify(Geometry geometry, CancellationToken cancellationToken = default) { return await InvokeAsync(nameof(ProjectionEngine), nameof(Simplify), - cancellationToken, geometry); + QueryResultsMaxSizeLimit, cancellationToken, geometry); } /// @@ -1494,7 +1522,7 @@ public async Task SymmetricDifference(IEnumerable leftGeom CancellationToken cancellationToken = default) { return await InvokeAsync(nameof(ProjectionEngine), nameof(SymmetricDifference), - cancellationToken, leftGeometries, rightGeometry); + QueryResultsMaxSizeLimit, cancellationToken, leftGeometries, rightGeometry); } /// @@ -1516,7 +1544,7 @@ public async Task SymmetricDifference(Geometry leftGeometry, Geometry CancellationToken cancellationToken = default) { return await InvokeAsync(nameof(ProjectionEngine), nameof(SymmetricDifference), - cancellationToken, leftGeometry, rightGeometry); + QueryResultsMaxSizeLimit, cancellationToken, leftGeometry, rightGeometry); } /// @@ -1537,7 +1565,7 @@ public async Task Touches(Geometry geometry1, Geometry geometry2, CancellationToken cancellationToken = default) { return await InvokeAsync(nameof(ProjectionEngine), nameof(Touches), - cancellationToken, geometry1, geometry2); + QueryResultsMaxSizeLimit, cancellationToken, geometry1, geometry2); } /// @@ -1568,7 +1596,7 @@ public async Task Touches(Geometry geometry1, Geometry geometry2, public async Task Union(IEnumerable geometries, CancellationToken cancellationToken = default) { return await InvokeAsync(nameof(ProjectionEngine), nameof(Union), - cancellationToken, geometries.Cast()); + QueryResultsMaxSizeLimit, cancellationToken, geometries.Cast()); } /// @@ -1589,7 +1617,7 @@ public async Task Within(Geometry innerGeometry, Geometry outerGeometry, CancellationToken cancellationToken = default) { return await InvokeAsync(nameof(ProjectionEngine), nameof(Within), - cancellationToken, innerGeometry, outerGeometry); + QueryResultsMaxSizeLimit, cancellationToken, innerGeometry, outerGeometry); } /// @@ -1620,7 +1648,7 @@ public async Task FromArcGisJson(string json, CancellationToken cancellati where T : Geometry { return await InvokeAsync(nameof(ProjectionEngine), nameof(FromArcGisJson), - cancellationToken, json, typeof(T).Name); + QueryResultsMaxSizeLimit, cancellationToken, json, typeof(T).Name); } /// @@ -1648,7 +1676,7 @@ public async Task ToArcGisJson(T geometry, CancellationToken cancella where T : Geometry { return await InvokeAsync(nameof(ProjectionEngine), nameof(ToArcGisJson), - cancellationToken, geometry); + QueryResultsMaxSizeLimit, cancellationToken, geometry); } /// @@ -1665,7 +1693,7 @@ public async Task Clone(T geometry, CancellationToken cancellationToken = where T : Geometry { return await InvokeAsync(nameof(ProjectionEngine), nameof(Clone), - cancellationToken, geometry); + QueryResultsMaxSizeLimit, cancellationToken, geometry); } /// @@ -1685,7 +1713,7 @@ public async Task Clone(T geometry, CancellationToken cancellationToken = public async Task CenterExtentAt(Extent extent, Point point, CancellationToken cancellationToken = default) { return await InvokeAsync(nameof(ProjectionEngine), nameof(CenterExtentAt), - cancellationToken, extent, point); + QueryResultsMaxSizeLimit, cancellationToken, extent, point); } /// @@ -1706,7 +1734,7 @@ public async Task CenterExtentAt(Extent extent, Point point, Cancellatio public async Task Expand(Extent extent, double factor, CancellationToken cancellationToken = default) { return await InvokeAsync(nameof(ProjectionEngine), nameof(Expand), - cancellationToken, extent, factor); + QueryResultsMaxSizeLimit, cancellationToken, extent, factor); } /// @@ -1725,7 +1753,7 @@ public async Task Expand(Extent extent, double factor, CancellationToken public async Task NormalizeExtent(Extent extent, CancellationToken cancellationToken = default) { return await InvokeAsync(nameof(ProjectionEngine), nameof(NormalizeExtent), - cancellationToken, extent); + QueryResultsMaxSizeLimit, cancellationToken, extent); } /// @@ -1750,7 +1778,7 @@ public async Task OffsetExtent(Extent extent, double dx, double dy, doub CancellationToken cancellationToken = default) { return await InvokeAsync(nameof(ProjectionEngine), nameof(OffsetExtent), - cancellationToken, extent, dx, dy, dz); + QueryResultsMaxSizeLimit, cancellationToken, extent, dx, dy, dz); } /// @@ -1767,7 +1795,7 @@ public async Task OffsetExtent(Extent extent, double dx, double dy, doub public async Task NormalizePoint(Point point, CancellationToken cancellationToken = default) { return await InvokeAsync(nameof(ProjectionEngine), nameof(NormalizePoint), - cancellationToken, point); + QueryResultsMaxSizeLimit, cancellationToken, point); } /// @@ -1788,7 +1816,7 @@ public async Task AddPath(Polyline polyline, MapPath points, CancellationToken cancellationToken = default) { return await InvokeAsync(nameof(ProjectionEngine), nameof(AddPath), - cancellationToken, polyline, points); + QueryResultsMaxSizeLimit, cancellationToken, polyline, points); } /// @@ -1839,7 +1867,7 @@ public async Task GetPoint(Polyline polyline, int pathIndex, int pointInd CancellationToken cancellationToken = default) { return await InvokeAsync(nameof(GeometryEngine), nameof(GetPoint), - cancellationToken, polyline, pathIndex, pointIndex); + QueryResultsMaxSizeLimit, cancellationToken, polyline, pathIndex, pointIndex); } /// @@ -1866,7 +1894,7 @@ public async Task InsertPoint(Polyline polyline, int pathIndex, int po CancellationToken cancellationToken = default) { return await InvokeAsync(nameof(ProjectionEngine), nameof(InsertPoint), - cancellationToken, polyline, pathIndex, pointIndex, point); + QueryResultsMaxSizeLimit, cancellationToken, polyline, pathIndex, pointIndex, point); } /// @@ -1888,7 +1916,7 @@ public async Task InsertPoint(Polyline polyline, int pathIndex, int po { var result = await InvokeAsync(nameof(ProjectionEngine), nameof(RemovePath), - cancellationToken, polyline, index); + QueryResultsMaxSizeLimit, cancellationToken, polyline, index); if (result.Length < 3) // need at least two points for the path { @@ -1920,7 +1948,7 @@ await InvokeAsync(nameof(ProjectionEngine), nameof(RemovePath), { var result = await InvokeAsync(nameof(ProjectionEngine), nameof(RemovePoint), - cancellationToken, polyline, pathIndex, pointIndex); + QueryResultsMaxSizeLimit, cancellationToken, polyline, pathIndex, pointIndex); if (result.Length < 2) { @@ -1955,7 +1983,7 @@ public async Task SetPoint(Polyline polyline, int pathIndex, int point CancellationToken cancellationToken = default) { return await InvokeAsync(nameof(GeometryEngine), nameof(SetPoint), - cancellationToken, polyline, pathIndex, pointIndex, point); + QueryResultsMaxSizeLimit, cancellationToken, polyline, pathIndex, pointIndex, point); } /// @@ -1976,7 +2004,7 @@ public async Task SetPoint(Polyline polyline, int pathIndex, int point public async Task AddRing(Polygon polygon, MapPath points, CancellationToken cancellationToken = default) { return await InvokeAsync(nameof(GeometryEngine), nameof(AddRing), - cancellationToken, polygon, points); + QueryResultsMaxSizeLimit, cancellationToken, polygon, points); } /// @@ -2022,7 +2050,7 @@ public async Task AddRing(Polygon polygon, Point[] points, Cancellation public async Task PolygonFromExtent(Extent extent, CancellationToken cancellationToken = default) { return await InvokeAsync(nameof(GeometryEngine), nameof(PolygonFromExtent), - cancellationToken, extent); + QueryResultsMaxSizeLimit, cancellationToken, extent); } /// @@ -2046,7 +2074,7 @@ public async Task GetPoint(Polygon polygon, int ringIndex, int pointIndex CancellationToken cancellationToken = default) { return await InvokeAsync(nameof(GeometryEngine), nameof(GetPoint), - cancellationToken, polygon, ringIndex, pointIndex); + QueryResultsMaxSizeLimit, cancellationToken, polygon, ringIndex, pointIndex); } /// @@ -2073,7 +2101,7 @@ public async Task InsertPoint(Polygon polygon, int ringIndex, int point CancellationToken cancellationToken = default) { return await InvokeAsync(nameof(GeometryEngine), nameof(InsertPoint), - cancellationToken, polygon, ringIndex, pointIndex, point); + QueryResultsMaxSizeLimit, cancellationToken, polygon, ringIndex, pointIndex, point); } /// @@ -2094,7 +2122,7 @@ public async Task InsertPoint(Polygon polygon, int ringIndex, int point public async Task IsClockwise(Polygon polygon, MapPath ring, CancellationToken cancellationToken = default) { return await InvokeAsync(nameof(GeometryEngine), nameof(IsClockwise), - cancellationToken, polygon, ring); + QueryResultsMaxSizeLimit, cancellationToken, polygon, ring); } /// @@ -2118,7 +2146,7 @@ public async Task IsClockwise(Polygon polygon, Point[] ring, CancellationT foreach (var p in ring) { - if (p.X is null && p.Longitude is null || p.Y is null && p.Latitude is null) + if ((p.X is null && p.Longitude is null) || (p.Y is null && p.Latitude is null)) { throw new ArgumentException("Points must have X and Y coordinates"); } @@ -2151,7 +2179,7 @@ public async Task IsClockwise(Polygon polygon, Point[] ring, CancellationT { var result = await InvokeAsync(nameof(GeometryEngine), nameof(RemovePoint), - cancellationToken, polygon, ringIndex, pointIndex); + QueryResultsMaxSizeLimit, cancellationToken, polygon, ringIndex, pointIndex); if (result.Length < 2) { @@ -2181,7 +2209,7 @@ await InvokeAsync(nameof(GeometryEngine), nameof(RemovePoint), { var result = await InvokeAsync(nameof(GeometryEngine), nameof(RemoveRing), - cancellationToken, polygon, index); + QueryResultsMaxSizeLimit, cancellationToken, polygon, index); if (result.Length < 3) // need at least two points for the ring { @@ -2215,7 +2243,7 @@ public async Task SetPoint(Polygon polygon, int ringIndex, int pointInd CancellationToken cancellationToken = default) { return await InvokeAsync(nameof(GeometryEngine), nameof(SetPoint), - cancellationToken, polygon, ringIndex, pointIndex, point); + QueryResultsMaxSizeLimit, cancellationToken, polygon, ringIndex, pointIndex, point); } /// @@ -2227,7 +2255,7 @@ public async Task SetPoint(Polygon polygon, int ringIndex, int pointInd public async Task GetExtentCenter(Extent extent, CancellationToken cancellationToken = default) { return await InvokeAsync(nameof(GeometryEngine), nameof(GetExtentCenter), - cancellationToken, extent); + QueryResultsMaxSizeLimit, cancellationToken, extent); } /// @@ -2239,7 +2267,7 @@ public async Task SetPoint(Polygon polygon, int ringIndex, int pointInd public async Task GetExtentHeight(Extent extent, CancellationToken cancellationToken = default) { return await InvokeAsync(nameof(ProjectionEngine), nameof(GetExtentHeight), - cancellationToken, extent); + QueryResultsMaxSizeLimit, cancellationToken, extent); } /// @@ -2251,6 +2279,6 @@ public async Task SetPoint(Polygon polygon, int ringIndex, int pointInd public async Task GetExtentWidth(Extent extent, CancellationToken cancellationToken = default) { return await InvokeAsync(nameof(ProjectionEngine), nameof(GetExtentWidth), - cancellationToken, extent); + QueryResultsMaxSizeLimit, cancellationToken, extent); } } \ No newline at end of file diff --git a/src/dymaptic.GeoBlazor.Core/Components/LocationService.cs b/src/dymaptic.GeoBlazor.Core/Model/LocationService.cs similarity index 99% rename from src/dymaptic.GeoBlazor.Core/Components/LocationService.cs rename to src/dymaptic.GeoBlazor.Core/Model/LocationService.cs index 1b01dcf00..538bd7c71 100644 --- a/src/dymaptic.GeoBlazor.Core/Components/LocationService.cs +++ b/src/dymaptic.GeoBlazor.Core/Model/LocationService.cs @@ -1,18 +1,20 @@ // ReSharper disable MethodOverloadWithOptionalParameter -namespace dymaptic.GeoBlazor.Core.Components; - +namespace dymaptic.GeoBlazor.Core.Model; + +/// +/// GeoBlazor Docs +/// A convenience module for importing locator functions when developing with +/// TypeScript. +/// ArcGIS Maps SDK for JavaScript +/// [CodeGenerationIgnore] -public partial class LocationService : LogicComponent +public class LocationService( + IAppValidator appValidator, + IJSRuntime jsRuntime, + JsModuleManager jsModuleManager, + AuthenticationManager authenticationManager) + : LogicComponent(appValidator, jsRuntime, jsModuleManager, authenticationManager) { - /// - /// Default Constructor - /// - public LocationService(IAppValidator appValidator, IJSRuntime jsRuntime, JsModuleManager jsModuleManager, - AuthenticationManager authenticationManager) - : base(appValidator, jsRuntime, jsModuleManager, authenticationManager) - { - } - /// protected override string ComponentName => nameof(LocationService); @@ -23,7 +25,8 @@ private async Task LocationToAddressImplementation(string url, CancellationToken cancellationToken = default) { return await InvokeAsync(nameof(LocationService), nameof(LocationToAddress), - cancellationToken, url, location, locationType, outSpatialReference, requestOptions); + QueryResultsMaxSizeLimit, cancellationToken, url, location, locationType, outSpatialReference, + requestOptions); } // Final implementation of all the permutations of AddressesToLocations @@ -34,7 +37,7 @@ private async Task> AddressesToLocationsImplementation(st string? addressSearchStringParameterName, CancellationToken cancellationToken = default) { return await InvokeAsync>(nameof(LocationService), - nameof(AddressesToLocations), cancellationToken, url, + nameof(AddressesToLocations), QueryResultsMaxSizeLimit, cancellationToken, url, addresses, countryCode, categories, locationType, outSpatialReference, requestOptions, addressSearchStringParameterName); } @@ -49,7 +52,7 @@ private async Task> AddressToLocationsImplementation(stri CancellationToken cancellationToken = default) { return await InvokeAsync>(nameof(LocationService), - nameof(AddressToLocations), cancellationToken, url, address, + nameof(AddressToLocations), QueryResultsMaxSizeLimit, cancellationToken, url, address, categories, countryCode, forStorage, location, locationType, magicKey, maxLocations, outFields, outSpatialReference, searchExtent, requestOptions, addressSearchStringParameterName); @@ -62,7 +65,7 @@ private async Task> SuggestLocationsImplementation(string CancellationToken cancellationToken) { return await InvokeAsync>(nameof(LocationService), - nameof(SuggestLocations), cancellationToken, url, location, text, + nameof(SuggestLocations), QueryResultsMaxSizeLimit, cancellationToken, url, location, text, categories, requestOptions); } diff --git a/src/dymaptic.GeoBlazor.Core/Model/LogicComponent.cs b/src/dymaptic.GeoBlazor.Core/Model/LogicComponent.cs index 4b58d9247..312f4e63d 100644 --- a/src/dymaptic.GeoBlazor.Core/Model/LogicComponent.cs +++ b/src/dymaptic.GeoBlazor.Core/Model/LogicComponent.cs @@ -12,6 +12,13 @@ public abstract class LogicComponent( JsModuleManager jsModuleManager, AuthenticationManager authenticationManager) { + /// + /// The maximum size of query results that will be returned in a stream. Note that setting this to a smaller value + /// might create errors in query returns. + /// + [Parameter] + public long QueryResultsMaxSizeLimit { get; set; } = 1_000_000_000L; + /// /// The name of the logic component. /// @@ -38,6 +45,11 @@ public abstract class LogicComponent( /// protected bool IsServer => jsRuntime.GetType().Name.Contains("Remote"); + /// + /// The AuthenticationManager instance. + /// + protected AuthenticationManager AuthenticationManager => authenticationManager; + /// /// A JavaScript invokable method that returns a JS Error and converts it to an Exception. /// @@ -103,6 +115,9 @@ protected internal virtual async Task InvokeVoidAsync(string className, [CallerM /// /// The name of the calling class. /// + /// + /// The maximum size of query results that will be returned in a stream. + /// /// /// The CancellationToken to cancel an asynchronous operation. /// @@ -110,11 +125,12 @@ protected internal virtual async Task InvokeVoidAsync(string className, [CallerM /// The collection of parameters to pass to the JS call. /// protected internal virtual async Task InvokeAsync(string className, [CallerMemberName] string method = "", - CancellationToken cancellationToken = default, params object?[] parameters) + long? maxAllowedSize = null, CancellationToken cancellationToken = default, params object?[] parameters) { await Initialize(cancellationToken); - return await Component!.InvokeJsMethod(IsServer, method, className, cancellationToken, parameters); + return await Component!.InvokeJsMethod(IsServer, method, className, maxAllowedSize, cancellationToken, + parameters); } private bool _validated; diff --git a/src/dymaptic.GeoBlazor.Core/Model/MapColor.cs b/src/dymaptic.GeoBlazor.Core/Model/MapColor.cs index 28ac417ab..3a2ef3350 100644 --- a/src/dymaptic.GeoBlazor.Core/Model/MapColor.cs +++ b/src/dymaptic.GeoBlazor.Core/Model/MapColor.cs @@ -9,7 +9,7 @@ namespace dymaptic.GeoBlazor.Core.Model; [JsonConverter(typeof(MapColorConverter))] [CodeGenerationIgnore] [ProtobufSerializable] -public class MapColor : IEquatable, IProtobufSerializable +public class MapColor : IEquatable, IEnumerable, IProtobufSerializable { /// /// Parameterless constructor for Protobuf deserialization. @@ -25,9 +25,9 @@ public MapColor() /// /// Requires 3 or 4 values, in the order R(0-255), G(0-255), B(0-255), A(0-1). A is optional. /// - public MapColor(params double[] values) + public MapColor(params IReadOnlyList values) { - RgbaValues = values; + RgbaValues = values.ToArray(); } /// @@ -57,20 +57,53 @@ public MapColor(string hexOrNameValue) return !Equals(left, right); } + /// + /// Implicitly converts a color hex or name value to a GeoBlazor instance. + /// + public static implicit operator MapColor(string hexOrNameValue) => new(hexOrNameValue); + + /// + /// Implicitly converts a GeoBlazor instance to a hex or name value. + /// + public static implicit operator string?(MapColor color) => color.HexOrNameValue ?? color.ToHex(); + + /// + /// Implicitly converts a numeric array to a GeoBlazor instance. + /// + public static implicit operator MapColor(double[] rgbaValues) => new(rgbaValues); + + /// + /// Implicitly converts a numeric array to a GeoBlazor instance. + /// + public static implicit operator MapColor(List rgbaValues) => new(rgbaValues); + /// /// The numeric values for calculating a color (rgb/rgba). /// public double[]? RgbaValues { - get => _rgbaValues; - set + get + { + if (_rgbaValues is null) + { + Color? color = ToSystemColor(); + + if (color is not null) + { + _rgbaValues = [color.Value.R, color.Value.G, color.Value.B, color.Value.A / 255.0]; + } + } + + return _rgbaValues; + } + private init { _rgbaValues = value; Color? color = ToSystemColor(); if (color is not null && (HexOrNameValue is null || HexOrNameValue.Length == 0)) { - HexOrNameValue = ToHex(); + _hexOrNameValue = ToHex(); } } } @@ -80,8 +113,13 @@ public double[]? RgbaValues /// public string? HexOrNameValue { - get => _hexOrNameValue; - set + get + { + _hexOrNameValue ??= ToHex(); + + return _hexOrNameValue; + } + private init { _hexOrNameValue = value; Color? color = ToSystemColor(); @@ -93,6 +131,14 @@ public string? HexOrNameValue } } + /// + /// Provides support for Collection Expressions. + /// + public IEnumerator GetEnumerator() + { + return _rgbaValues?.GetEnumerator() ?? Enumerable.Empty().GetEnumerator(); + } + /// public bool Equals(MapColor? other) { @@ -137,17 +183,6 @@ public MapColorSerializationRecord ToProtobuf() /// public static MapColor? BlendColors(MapColor start, MapColor end, double weight) { - if (start.RgbaValues?.Any() != true) - { - // reset triggers calculation of rgba values from hex or name - start.HexOrNameValue = start.HexOrNameValue; - } - - if (end.RgbaValues?.Any() != true) - { - end.HexOrNameValue = end.HexOrNameValue; - } - if (start.RgbaValues?.Any() == true && end.RgbaValues?.Any() == true) { double[] startValues = start.RgbaValues.ToArray(); @@ -187,6 +222,14 @@ public override int GetHashCode() return HashCode.Combine(RgbaValues, HexOrNameValue); } + /// + /// For internal use only, used to support Collection Expressions and implicit conversions from arrays/lists. + /// + public void Add(double val) + { + _rgbaValues = _rgbaValues is null ? [val] : [.._rgbaValues, val]; + } + /// /// Clones the color object. /// diff --git a/src/dymaptic.GeoBlazor.Core/Model/PortalFeaturedGroups.cs b/src/dymaptic.GeoBlazor.Core/Model/PortalFeaturedGroups.cs new file mode 100644 index 000000000..2ea95db9d --- /dev/null +++ b/src/dymaptic.GeoBlazor.Core/Model/PortalFeaturedGroups.cs @@ -0,0 +1,16 @@ +namespace dymaptic.GeoBlazor.Core.Model; + +/// +/// GeoBlazor Docs +/// The featured groups for the portal. +/// ArcGIS Maps SDK for JavaScript +/// +/// +/// Name of the group owner. +/// ArcGIS Maps SDK for JavaScript +/// +/// +/// Group title. +/// ArcGIS Maps SDK for JavaScript +/// +public record PortalFeaturedGroups(string Owner, string Title); \ No newline at end of file diff --git a/src/dymaptic.GeoBlazor.Core/Model/ProjectionEngine.cs b/src/dymaptic.GeoBlazor.Core/Model/ProjectionEngine.cs index 740333191..49cb52126 100644 --- a/src/dymaptic.GeoBlazor.Core/Model/ProjectionEngine.cs +++ b/src/dymaptic.GeoBlazor.Core/Model/ProjectionEngine.cs @@ -5,17 +5,13 @@ /// ArcGIS Maps SDK for JavaScript /// [CodeGenerationIgnore] -public class ProjectionEngine : LogicComponent +public class ProjectionEngine( + IAppValidator appValidator, + IJSRuntime jsRuntime, + JsModuleManager jsModuleManager, + AuthenticationManager authenticationManager) + : LogicComponent(appValidator, jsRuntime, jsModuleManager, authenticationManager) { - /// - /// Default Constructor - /// - public ProjectionEngine(IAppValidator appValidator, IJSRuntime jsRuntime, JsModuleManager jsModuleManager, - AuthenticationManager authenticationManager) - : base(appValidator, jsRuntime, jsModuleManager, authenticationManager) - { - } - /// protected override string ComponentName => nameof(ProjectionEngine); @@ -64,7 +60,7 @@ public ProjectionEngine(IAppValidator appValidator, IJSRuntime jsRuntime, JsModu GeographicTransformation? geographicTransformation, CancellationToken cancellationToken = default) { return await InvokeAsync(nameof(ProjectionEngine), nameof(Project), - cancellationToken, geometries, spatialReference, geographicTransformation); + QueryResultsMaxSizeLimit, cancellationToken, geometries, spatialReference, geographicTransformation); } /// @@ -113,7 +109,7 @@ public ProjectionEngine(IAppValidator appValidator, IJSRuntime jsRuntime, JsModu GeographicTransformation? geographicTransformation, CancellationToken cancellationToken = default) { return await InvokeAsync(nameof(ProjectionEngine), nameof(Project), - cancellationToken, geometry, spatialReference, geographicTransformation); + QueryResultsMaxSizeLimit, cancellationToken, geometry, spatialReference, geographicTransformation); } /// @@ -137,7 +133,7 @@ public ProjectionEngine(IAppValidator appValidator, IJSRuntime jsRuntime, JsModu SpatialReference outSpatialReference, Extent extent, CancellationToken cancellationToken = default) { return await InvokeAsync(nameof(ProjectionEngine), nameof(GetTransformation), - cancellationToken, inSpatialReference, + QueryResultsMaxSizeLimit, cancellationToken, inSpatialReference, outSpatialReference, extent); } @@ -162,6 +158,7 @@ public ProjectionEngine(IAppValidator appValidator, IJSRuntime jsRuntime, JsModu SpatialReference outSpatialReference, Extent extent, CancellationToken cancellationToken = default) { return await InvokeAsync(nameof(ProjectionEngine), - nameof(GetTransformations), cancellationToken, inSpatialReference, outSpatialReference, extent); + nameof(GetTransformations), QueryResultsMaxSizeLimit, cancellationToken, inSpatialReference, + outSpatialReference, extent); } } \ No newline at end of file diff --git a/src/dymaptic.GeoBlazor.Core/Scripts/arcGisJsInterop.ts b/src/dymaptic.GeoBlazor.Core/Scripts/arcGisJsInterop.ts index bb16b6404..b3dd51765 100644 --- a/src/dymaptic.GeoBlazor.Core/Scripts/arcGisJsInterop.ts +++ b/src/dymaptic.GeoBlazor.Core/Scripts/arcGisJsInterop.ts @@ -1230,7 +1230,7 @@ export function setGraphicSymbol(id: string, symbol: any, layerId: string | null export function getGraphicSymbol(id: string, layerId: string | null, viewId: string | null): any { const graphic = lookupJsGraphicById(id, layerId, viewId); if (hasValue(graphic?.symbol)) { - return buildDotNetSymbol(graphic!.symbol!, viewId); + return buildDotNetSymbol(graphic!.symbol!); } return null; diff --git a/src/dymaptic.GeoBlazor.Core/Scripts/cSVLayer.gb.ts b/src/dymaptic.GeoBlazor.Core/Scripts/cSVLayer.gb.ts index 773015fac..0a0b5d9b7 100644 --- a/src/dymaptic.GeoBlazor.Core/Scripts/cSVLayer.gb.ts +++ b/src/dymaptic.GeoBlazor.Core/Scripts/cSVLayer.gb.ts @@ -465,7 +465,7 @@ export default class CSVLayerGenerated extends BaseComponent { } let { buildDotNetLabel } = await import('./label'); - return await Promise.all(this.layer.labelingInfo!.map(async i => await buildDotNetLabel(i, this.layerId, this.viewId))); + return await Promise.all(this.layer.labelingInfo!.map(async i => await buildDotNetLabel(i))); } async setLabelingInfo(value: any): Promise { @@ -953,7 +953,7 @@ export async function buildDotNetCSVLayerGenerated(jsObject: any, layerId: strin if (hasValue(jsObject.labelingInfo)) { let { buildDotNetLabel } = await import('./label'); - dotNetCSVLayer.labelingInfo = await Promise.all(jsObject.labelingInfo.map(async i => await buildDotNetLabel(i, layerId, viewId))); + dotNetCSVLayer.labelingInfo = await Promise.all(jsObject.labelingInfo.map(async i => await buildDotNetLabel(i))); } if (hasValue(jsObject.orderBy)) { diff --git a/src/dymaptic.GeoBlazor.Core/Scripts/featureLayer.gb.ts b/src/dymaptic.GeoBlazor.Core/Scripts/featureLayer.gb.ts index e450b56f9..91c5a27ba 100644 --- a/src/dymaptic.GeoBlazor.Core/Scripts/featureLayer.gb.ts +++ b/src/dymaptic.GeoBlazor.Core/Scripts/featureLayer.gb.ts @@ -551,7 +551,7 @@ export default class FeatureLayerGenerated extends BaseComponent { } let { buildDotNetLabel } = await import('./label'); - return await Promise.all(this.layer.labelingInfo!.map(async i => await buildDotNetLabel(i, this.layerId, this.viewId))); + return await Promise.all(this.layer.labelingInfo!.map(async i => await buildDotNetLabel(i))); } async setLabelingInfo(value: any): Promise { @@ -1188,7 +1188,7 @@ export async function buildDotNetFeatureLayerGenerated(jsObject: any, layerId: s if (hasValue(jsObject.labelingInfo)) { let { buildDotNetLabel } = await import('./label'); - dotNetFeatureLayer.labelingInfo = await Promise.all(jsObject.labelingInfo.map(async i => await buildDotNetLabel(i, layerId, viewId))); + dotNetFeatureLayer.labelingInfo = await Promise.all(jsObject.labelingInfo.map(async i => await buildDotNetLabel(i))); } if (hasValue(jsObject.orderBy)) { diff --git a/src/dymaptic.GeoBlazor.Core/Scripts/geoJSONLayer.gb.ts b/src/dymaptic.GeoBlazor.Core/Scripts/geoJSONLayer.gb.ts index 1e9b5f060..d67650230 100644 --- a/src/dymaptic.GeoBlazor.Core/Scripts/geoJSONLayer.gb.ts +++ b/src/dymaptic.GeoBlazor.Core/Scripts/geoJSONLayer.gb.ts @@ -455,7 +455,7 @@ export default class GeoJSONLayerGenerated extends BaseComponent { } let { buildDotNetLabel } = await import('./label'); - return await Promise.all(this.layer.labelingInfo!.map(async i => await buildDotNetLabel(i, this.layerId, this.viewId))); + return await Promise.all(this.layer.labelingInfo!.map(async i => await buildDotNetLabel(i))); } async setLabelingInfo(value: any): Promise { @@ -927,7 +927,7 @@ export async function buildDotNetGeoJSONLayerGenerated(jsObject: any, layerId: s if (hasValue(jsObject.labelingInfo)) { let { buildDotNetLabel } = await import('./label'); - dotNetGeoJSONLayer.labelingInfo = await Promise.all(jsObject.labelingInfo.map(async i => await buildDotNetLabel(i, layerId, viewId))); + dotNetGeoJSONLayer.labelingInfo = await Promise.all(jsObject.labelingInfo.map(async i => await buildDotNetLabel(i))); } if (hasValue(jsObject.orderBy)) { diff --git a/src/dymaptic.GeoBlazor.Core/Scripts/graphic.ts b/src/dymaptic.GeoBlazor.Core/Scripts/graphic.ts index 49426e1eb..c263b32ec 100644 --- a/src/dymaptic.GeoBlazor.Core/Scripts/graphic.ts +++ b/src/dymaptic.GeoBlazor.Core/Scripts/graphic.ts @@ -110,7 +110,7 @@ export function buildDotNetGraphic(graphic: Graphic, layerId: string | null, vie dotNetGraphic.viewId = viewId; if (hasValue(graphic.symbol)) { - dotNetGraphic.symbol = buildDotNetSymbol(graphic.symbol, viewId); + dotNetGraphic.symbol = buildDotNetSymbol(graphic.symbol); } if (hasValue(graphic.geometry)) { diff --git a/src/dymaptic.GeoBlazor.Core/Scripts/label.gb.ts b/src/dymaptic.GeoBlazor.Core/Scripts/label.gb.ts deleted file mode 100644 index 6c654686e..000000000 --- a/src/dymaptic.GeoBlazor.Core/Scripts/label.gb.ts +++ /dev/null @@ -1,256 +0,0 @@ -// File auto-generated by dymaptic tooling. Any changes made here will be lost on future generation. To override functionality, use the relevant root .ts file. -import LabelClass from '@arcgis/core/layers/support/LabelClass'; -import { arcGisObjectRefs, jsObjectRefs, dotNetRefs, hasValue, lookupGeoBlazorId, sanitize, removeCircularReferences, generateSerializableJson } from './geoBlazorCore'; -import BaseComponent from "./baseComponent"; - -export default class LabelGenerated extends BaseComponent { - public component: LabelClass; - public geoBlazorId: string | null = null; - public viewId: string | null = null; - public layerId: string | null = null; - - constructor(component: LabelClass) { - super(component); - this.component = component; - } - - - async updateComponent(dotNetObject: any): Promise { - if (hasValue(dotNetObject.symbol)) { - let { buildJsSymbol } = await import('./symbol'); - this.component.symbol = buildJsSymbol(dotNetObject.symbol, this.layerId, this.viewId) as any; - } - - if (hasValue(dotNetObject.allowOverrun)) { - this.component.allowOverrun = dotNetObject.allowOverrun; - } - if (hasValue(dotNetObject.deconflictionStrategy)) { - this.component.deconflictionStrategy = dotNetObject.deconflictionStrategy; - } - if (hasValue(dotNetObject.labelExpression)) { - this.component.labelExpression = dotNetObject.labelExpression; - } - if (hasValue(dotNetObject.labelExpressionInfo)) { - this.component.labelExpressionInfo = sanitize(dotNetObject.labelExpressionInfo); - } - if (hasValue(dotNetObject.labelPlacement)) { - this.component.labelPlacement = dotNetObject.labelPlacement; - } - if (hasValue(dotNetObject.labelPosition)) { - this.component.labelPosition = dotNetObject.labelPosition; - } - if (hasValue(dotNetObject.maxScale)) { - this.component.maxScale = dotNetObject.maxScale; - } - if (hasValue(dotNetObject.minScale)) { - this.component.minScale = dotNetObject.minScale; - } - if (hasValue(dotNetObject.repeatLabel)) { - this.component.repeatLabel = dotNetObject.repeatLabel; - } - if (hasValue(dotNetObject.repeatLabelDistance)) { - this.component.repeatLabelDistance = dotNetObject.repeatLabelDistance; - } - if (hasValue(dotNetObject.useCodedValues)) { - this.component.useCodedValues = dotNetObject.useCodedValues; - } - if (hasValue(dotNetObject.where)) { - this.component.where = dotNetObject.where; - } - } - - // region properties - - getLabelExpression(): any { - if (!hasValue(this.component.labelExpression)) { - return null; - } - - return generateSerializableJson(this.component.labelExpression); - } - - setLabelExpression(value: any): void { - this.component.labelExpression = JSON.parse(value); - } - - async getSymbol(): Promise { - if (!hasValue(this.component.symbol)) { - return null; - } - - let { buildDotNetSymbol } = await import('./symbol'); - return buildDotNetSymbol(this.component.symbol, this.viewId); - } - - async setSymbol(value: any): Promise { - let { buildJsSymbol } = await import('./symbol'); - this.component.symbol = buildJsSymbol(value, this.layerId, this.viewId); - } - - getWhere(): any { - if (!hasValue(this.component.where)) { - return null; - } - - return generateSerializableJson(this.component.where); - } - - setWhere(value: any): void { - this.component.where = JSON.parse(value); - } - -} - - -export async function buildJsLabelGenerated(dotNetObject: any, layerId: string | null, viewId: string | null): Promise { - if (!hasValue(dotNetObject)) { - return null; - } - - let properties: any = {}; - if (hasValue(dotNetObject.symbol)) { - let { buildJsSymbol } = await import('./symbol'); - properties.symbol = buildJsSymbol(dotNetObject.symbol, layerId, viewId) as any; - } - - if (hasValue(dotNetObject.allowOverrun)) { - properties.allowOverrun = dotNetObject.allowOverrun; - } - if (hasValue(dotNetObject.deconflictionStrategy)) { - properties.deconflictionStrategy = dotNetObject.deconflictionStrategy; - } - if (hasValue(dotNetObject.labelExpression)) { - properties.labelExpression = dotNetObject.labelExpression; - } - if (hasValue(dotNetObject.labelExpressionInfo)) { - properties.labelExpressionInfo = sanitize(dotNetObject.labelExpressionInfo); - } - if (hasValue(dotNetObject.labelPlacement)) { - properties.labelPlacement = dotNetObject.labelPlacement; - } - if (hasValue(dotNetObject.labelPosition)) { - properties.labelPosition = dotNetObject.labelPosition; - } - if (hasValue(dotNetObject.maxScale)) { - properties.maxScale = dotNetObject.maxScale; - } - if (hasValue(dotNetObject.minScale)) { - properties.minScale = dotNetObject.minScale; - } - if (hasValue(dotNetObject.repeatLabel)) { - properties.repeatLabel = dotNetObject.repeatLabel; - } - if (hasValue(dotNetObject.repeatLabelDistance)) { - properties.repeatLabelDistance = dotNetObject.repeatLabelDistance; - } - if (hasValue(dotNetObject.useCodedValues)) { - properties.useCodedValues = dotNetObject.useCodedValues; - } - if (hasValue(dotNetObject.where)) { - properties.where = dotNetObject.where; - } - let jsLabelClass = new LabelClass(properties); - - let { default: LabelWrapper } = await import('./label'); - - let labelWrapper = new LabelWrapper(jsLabelClass); - labelWrapper.geoBlazorId = dotNetObject.id; - labelWrapper.viewId = viewId; - labelWrapper.layerId = layerId; - - jsObjectRefs[dotNetObject.id] = labelWrapper; - arcGisObjectRefs[dotNetObject.id] = jsLabelClass; - - return jsLabelClass; -} - - -export async function buildDotNetLabelGenerated(jsObject: any, layerId: string | null, viewId: string | null): Promise { - if (!hasValue(jsObject)) { - return null; - } - - let dotNetLabel: any = {}; - - if (hasValue(jsObject.symbol)) { - let { buildDotNetSymbol } = await import('./symbol'); - dotNetLabel.symbol = buildDotNetSymbol(jsObject.symbol, viewId); - } - - if (hasValue(jsObject.allowOverrun)) { - dotNetLabel.allowOverrun = jsObject.allowOverrun; - } - - if (hasValue(jsObject.deconflictionStrategy)) { - dotNetLabel.deconflictionStrategy = removeCircularReferences(jsObject.deconflictionStrategy); - } - - if (hasValue(jsObject.labelExpression)) { - dotNetLabel.labelExpression = jsObject.labelExpression; - } - - if (hasValue(jsObject.labelExpressionInfo)) { - dotNetLabel.labelExpressionInfo = removeCircularReferences(jsObject.labelExpressionInfo); - } - - if (hasValue(jsObject.labelPlacement)) { - dotNetLabel.labelPlacement = removeCircularReferences(jsObject.labelPlacement); - } - - if (hasValue(jsObject.labelPosition)) { - dotNetLabel.labelPosition = removeCircularReferences(jsObject.labelPosition); - } - - if (hasValue(jsObject.maxScale)) { - dotNetLabel.maxScale = jsObject.maxScale; - } - - if (hasValue(jsObject.minScale)) { - dotNetLabel.minScale = jsObject.minScale; - } - - if (hasValue(jsObject.repeatLabel)) { - dotNetLabel.repeatLabel = jsObject.repeatLabel; - } - - if (hasValue(jsObject.repeatLabelDistance)) { - dotNetLabel.repeatLabelDistance = removeCircularReferences(jsObject.repeatLabelDistance); - } - - if (hasValue(jsObject.useCodedValues)) { - dotNetLabel.useCodedValues = jsObject.useCodedValues; - } - - if (hasValue(jsObject.where)) { - dotNetLabel.where = jsObject.where; - } - - - let geoBlazorId = lookupGeoBlazorId(jsObject); - if (hasValue(geoBlazorId)) { - dotNetLabel.id = geoBlazorId; - } else if (hasValue(viewId)) { - let dotNetRef = dotNetRefs[viewId!]; - if (hasValue(dotNetRef)) { - try { - dotNetLabel.id = await dotNetRef.invokeMethodAsync('GetId'); - } catch (e) { - console.error('Error invoking GetId for Label', e); - } - } - } - - if (hasValue(dotNetLabel.id)) { - if (!jsObjectRefs.hasOwnProperty(dotNetLabel.id)) { - let {default: LabelWrapper} = await import('./label'); - let labelWrapper = new LabelWrapper(jsObject); - labelWrapper.geoBlazorId = dotNetLabel.id; - labelWrapper.viewId = viewId; - labelWrapper.layerId = layerId; - jsObjectRefs[dotNetLabel.id] = labelWrapper; - } - arcGisObjectRefs[dotNetLabel.id] ??= jsObject; - } - return dotNetLabel; -} - diff --git a/src/dymaptic.GeoBlazor.Core/Scripts/label.ts b/src/dymaptic.GeoBlazor.Core/Scripts/label.ts index f1499ce40..5807307f6 100644 --- a/src/dymaptic.GeoBlazor.Core/Scripts/label.ts +++ b/src/dymaptic.GeoBlazor.Core/Scripts/label.ts @@ -1,20 +1,137 @@ -import LabelGenerated from './label.gb'; -import LabelClass from '@arcgis/core/layers/support/LabelClass'; +import { + arcGisObjectRefs, + hasValue, + jsObjectRefs, + lookupGeoBlazorId, + removeCircularReferences, + sanitize +} from "./geoBlazorCore"; +import LabelClass from "@arcgis/core/layers/support/LabelClass"; + export async function buildJsLabel(dotNetObject: any, layerId: string | null, viewId: string | null): Promise { - let {buildJsLabelGenerated} = await import('./label.gb'); - return await buildJsLabelGenerated(dotNetObject, layerId, viewId); -} + if (!hasValue(dotNetObject)) { + return null; + } + + let properties: any = {}; + if (hasValue(dotNetObject.symbol)) { + let {buildJsSymbol} = await import('./symbol'); + properties.symbol = buildJsSymbol(dotNetObject.symbol, layerId, viewId) as any; + } + + if (hasValue(dotNetObject.allowOverrun)) { + properties.allowOverrun = dotNetObject.allowOverrun; + } + if (hasValue(dotNetObject.deconflictionStrategy)) { + properties.deconflictionStrategy = dotNetObject.deconflictionStrategy; + } + if (hasValue(dotNetObject.labelExpression)) { + properties.labelExpression = dotNetObject.labelExpression; + } + if (hasValue(dotNetObject.labelExpressionInfo)) { + properties.labelExpressionInfo = sanitize(dotNetObject.labelExpressionInfo); + } + if (hasValue(dotNetObject.labelPlacement)) { + properties.labelPlacement = dotNetObject.labelPlacement; + } + if (hasValue(dotNetObject.labelPosition)) { + properties.labelPosition = dotNetObject.labelPosition; + } + if (hasValue(dotNetObject.maxScale)) { + properties.maxScale = dotNetObject.maxScale; + } + if (hasValue(dotNetObject.minScale)) { + properties.minScale = dotNetObject.minScale; + } + if (hasValue(dotNetObject.repeatLabel)) { + properties.repeatLabel = dotNetObject.repeatLabel; + } + if (hasValue(dotNetObject.repeatLabelDistance)) { + properties.repeatLabelDistance = dotNetObject.repeatLabelDistance; + } + if (hasValue(dotNetObject.useCodedValues)) { + properties.useCodedValues = dotNetObject.useCodedValues; + } + if (hasValue(dotNetObject.where)) { + properties.where = dotNetObject.where; + } + let jsLabelClass = new LabelClass(properties); + + jsObjectRefs[dotNetObject.id] = jsLabelClass; + arcGisObjectRefs[dotNetObject.id] = jsLabelClass; -export async function buildDotNetLabel(jsObject: any, layerId: string | null, viewId: string | null): Promise { - let {buildDotNetLabelGenerated} = await import('./label.gb'); - return await buildDotNetLabelGenerated(jsObject, layerId, viewId); + return jsLabelClass; } -export default class LabelWrapper extends LabelGenerated { +export async function buildDotNetLabel(jsObject: any): Promise { + if (!hasValue(jsObject)) { + return null; + } - constructor(component: LabelClass) { - super(component); + let dotNetLabel: any = {}; + + if (hasValue(jsObject.symbol)) { + let {buildDotNetSymbol} = await import('./symbol'); + dotNetLabel.symbol = buildDotNetSymbol(jsObject.symbol); + } + + if (hasValue(jsObject.allowOverrun)) { + dotNetLabel.allowOverrun = jsObject.allowOverrun; + } + + if (hasValue(jsObject.deconflictionStrategy)) { + dotNetLabel.deconflictionStrategy = removeCircularReferences(jsObject.deconflictionStrategy); } - -} + if (hasValue(jsObject.labelExpression)) { + dotNetLabel.labelExpression = jsObject.labelExpression; + } + + if (hasValue(jsObject.labelExpressionInfo)) { + dotNetLabel.labelExpressionInfo = removeCircularReferences(jsObject.labelExpressionInfo); + } + + if (hasValue(jsObject.labelPlacement)) { + dotNetLabel.labelPlacement = removeCircularReferences(jsObject.labelPlacement); + } + + if (hasValue(jsObject.labelPosition)) { + dotNetLabel.labelPosition = removeCircularReferences(jsObject.labelPosition); + } + + if (hasValue(jsObject.maxScale)) { + dotNetLabel.maxScale = jsObject.maxScale; + } + + if (hasValue(jsObject.minScale)) { + dotNetLabel.minScale = jsObject.minScale; + } + + if (hasValue(jsObject.repeatLabel)) { + dotNetLabel.repeatLabel = jsObject.repeatLabel; + } + + if (hasValue(jsObject.repeatLabelDistance)) { + dotNetLabel.repeatLabelDistance = removeCircularReferences(jsObject.repeatLabelDistance); + } + + if (hasValue(jsObject.useCodedValues)) { + dotNetLabel.useCodedValues = jsObject.useCodedValues; + } + + if (hasValue(jsObject.where)) { + dotNetLabel.where = jsObject.where; + } + + let geoBlazorId = lookupGeoBlazorId(jsObject); + if (hasValue(geoBlazorId)) { + dotNetLabel.id = geoBlazorId; + } + + if (hasValue(dotNetLabel.id)) { + jsObjectRefs[dotNetLabel.id] ??= jsObject; + arcGisObjectRefs[dotNetLabel.id] ??= jsObject; + } + + return dotNetLabel; +} diff --git a/src/dymaptic.GeoBlazor.Core/Scripts/layerSearchSource.gb.ts b/src/dymaptic.GeoBlazor.Core/Scripts/layerSearchSource.gb.ts index e9af6bed2..a9d83a619 100644 --- a/src/dymaptic.GeoBlazor.Core/Scripts/layerSearchSource.gb.ts +++ b/src/dymaptic.GeoBlazor.Core/Scripts/layerSearchSource.gb.ts @@ -21,7 +21,7 @@ export async function buildDotNetLayerSearchSourceGenerated(jsObject: any, viewI if (hasValue(jsObject.resultSymbol)) { let { buildDotNetSymbol } = await import('./symbol'); - dotNetLayerSearchSource.resultSymbol = buildDotNetSymbol(jsObject.resultSymbol, viewId); + dotNetLayerSearchSource.resultSymbol = buildDotNetSymbol(jsObject.resultSymbol); } if (hasValue(jsObject.autoNavigate)) { diff --git a/src/dymaptic.GeoBlazor.Core/Scripts/locatorSearchSource.gb.ts b/src/dymaptic.GeoBlazor.Core/Scripts/locatorSearchSource.gb.ts index 65d0eed14..fe4b61f2c 100644 --- a/src/dymaptic.GeoBlazor.Core/Scripts/locatorSearchSource.gb.ts +++ b/src/dymaptic.GeoBlazor.Core/Scripts/locatorSearchSource.gb.ts @@ -118,7 +118,7 @@ export async function buildDotNetLocatorSearchSourceGenerated(jsObject: any, vie if (hasValue(jsObject.resultSymbol)) { let { buildDotNetSymbol } = await import('./symbol'); - dotNetLocatorSearchSource.resultSymbol = buildDotNetSymbol(jsObject.resultSymbol, viewId); + dotNetLocatorSearchSource.resultSymbol = buildDotNetSymbol(jsObject.resultSymbol); } if (hasValue(jsObject.apiKey)) { diff --git a/src/dymaptic.GeoBlazor.Core/Scripts/portal.gb.ts b/src/dymaptic.GeoBlazor.Core/Scripts/portal.gb.ts index 1c1bc2360..a05808500 100644 --- a/src/dymaptic.GeoBlazor.Core/Scripts/portal.gb.ts +++ b/src/dymaptic.GeoBlazor.Core/Scripts/portal.gb.ts @@ -543,7 +543,7 @@ export default class PortalGenerated extends BaseComponent { } let { buildDotNetPortalFeaturedGroups } = await import('./portalFeaturedGroups'); - return await Promise.all(this.component.featuredGroups!.map(async i => await buildDotNetPortalFeaturedGroups(i, this.viewId))); + return await Promise.all(this.component.featuredGroups!.map(async i => await buildDotNetPortalFeaturedGroups(i))); } async setFeaturedGroups(value: any): Promise { @@ -692,7 +692,7 @@ export default class PortalGenerated extends BaseComponent { } let { buildDotNetPortalProperties } = await import('./portalProperties'); - return await buildDotNetPortalProperties(this.component.portalProperties, this.viewId); + return await buildDotNetPortalProperties(this.component.portalProperties); } async setPortalProperties(value: any): Promise { @@ -1074,12 +1074,12 @@ export async function buildDotNetPortalGenerated(jsObject: any, layerId: string if (hasValue(jsObject.featuredGroups)) { let { buildDotNetPortalFeaturedGroups } = await import('./portalFeaturedGroups'); - dotNetPortal.featuredGroups = await Promise.all(jsObject.featuredGroups.map(async i => await buildDotNetPortalFeaturedGroups(i, viewId))); + dotNetPortal.featuredGroups = await Promise.all(jsObject.featuredGroups.map(async i => await buildDotNetPortalFeaturedGroups(i))); } if (hasValue(jsObject.portalProperties)) { let { buildDotNetPortalProperties } = await import('./portalProperties'); - dotNetPortal.portalProperties = await buildDotNetPortalProperties(jsObject.portalProperties, viewId); + dotNetPortal.portalProperties = await buildDotNetPortalProperties(jsObject.portalProperties); } if (hasValue(jsObject.access)) { diff --git a/src/dymaptic.GeoBlazor.Core/Scripts/portal.ts b/src/dymaptic.GeoBlazor.Core/Scripts/portal.ts index 893e76b44..f3d425ccc 100644 --- a/src/dymaptic.GeoBlazor.Core/Scripts/portal.ts +++ b/src/dymaptic.GeoBlazor.Core/Scripts/portal.ts @@ -1,4 +1,3 @@ -import PortalGenerated from './portal.gb'; // override generated code in this file import PortalGenerated, {buildDotNetPortalGenerated, buildJsPortalGenerated} from './portal.gb'; import Portal from '@arcgis/core/portal/Portal'; diff --git a/src/dymaptic.GeoBlazor.Core/Scripts/portalFeaturedGroups.gb.ts b/src/dymaptic.GeoBlazor.Core/Scripts/portalFeaturedGroups.gb.ts index 5e4710d3e..88500befd 100644 --- a/src/dymaptic.GeoBlazor.Core/Scripts/portalFeaturedGroups.gb.ts +++ b/src/dymaptic.GeoBlazor.Core/Scripts/portalFeaturedGroups.gb.ts @@ -22,7 +22,7 @@ export function buildJsPortalFeaturedGroupsGenerated(dotNetObject: any): any { } -export async function buildDotNetPortalFeaturedGroupsGenerated(jsObject: any, viewId: string | null): Promise { +export async function buildDotNetPortalFeaturedGroupsGenerated(jsObject: any): Promise { if (!hasValue(jsObject)) { return null; } @@ -36,26 +36,6 @@ export async function buildDotNetPortalFeaturedGroupsGenerated(jsObject: any, vi if (hasValue(jsObject.title)) { dotNetPortalFeaturedGroups.title = jsObject.title; } - - - let geoBlazorId = lookupGeoBlazorId(jsObject); - if (hasValue(geoBlazorId)) { - dotNetPortalFeaturedGroups.id = geoBlazorId; - } else if (hasValue(viewId)) { - let dotNetRef = dotNetRefs[viewId!]; - if (hasValue(dotNetRef)) { - try { - dotNetPortalFeaturedGroups.id = await dotNetRef.invokeMethodAsync('GetId'); - } catch (e) { - console.error('Error invoking GetId for PortalFeaturedGroups', e); - } - } - } - - if (hasValue(dotNetPortalFeaturedGroups.id)) { - jsObjectRefs[dotNetPortalFeaturedGroups.id] ??= jsObject; - arcGisObjectRefs[dotNetPortalFeaturedGroups.id] ??= jsObject; - } return dotNetPortalFeaturedGroups; } diff --git a/src/dymaptic.GeoBlazor.Core/Scripts/portalFeaturedGroups.ts b/src/dymaptic.GeoBlazor.Core/Scripts/portalFeaturedGroups.ts index 76aefed20..835c85f70 100644 --- a/src/dymaptic.GeoBlazor.Core/Scripts/portalFeaturedGroups.ts +++ b/src/dymaptic.GeoBlazor.Core/Scripts/portalFeaturedGroups.ts @@ -5,8 +5,8 @@ import { export function buildJsPortalFeaturedGroups(dotNetObject: any): Promise { return buildJsPortalFeaturedGroupsGenerated(dotNetObject); -} +} -export async function buildDotNetPortalFeaturedGroups(jsObject: any, viewId: string | null): Promise { - return await buildDotNetPortalFeaturedGroupsGenerated(jsObject, viewId); +export async function buildDotNetPortalFeaturedGroups(jsObject: any): Promise { + return await buildDotNetPortalFeaturedGroupsGenerated(jsObject); } diff --git a/src/dymaptic.GeoBlazor.Core/Scripts/portalProperties.gb.ts b/src/dymaptic.GeoBlazor.Core/Scripts/portalProperties.gb.ts index 5344ba9fc..c420b5584 100644 --- a/src/dymaptic.GeoBlazor.Core/Scripts/portalProperties.gb.ts +++ b/src/dymaptic.GeoBlazor.Core/Scripts/portalProperties.gb.ts @@ -217,7 +217,7 @@ export function buildJsPortalPropertiesGenerated(dotNetObject: any): any { } -export async function buildDotNetPortalPropertiesGenerated(jsObject: any, viewId: string | null): Promise { +export async function buildDotNetPortalPropertiesGenerated(jsObject: any): Promise { if (!hasValue(jsObject)) { return null; } diff --git a/src/dymaptic.GeoBlazor.Core/Scripts/portalProperties.ts b/src/dymaptic.GeoBlazor.Core/Scripts/portalProperties.ts index d6d0a8217..3c6189406 100644 --- a/src/dymaptic.GeoBlazor.Core/Scripts/portalProperties.ts +++ b/src/dymaptic.GeoBlazor.Core/Scripts/portalProperties.ts @@ -2,8 +2,8 @@ import {buildDotNetPortalPropertiesGenerated, buildJsPortalPropertiesGenerated} export function buildJsPortalProperties(dotNetObject: any): any { return buildJsPortalPropertiesGenerated(dotNetObject); -} +} -export async function buildDotNetPortalProperties(jsObject: any, viewId: string | null): Promise { - return await buildDotNetPortalPropertiesGenerated(jsObject, viewId); +export async function buildDotNetPortalProperties(jsObject: any): Promise { + return await buildDotNetPortalPropertiesGenerated(jsObject); } diff --git a/src/dymaptic.GeoBlazor.Core/Scripts/searchViewModelDefaultSymbols.gb.ts b/src/dymaptic.GeoBlazor.Core/Scripts/searchViewModelDefaultSymbols.gb.ts index a17dd5ee7..1182f3c17 100644 --- a/src/dymaptic.GeoBlazor.Core/Scripts/searchViewModelDefaultSymbols.gb.ts +++ b/src/dymaptic.GeoBlazor.Core/Scripts/searchViewModelDefaultSymbols.gb.ts @@ -37,17 +37,17 @@ export async function buildDotNetSearchViewModelDefaultSymbolsGenerated(jsObject if (hasValue(jsObject.point)) { let { buildDotNetSymbol } = await import('./symbol'); - dotNetSearchViewModelDefaultSymbols.point = buildDotNetSymbol(jsObject.point, viewId); + dotNetSearchViewModelDefaultSymbols.point = buildDotNetSymbol(jsObject.point); } if (hasValue(jsObject.polygon)) { let { buildDotNetSymbol } = await import('./symbol'); - dotNetSearchViewModelDefaultSymbols.polygon = buildDotNetSymbol(jsObject.polygon, viewId); + dotNetSearchViewModelDefaultSymbols.polygon = buildDotNetSymbol(jsObject.polygon); } if (hasValue(jsObject.polyline)) { let { buildDotNetSymbol } = await import('./symbol'); - dotNetSearchViewModelDefaultSymbols.polyline = buildDotNetSymbol(jsObject.polyline, viewId); + dotNetSearchViewModelDefaultSymbols.polyline = buildDotNetSymbol(jsObject.polyline); } diff --git a/src/dymaptic.GeoBlazor.Core/Scripts/simpleRenderer.gb.ts b/src/dymaptic.GeoBlazor.Core/Scripts/simpleRenderer.gb.ts index 5a6353808..46652df79 100644 --- a/src/dymaptic.GeoBlazor.Core/Scripts/simpleRenderer.gb.ts +++ b/src/dymaptic.GeoBlazor.Core/Scripts/simpleRenderer.gb.ts @@ -47,7 +47,7 @@ export async function buildDotNetSimpleRendererGenerated(jsObject: any, viewId: if (hasValue(jsObject.symbol)) { let { buildDotNetSymbol } = await import('./symbol'); - dotNetSimpleRenderer.symbol = buildDotNetSymbol(jsObject.symbol, viewId); + dotNetSimpleRenderer.symbol = buildDotNetSymbol(jsObject.symbol); } if (hasValue(jsObject.visualVariables)) { diff --git a/src/dymaptic.GeoBlazor.Core/Scripts/sizeRampStop.gb.ts b/src/dymaptic.GeoBlazor.Core/Scripts/sizeRampStop.gb.ts index 060178dfa..c3687f94b 100644 --- a/src/dymaptic.GeoBlazor.Core/Scripts/sizeRampStop.gb.ts +++ b/src/dymaptic.GeoBlazor.Core/Scripts/sizeRampStop.gb.ts @@ -44,7 +44,7 @@ export async function buildDotNetSizeRampStopGenerated(jsObject: any, viewId: st if (hasValue(jsObject.symbol)) { let { buildDotNetSymbol } = await import('./symbol'); - dotNetSizeRampStop.symbol = buildDotNetSymbol(jsObject.symbol, viewId); + dotNetSizeRampStop.symbol = buildDotNetSymbol(jsObject.symbol); } if (hasValue(jsObject.label)) { diff --git a/src/dymaptic.GeoBlazor.Core/Scripts/sublayer.gb.ts b/src/dymaptic.GeoBlazor.Core/Scripts/sublayer.gb.ts index 8e165870b..50749e7b4 100644 --- a/src/dymaptic.GeoBlazor.Core/Scripts/sublayer.gb.ts +++ b/src/dymaptic.GeoBlazor.Core/Scripts/sublayer.gb.ts @@ -275,7 +275,7 @@ export default class SublayerGenerated extends BaseComponent { } let { buildDotNetLabel } = await import('./label'); - return await Promise.all(this.component.labelingInfo!.map(async i => await buildDotNetLabel(i, this.layerId, this.viewId))); + return await Promise.all(this.component.labelingInfo!.map(async i => await buildDotNetLabel(i))); } async setLabelingInfo(value: any): Promise { @@ -615,7 +615,7 @@ export async function buildDotNetSublayerGenerated(jsObject: any, layerId: strin if (hasValue(jsObject.labelingInfo)) { let { buildDotNetLabel } = await import('./label'); - dotNetSublayer.labelingInfo = await Promise.all(jsObject.labelingInfo.map(async i => await buildDotNetLabel(i, layerId, viewId))); + dotNetSublayer.labelingInfo = await Promise.all(jsObject.labelingInfo.map(async i => await buildDotNetLabel(i))); } if (hasValue(jsObject.orderBy)) { diff --git a/src/dymaptic.GeoBlazor.Core/Scripts/symbol.ts b/src/dymaptic.GeoBlazor.Core/Scripts/symbol.ts index efaf448af..890d7fec8 100644 --- a/src/dymaptic.GeoBlazor.Core/Scripts/symbol.ts +++ b/src/dymaptic.GeoBlazor.Core/Scripts/symbol.ts @@ -32,7 +32,7 @@ export function buildJsSymbol(symbol: any, layerId: string | null, viewId: strin } } -export function buildDotNetSymbol(symbol: any, viewId: string | null): any { +export function buildDotNetSymbol(symbol: any): any { if (!hasValue(symbol)) { return null; } @@ -50,7 +50,7 @@ export function buildDotNetSymbol(symbol: any, viewId: string | null): any { case 'text': return buildDotNetTextSymbol(symbol); case 'web-style': - return buildDotNetWebStyleSymbol(symbol, viewId); + return buildDotNetWebStyleSymbol(symbol); default: return removeCircularReferences(symbol); } diff --git a/src/dymaptic.GeoBlazor.Core/Scripts/symbolTableElementInfo.gb.ts b/src/dymaptic.GeoBlazor.Core/Scripts/symbolTableElementInfo.gb.ts index 749ea1ab3..d2652bfb6 100644 --- a/src/dymaptic.GeoBlazor.Core/Scripts/symbolTableElementInfo.gb.ts +++ b/src/dymaptic.GeoBlazor.Core/Scripts/symbolTableElementInfo.gb.ts @@ -41,7 +41,7 @@ export async function buildDotNetSymbolTableElementInfoGenerated(jsObject: any, if (hasValue(jsObject.symbol)) { let { buildDotNetSymbol } = await import('./symbol'); - dotNetSymbolTableElementInfo.symbol = buildDotNetSymbol(jsObject.symbol, viewId); + dotNetSymbolTableElementInfo.symbol = buildDotNetSymbol(jsObject.symbol); } if (hasValue(jsObject.label)) { diff --git a/src/dymaptic.GeoBlazor.Core/Scripts/trackPartInfo.gb.ts b/src/dymaptic.GeoBlazor.Core/Scripts/trackPartInfo.gb.ts index 4f1c0cb65..e6e52d2f3 100644 --- a/src/dymaptic.GeoBlazor.Core/Scripts/trackPartInfo.gb.ts +++ b/src/dymaptic.GeoBlazor.Core/Scripts/trackPartInfo.gb.ts @@ -41,7 +41,7 @@ export async function buildDotNetTrackPartInfoGenerated(jsObject: any, layerId: if (hasValue(jsObject.labelingInfo)) { let { buildDotNetLabel } = await import('./label'); - dotNetTrackPartInfo.labelingInfo = await Promise.all(jsObject.labelingInfo.map(async i => await buildDotNetLabel(i, layerId, viewId))); + dotNetTrackPartInfo.labelingInfo = await Promise.all(jsObject.labelingInfo.map(async i => await buildDotNetLabel(i))); } if (hasValue(jsObject.renderer)) { diff --git a/src/dymaptic.GeoBlazor.Core/Scripts/uniqueValueClass.gb.ts b/src/dymaptic.GeoBlazor.Core/Scripts/uniqueValueClass.gb.ts index c05609f41..5ce5b40ce 100644 --- a/src/dymaptic.GeoBlazor.Core/Scripts/uniqueValueClass.gb.ts +++ b/src/dymaptic.GeoBlazor.Core/Scripts/uniqueValueClass.gb.ts @@ -38,7 +38,7 @@ export async function buildDotNetUniqueValueClassGenerated(jsObject: any, viewId if (hasValue(jsObject.symbol)) { let { buildDotNetSymbol } = await import('./symbol'); - dotNetUniqueValueClass.symbol = buildDotNetSymbol(jsObject.symbol, viewId); + dotNetUniqueValueClass.symbol = buildDotNetSymbol(jsObject.symbol); } if (hasValue(jsObject.values)) { diff --git a/src/dymaptic.GeoBlazor.Core/Scripts/uniqueValueInfo.gb.ts b/src/dymaptic.GeoBlazor.Core/Scripts/uniqueValueInfo.gb.ts index 0dc57960c..e3631addc 100644 --- a/src/dymaptic.GeoBlazor.Core/Scripts/uniqueValueInfo.gb.ts +++ b/src/dymaptic.GeoBlazor.Core/Scripts/uniqueValueInfo.gb.ts @@ -37,7 +37,7 @@ export async function buildDotNetUniqueValueInfoGenerated(jsObject: any, viewId: if (hasValue(jsObject.symbol)) { let { buildDotNetSymbol } = await import('./symbol'); - dotNetUniqueValueInfo.symbol = buildDotNetSymbol(jsObject.symbol, viewId); + dotNetUniqueValueInfo.symbol = buildDotNetSymbol(jsObject.symbol); } if (hasValue(jsObject.label)) { diff --git a/src/dymaptic.GeoBlazor.Core/Scripts/uniqueValueRenderer.gb.ts b/src/dymaptic.GeoBlazor.Core/Scripts/uniqueValueRenderer.gb.ts index e180fa0e7..5a4040363 100644 --- a/src/dymaptic.GeoBlazor.Core/Scripts/uniqueValueRenderer.gb.ts +++ b/src/dymaptic.GeoBlazor.Core/Scripts/uniqueValueRenderer.gb.ts @@ -130,7 +130,7 @@ export default class UniqueValueRendererGenerated extends BaseComponent { } let { buildDotNetSymbol } = await import('./symbol'); - return buildDotNetSymbol(this.component.defaultSymbol, this.viewId); + return buildDotNetSymbol(this.component.defaultSymbol); } async setDefaultSymbol(value: any): Promise { @@ -372,7 +372,7 @@ export async function buildDotNetUniqueValueRendererGenerated(jsObject: any, lay if (hasValue(jsObject.defaultSymbol)) { let { buildDotNetSymbol } = await import('./symbol'); - dotNetUniqueValueRenderer.defaultSymbol = buildDotNetSymbol(jsObject.defaultSymbol, viewId); + dotNetUniqueValueRenderer.defaultSymbol = buildDotNetSymbol(jsObject.defaultSymbol); } if (hasValue(jsObject.legendOptions)) { diff --git a/src/dymaptic.GeoBlazor.Core/Scripts/wFSLayer.gb.ts b/src/dymaptic.GeoBlazor.Core/Scripts/wFSLayer.gb.ts index 271e00244..e16b671f5 100644 --- a/src/dymaptic.GeoBlazor.Core/Scripts/wFSLayer.gb.ts +++ b/src/dymaptic.GeoBlazor.Core/Scripts/wFSLayer.gb.ts @@ -445,7 +445,7 @@ export default class WFSLayerGenerated extends BaseComponent { } let { buildDotNetLabel } = await import('./label'); - return await Promise.all(this.layer.labelingInfo!.map(async i => await buildDotNetLabel(i, this.layerId, this.viewId))); + return await Promise.all(this.layer.labelingInfo!.map(async i => await buildDotNetLabel(i))); } async setLabelingInfo(value: any): Promise { @@ -888,7 +888,7 @@ export async function buildDotNetWFSLayerGenerated(jsObject: any, layerId: strin if (hasValue(jsObject.labelingInfo)) { let { buildDotNetLabel } = await import('./label'); - dotNetWFSLayer.labelingInfo = await Promise.all(jsObject.labelingInfo.map(async i => await buildDotNetLabel(i, layerId, viewId))); + dotNetWFSLayer.labelingInfo = await Promise.all(jsObject.labelingInfo.map(async i => await buildDotNetLabel(i))); } if (hasValue(jsObject.orderBy)) { diff --git a/src/dymaptic.GeoBlazor.Core/Scripts/webStyleSymbol.ts b/src/dymaptic.GeoBlazor.Core/Scripts/webStyleSymbol.ts index 80f9874ab..80c1f9f22 100644 --- a/src/dymaptic.GeoBlazor.Core/Scripts/webStyleSymbol.ts +++ b/src/dymaptic.GeoBlazor.Core/Scripts/webStyleSymbol.ts @@ -47,7 +47,7 @@ export function buildJsWebStyleSymbol(dotNetObject: any, layerId: string | null, return jsWebStyleSymbol; } -export function buildDotNetWebStyleSymbol(jsObject: any, viewId: string | null): any { +export function buildDotNetWebStyleSymbol(jsObject: any): any { if (!hasValue(jsObject)) { return null; } @@ -59,7 +59,7 @@ export function buildDotNetWebStyleSymbol(jsObject: any, viewId: string | null): } if (hasValue(jsObject.portal)) { - dotNetWebStyleSymbol.portal = buildDotNetPortal(jsObject.portal, viewId); + dotNetWebStyleSymbol.portal = buildDotNetPortal(jsObject.portal, null, null); } if (hasValue(jsObject.name)) { diff --git a/src/dymaptic.GeoBlazor.Core/Serialization/CoreSerializationData.cs b/src/dymaptic.GeoBlazor.Core/Serialization/CoreSerializationData.cs index ceb179c72..86b0c60cc 100644 --- a/src/dymaptic.GeoBlazor.Core/Serialization/CoreSerializationData.cs +++ b/src/dymaptic.GeoBlazor.Core/Serialization/CoreSerializationData.cs @@ -19,9 +19,9 @@ internal static partial class CoreSerializationData public static partial object ToProtobufCollectionParameter(this IList items, Type serializableType, bool isServer); public static partial Task ReadJsStreamReferenceAsProtobuf(this IJSStreamReference jsStreamReference, - Type returnType, long maxAllowedSize = 1_000_000_000); + Type returnType, long? maxAllowedSize = null, CancellationToken cancellationToken = default); public static partial Task ReadJsStreamReferenceAsProtobufCollection( this IJSStreamReference jsStreamReference, - Type returnType, long maxAllowedSize = 1_000_000_000); + Type returnType, long? maxAllowedSize = null, CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/src/dymaptic.GeoBlazor.Core/Serialization/IJSStreamReferenceExtensions.cs b/src/dymaptic.GeoBlazor.Core/Serialization/IJSStreamReferenceExtensions.cs index ca23683f2..6d5393f46 100644 --- a/src/dymaptic.GeoBlazor.Core/Serialization/IJSStreamReferenceExtensions.cs +++ b/src/dymaptic.GeoBlazor.Core/Serialization/IJSStreamReferenceExtensions.cs @@ -6,18 +6,22 @@ namespace dymaptic.GeoBlazor.Core.Serialization; public static class IJSStreamReferenceExtensions { internal static async Task ReadJsStreamReferenceAsStream(this IJSStreamReference jsStreamReference, - long maxAllowedSize = 1_000_000_000L) + long? maxAllowedSize = null, CancellationToken cancellationToken = default) { - return await jsStreamReference.OpenReadStreamAsync(maxAllowedSize); + maxAllowedSize ??= 1_000_000_000L; + + return await jsStreamReference.OpenReadStreamAsync(maxAllowedSize.Value, cancellationToken); } /// /// Convenience method to deserialize an to a specific .NET type. /// internal static async Task ReadJsStreamReferenceAsJSON(this IJSStreamReference jsStreamReference, - long maxAllowedSize = 1_000_000_000L) + long? maxAllowedSize = null, CancellationToken cancellationToken = default) { - return (T?)await jsStreamReference.ReadJsStreamReferenceAsJSON(typeof(T), maxAllowedSize); + maxAllowedSize ??= 1_000_000_000L; + + return (T?)await jsStreamReference.ReadJsStreamReferenceAsJSON(typeof(T), maxAllowedSize, cancellationToken); } /// @@ -26,12 +30,16 @@ public static class IJSStreamReferenceExtensions /// internal static async Task ReadJsStreamReferenceAsJSON(this IJSStreamReference jsStreamReference, Type returnType, - long maxAllowedSize = 1_000_000_000) + long? maxAllowedSize = null, + CancellationToken cancellationToken = default) { - await using Stream stream = await jsStreamReference.OpenReadStreamAsync(maxAllowedSize); + maxAllowedSize ??= 1_000_000_000L; + + await using Stream stream = + await jsStreamReference.OpenReadStreamAsync(maxAllowedSize.Value, cancellationToken); using StreamReader reader = new(stream, Encoding.UTF8); - string json = await reader.ReadToEndAsync(); + string json = await reader.ReadToEndAsync(cancellationToken); if (returnType == typeof(string)) { diff --git a/src/dymaptic.GeoBlazor.Core/Serialization/JsSyncManager.cs b/src/dymaptic.GeoBlazor.Core/Serialization/JsSyncManager.cs index 1fe9098aa..0789da299 100644 --- a/src/dymaptic.GeoBlazor.Core/Serialization/JsSyncManager.cs +++ b/src/dymaptic.GeoBlazor.Core/Serialization/JsSyncManager.cs @@ -24,12 +24,17 @@ public static class JsSyncManager /// public static Dictionary ProtoCollectionTypes { get; set; } = []; + /// + /// Collection of Reusable instances for components. + /// + public static Dictionary AbortManagers { get; set; } = []; + /// /// Initializes the JsSyncManager by registering protobuf types and compiling the runtime model. /// public static void Initialize() { - foreach (Type protoType in ProtoContractTypes.Values) + foreach (var protoType in ProtoContractTypes.Values) { RuntimeTypeModel.Default.Add(protoType, true); } @@ -66,8 +71,8 @@ public static async Task InvokeVoidJsMethod(this IJSObjectReference js, bool isS [CallerMemberName] string method = "", string className = "", CancellationToken cancellationToken = default, params object?[] parameters) { - SerializableMethodRecord methodRecord = GetMethodRecord(method, className, true, parameters); - List parameterList = await GenerateSerializedParameters(methodRecord, parameters, isServer, js); + var methodRecord = GetMethodRecord(method, className, true, parameters); + var parameterList = await GenerateSerializedParameters(methodRecord, parameters, isServer, js); await js.InvokeVoidAsync("invokeVoidSerializedMethod", cancellationToken, [methodRecord.MethodName, isServer, ..parameterList]); @@ -88,6 +93,9 @@ await js.InvokeVoidAsync("invokeVoidSerializedMethod", cancellationToken, /// /// The name of the calling class. /// + /// + /// The maximum size of the returned data. + /// /// /// A CancellationToken to cancel an asynchronous operation. /// @@ -95,17 +103,18 @@ await js.InvokeVoidAsync("invokeVoidSerializedMethod", cancellationToken, /// The collection of parameters to pass to the JS call. /// public static async Task InvokeJsMethod(this IJSObjectReference js, bool isServer, - [CallerMemberName] string method = "", string className = "", + [CallerMemberName] string method = "", string className = "", long? maxAllowedSize = null, CancellationToken cancellationToken = default, params object?[] parameters) { - SerializableMethodRecord methodRecord = GetMethodRecord(method, className, false, parameters); + maxAllowedSize ??= 1_000_000_000L; + var methodRecord = GetMethodRecord(method, className, false, parameters); - List parameterList = await GenerateSerializedParameters(methodRecord, parameters, isServer, js); - Type? returnType = methodRecord.ReturnValue?.Type; - bool returnTypeIsProtobuf = returnType is not null && ProtoContractTypes.ContainsKey(returnType); + var parameterList = await GenerateSerializedParameters(methodRecord, parameters, isServer, js); + var returnType = methodRecord.ReturnValue?.Type; + var returnTypeIsProtobuf = returnType is not null && ProtoContractTypes.ContainsKey(returnType); - if (isServer || returnTypeIsProtobuf || returnType?.IsAssignableTo(typeof(Stream)) == true) + if (isServer || returnTypeIsProtobuf || (returnType?.IsAssignableTo(typeof(Stream)) == true)) { string? protoReturnTypeName = null; @@ -126,13 +135,13 @@ public static async Task InvokeJsMethod(this IJSObjectReference js, bool i protoReturnTypeName = protoReturnType?.Name.Replace("SerializationRecord", ""); } - IJSStreamReference? streamRef = await js.InvokeAsync("invokeSerializedMethod", + var streamRef = await js.InvokeAsync("invokeSerializedMethod", cancellationToken, [methodRecord.MethodName, true, returnTypeIsProtobuf, protoReturnTypeName, ..parameterList]); if (streamRef is null) { - return default!; + return default(T)!; } if (returnTypeIsProtobuf) @@ -140,26 +149,29 @@ public static async Task InvokeJsMethod(this IJSObjectReference js, bool i if (methodRecord.ReturnValue?.SingleType is not null) { return await streamRef.ReadJsStreamReferenceAsProtobufCollection(methodRecord.ReturnValue - .SingleType) ?? default!; + .SingleType, maxAllowedSize, cancellationToken) ?? default(T)!; } - return await streamRef.ReadJsStreamReferenceAsProtobuf(returnType!) ?? default!; + return await streamRef.ReadJsStreamReferenceAsProtobuf(returnType!, maxAllowedSize, + cancellationToken) ?? default(T)!; } if (returnType?.IsAssignableTo(typeof(Stream)) == true) { - Stream? result = await streamRef.ReadJsStreamReferenceAsStream(); + // the calling code actually wants to handle the stream, so we will just unwrap and pass it back + var result = await streamRef.ReadJsStreamReferenceAsStream(maxAllowedSize, cancellationToken); if (result is null) { - return default!; + return default(T)!; } // double-cast to force to generic return (T)(object)result; } - return (await streamRef.ReadJsStreamReferenceAsJSON())!; + // read and deserialize the stream + return (await streamRef.ReadJsStreamReferenceAsJSON(maxAllowedSize, cancellationToken))!; } return await js.InvokeAsync("invokeSerializedMethod", cancellationToken, @@ -169,7 +181,7 @@ public static async Task InvokeJsMethod(this IJSObjectReference js, bool i private static SerializableMethodRecord GetMethodRecord(string method, string className, bool returnsVoid, object?[] providedParameters) { - if (!_serializableMethods.TryGetValue(className, out List? classMethods)) + if (!_serializableMethods.TryGetValue(className, out var classMethods)) { classMethods = new List(); _serializableMethods[className] = classMethods; @@ -181,7 +193,7 @@ private static SerializableMethodRecord GetMethodRecord(string method, string string.Equals(m.MethodName, method, StringComparison.OrdinalIgnoreCase) // same number of parameters - && m.Parameters.Length == providedParameters.Length + && (m.Parameters.Length == providedParameters.Length) // either both void or both non-void && ((m.ReturnValue is null && returnsVoid) @@ -194,27 +206,27 @@ private static SerializableMethodRecord GetMethodRecord(string method, string var methodInfos = classType.GetMethods(BindingFlags.Instance | BindingFlags.Public) .Where(m => string.Equals(m.Name, method, StringComparison.OrdinalIgnoreCase) - && m.GetParameters().Length == providedParameters.Length) + && (m.GetParameters().Length == providedParameters.Length)) .ToArray(); matchedMethods = []; - foreach (MethodInfo methodInfo in methodInfos.Where(m => m.ReturnType.IsGenericType)) + foreach (var methodInfo in methodInfos.Where(m => m.ReturnType.IsGenericType)) { List methodParams = []; var paramInfos = methodInfo.GetParameters(); - foreach (ParameterInfo paramInfo in paramInfos) + foreach (var paramInfo in paramInfos) { methodParams.Add(CreateParameterRecord(paramInfo.ParameterType, paramInfo)); } SerializableParameterRecord? returnRecord = null; - if (!returnsVoid && methodInfo.ReturnType != typeof(void)) + if (!returnsVoid && (methodInfo.ReturnType != typeof(void))) { - ParameterInfo returnParamInfo = methodInfo.ReturnParameter; - Type returnType = returnParamInfo.ParameterType; + var returnParamInfo = methodInfo.ReturnParameter; + var returnType = returnParamInfo.ParameterType; returnRecord = CreateParameterRecord(returnType, returnParamInfo); @@ -245,23 +257,23 @@ private static SerializableMethodRecord GetMethodRecord(string method, string return matchedMethods[0]; } - Type requestedReturnType = typeof(T); + var requestedReturnType = typeof(T); // find record with potentially matching parameter types including nulls return matchedMethods.First(m => { - for (int i = 0; i < m.Parameters.Length; i++) + for (var i = 0; i < m.Parameters.Length; i++) { - Type? providedParameterType = providedParameters[i]?.GetType(); + var providedParameterType = providedParameters[i]?.GetType(); if (providedParameterType is not null && providedParameterType.IsGenericType && - providedParameterType.GetGenericTypeDefinition() == typeof(Nullable<>)) + (providedParameterType.GetGenericTypeDefinition() == typeof(Nullable<>))) { providedParameterType = Nullable.GetUnderlyingType(providedParameterType)!; } - SerializableParameterRecord methodParam = m.Parameters[i]; + var methodParam = m.Parameters[i]; if (providedParameterType is null) { @@ -276,7 +288,7 @@ private static SerializableMethodRecord GetMethodRecord(string method, string } } - if (!returnsVoid && requestedReturnType != m.ReturnValue?.Type) + if (!returnsVoid && (requestedReturnType != m.ReturnValue?.Type)) { return false; } @@ -290,10 +302,10 @@ private static SerializableMethodRecord GetMethodRecord(string method, string { List serializedParameters = []; - for (int i = 0; i < parameters.Length; i++) + for (var i = 0; i < parameters.Length; i++) { - object? parameterValue = parameters[i]; - SerializableParameterRecord parameterRecord = methodRecord.Parameters[i]; + var parameterValue = parameters[i]; + var parameterRecord = methodRecord.Parameters[i]; serializedParameters.AddRange(await ProcessParameter(parameterValue, parameterRecord, isServer, js)); } @@ -324,14 +336,14 @@ private static SerializableMethodRecord GetMethodRecord(string method, string return ["null", null]; } - Type paramType = parameterRecord.Type; + var paramType = parameterRecord.Type; if (paramType == typeof(CancellationToken)) { - if (!abortManagers.TryGetValue(js, out AbortManager? abortManager)) + if (!AbortManagers.TryGetValue(js, out var abortManager)) { abortManager = new AbortManager(js); - abortManagers[js] = abortManager; + AbortManagers[js] = abortManager; } object? value = parameterValue is CancellationToken token @@ -354,16 +366,16 @@ private static SerializableMethodRecord GetMethodRecord(string method, string if (paramType.IsEnum) { // use the JsonConverter defined EnumToKebabCaseConverters to serialize enums as strings - string stringValue = JsonSerializer.Serialize(parameterValue, GeoBlazorSerialization.JsonSerializerOptions); + var stringValue = JsonSerializer.Serialize(parameterValue, GeoBlazorSerialization.JsonSerializerOptions); // pass as type string so JS can parse correctly return [nameof(String), stringValue]; } - if (paramType.IsGenericType && paramType.GetGenericTypeDefinition() == typeof(Nullable<>)) + if (paramType.IsGenericType && (paramType.GetGenericTypeDefinition() == typeof(Nullable<>))) { - Type underlyingType = Nullable.GetUnderlyingType(paramType)!; - object underlyingValue = Convert.ChangeType(parameterValue, underlyingType); + var underlyingType = Nullable.GetUnderlyingType(paramType)!; + var underlyingValue = Convert.ChangeType(parameterValue, underlyingType); return await ProcessParameter(underlyingValue, parameterRecord with { Type = underlyingType, IsNullable = true }, isServer, js); @@ -371,14 +383,14 @@ private static SerializableMethodRecord GetMethodRecord(string method, string if (parameterValue is IList list && !geoblazorEnumerableTypes.Contains(paramType)) { - Type genericType = parameterRecord.SingleType ?? (paramType.IsArray + var genericType = parameterRecord.SingleType ?? (paramType.IsArray ? paramType.GetElementType()! : paramType.GenericTypeArguments[0]); - string key = $"{GetKey(genericType)}Collection"; + var key = $"{GetKey(genericType)}Collection"; if (ProtoContractTypes.ContainsKey(genericType)) { - object protobufParameter = list.ToProtobufCollectionParameter(genericType, isServer); + var protobufParameter = list.ToProtobufCollectionParameter(genericType, isServer); return [key, protobufParameter]; } @@ -388,14 +400,14 @@ private static SerializableMethodRecord GetMethodRecord(string method, string if (ProtoContractTypes.ContainsKey(paramType)) { - object protobufParameter = parameterValue.ToProtobufParameter(paramType, isServer); + var protobufParameter = parameterValue.ToProtobufParameter(paramType, isServer); return [GetKey(paramType), protobufParameter]; } if (parameterValue is AttributesDictionary attributesDictionary) { - AttributeSerializationRecord[] serializedItems = attributesDictionary.ToProtobufArray(); + var serializedItems = attributesDictionary.ToProtobufArray(); AttributeCollectionSerializationRecord collection = new(serializedItems); MemoryStream memoryStream = new(); Serializer.Serialize(memoryStream, collection); @@ -406,7 +418,7 @@ private static SerializableMethodRecord GetMethodRecord(string method, string return [nameof(AttributesDictionary), new DotNetStreamReference(memoryStream)]; } - byte[] data = memoryStream.ToArray(); + var data = memoryStream.ToArray(); await memoryStream.DisposeAsync(); return [nameof(AttributesDictionary), data]; @@ -431,7 +443,7 @@ private static object ToJsonParameter(this T obj, bool isServer) private static string GetKey(Type type) { - Type? matchedType = ProtoContractTypes.Keys.FirstOrDefault(t => t == type); + var matchedType = ProtoContractTypes.Keys.FirstOrDefault(t => t == type); if (matchedType is not null) { @@ -449,7 +461,7 @@ private static SerializableParameterRecord GetSerializableParameterRecord(object null, false); } - Type paramType = parameter.GetType(); + var paramType = parameter.GetType(); return CreateParameterRecord(paramType, null); } @@ -462,7 +474,7 @@ private static SerializableParameterRecord GetSerializableReturnRecord(bool r null, false); } - Type returnType = typeof(T); + var returnType = typeof(T); return CreateParameterRecord(returnType, null); } @@ -498,41 +510,41 @@ private static SerializableParameterRecord CreateParameterRecord(Type paramType, typeof(AttributeSerializationRecord), false); } - bool typeIsNullable = IsTypeNullable(paramType) + var typeIsNullable = IsTypeNullable(paramType) || (parameterInfo is not null && IsParameterNullable(parameterInfo)); // unwrap nullable types - if (typeIsNullable && paramType.IsGenericType && paramType.GetGenericTypeDefinition() == typeof(Nullable<>)) + if (typeIsNullable && paramType.IsGenericType && (paramType.GetGenericTypeDefinition() == typeof(Nullable<>))) { paramType = Nullable.GetUnderlyingType(paramType)!; } - bool isCollection = paramType.IsAssignableTo(typeof(IEnumerable)) + var isCollection = paramType.IsAssignableTo(typeof(IEnumerable)) && !paramType.IsAssignableTo(typeof(IDictionary)); - Type? collectionType = isCollection + var collectionType = isCollection ? paramType.IsArray ? paramType.GetElementType() : paramType.GenericTypeArguments[0] : null; - bool collectionTypeIsNullable = IsTypeNullable(collectionType); + var collectionTypeIsNullable = IsTypeNullable(collectionType); return new SerializableParameterRecord(paramType, typeIsNullable, collectionType, collectionTypeIsNullable); } - static bool IsParameterNullable(ParameterInfo paramInfo) + private static bool IsParameterNullable(ParameterInfo paramInfo) { - NullabilityInfo nullabilityInfo = nullabilityContext.Create(paramInfo); + var nullabilityInfo = nullabilityContext.Create(paramInfo); return nullabilityInfo.ReadState == NullabilityState.Nullable; } - static bool IsTypeNullable(Type? type) + private static bool IsTypeNullable(Type? type) { return type is { IsGenericType: true } - && type.GetGenericTypeDefinition() == typeof(Nullable<>); + && (type.GetGenericTypeDefinition() == typeof(Nullable<>)); } private static readonly NullabilityInfoContext nullabilityContext = new(); @@ -549,7 +561,6 @@ static bool IsTypeNullable(Type? type) [ typeof(MapPath), typeof(MapPoint) ]; - private static readonly Dictionary abortManagers = []; private static Dictionary> _serializableMethods = []; } diff --git a/src/dymaptic.GeoBlazor.Core/Serialization/SizeVariableConverter.cs b/src/dymaptic.GeoBlazor.Core/Serialization/SizeVariableConverter.cs new file mode 100644 index 000000000..fb882f29c --- /dev/null +++ b/src/dymaptic.GeoBlazor.Core/Serialization/SizeVariableConverter.cs @@ -0,0 +1,221 @@ +namespace dymaptic.GeoBlazor.Core.Serialization; + +internal class SizeVariableConverter : JsonConverter +{ + public override SizeVariable? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + // manually loop through the reader + // required because of the "MinSize" and "MaxSize" properties which can be either Dimension or SizeVariable + if (reader.TokenType == JsonTokenType.StartObject) + { + SizeVariable sizeVariable = new(); + string? currentPropertyName = null; +#pragma warning disable BL0005 + while (reader.Read()) + { + switch (reader.TokenType) + { + case JsonTokenType.PropertyName: + currentPropertyName = reader.GetString()?.ToUpperFirstChar(); + + break; + case JsonTokenType.EndObject: + return sizeVariable; + default: + switch (currentPropertyName) + { + case nameof(SizeVariable.Field): + sizeVariable.Field = reader.GetString(); + + break; + case nameof(SizeVariable.MinSize): + var (minDimension, minSizeVariable) = + GetDimensionOrSizeVariable(ref reader, options); + + if (minDimension is not null) + { + sizeVariable.MinSize = minDimension; + } + + if (minSizeVariable is not null) + { + sizeVariable.MinSize = minSizeVariable.MinSize; + } + + break; + case nameof(SizeVariable.MaxSize): + var (maxDimension, maxSizeVariable) = + GetDimensionOrSizeVariable(ref reader, options); + + if (maxDimension is not null) + { + sizeVariable.MaxSize = maxDimension; + } + + if (maxSizeVariable is not null) + { + sizeVariable.MaxSize = maxSizeVariable.MaxSize; + } + + break; + case nameof(SizeVariable.MinDataValue): + if (reader.TokenType == JsonTokenType.Number) + { + sizeVariable.MinDataValue = reader.GetDouble(); + } + + break; + case nameof(SizeVariable.MaxDataValue): + if (reader.TokenType == JsonTokenType.Number) + { + sizeVariable.MaxDataValue = reader.GetDouble(); + } + + break; + case nameof(SizeVariable.ValueRepresentation): + sizeVariable.ValueRepresentation = + JsonSerializer.Deserialize(ref reader, options); + + break; + case nameof(SizeVariable.ValueUnit): + sizeVariable.ValueUnit = + JsonSerializer.Deserialize(ref reader, options); + + break; + case nameof(SizeVariable.NormalizationField): + sizeVariable.NormalizationField = reader.GetString(); + + break; + case nameof(SizeVariable.Target): + sizeVariable.Target = reader.GetString(); + + break; + case nameof(SizeVariable.UseSymbolValue): + if (reader.TokenType != JsonTokenType.Null) + { + sizeVariable.UseSymbolValue = reader.GetBoolean(); + } + + break; + case nameof(SizeVariable.Axis): + sizeVariable.Axis = + JsonSerializer.Deserialize(ref reader, options); + + break; + case nameof(SizeVariable.ValueExpression): + sizeVariable.ValueExpression = reader.GetString(); + + break; + case nameof(SizeVariable.ValueExpressionTitle): + sizeVariable.ValueExpressionTitle = reader.GetString(); + + break; + case nameof(SizeVariable.LegendOptions): + sizeVariable.LegendOptions = JsonSerializer + .Deserialize(ref reader, options); + + break; + case nameof(SizeVariable.Stops): + sizeVariable.Stops = JsonSerializer + .Deserialize>(ref reader, options); + + break; + } + + break; + } + } + + return sizeVariable; + } +#pragma warning restore BL0005 + return null; + } + + public override void Write(Utf8JsonWriter writer, SizeVariable value, JsonSerializerOptions options) + { + // Dimensions are handled normally, but SizeVariables need to be serialized differently + if (value.MinSizeVariable is null && value.MaxSizeVariable is null) + { + writer.WriteRawValue(JsonSerializer.Serialize(value, typeof(object), + GeoBlazorSerialization.JsonSerializerOptions)); + + return; + } + + PropertyInfo[] props = value.GetType() + .GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy); + + writer.WriteStartObject(); + + foreach (PropertyInfo prop in props) + { + JsonIgnoreAttribute? ignoreAttribute = prop.GetCustomAttribute(); + + if (ignoreAttribute is not null && ignoreAttribute.Condition == JsonIgnoreCondition.WhenWritingNull) + { + continue; + } + + object? propValue = prop.GetValue(value); + + if (ignoreAttribute is not null && propValue is null + && ignoreAttribute.Condition == JsonIgnoreCondition.WhenWritingNull) + { + continue; + } + + if (prop.Name == nameof(SizeVariable.MinSizeVariable) || prop.Name == nameof(SizeVariable.MaxSizeVariable)) + { + writer.WritePropertyName(prop.Name.Replace("Variable", "").ToLowerFirstChar()); + } + else + { + writer.WritePropertyName(prop.Name.ToLowerFirstChar()); + } + + writer.WriteRawValue(JsonSerializer.Serialize(propValue, typeof(object), + GeoBlazorSerialization.JsonSerializerOptions)); + } + + writer.WriteEndObject(); + } + + private (Dimension? dimension, SizeVariable? sizeVariable) GetDimensionOrSizeVariable(ref Utf8JsonReader reader, + JsonSerializerOptions options) + { + Dimension? dimension = null; + SizeVariable? sizeVariable = null; + + switch (reader.TokenType) + { + case JsonTokenType.String: + var stringVal = reader.GetString(); + + if (stringVal is not null) + { + dimension = new Dimension(stringVal); + } + + break; + case JsonTokenType.Number: + var doubleVal = reader.GetDouble(); + dimension = new Dimension(doubleVal); + + break; + case JsonTokenType.StartObject: + try + { + sizeVariable = JsonSerializer.Deserialize(ref reader, options); + } + catch + { + dimension = JsonSerializer.Deserialize(ref reader, options); + } + + break; + } + + return (dimension, sizeVariable); + } +} \ No newline at end of file diff --git a/src/dymaptic.GeoBlazor.Core/Serialization/VisualVariableConverter.cs b/src/dymaptic.GeoBlazor.Core/Serialization/VisualVariableConverter.cs index c51105945..4820c356c 100644 --- a/src/dymaptic.GeoBlazor.Core/Serialization/VisualVariableConverter.cs +++ b/src/dymaptic.GeoBlazor.Core/Serialization/VisualVariableConverter.cs @@ -4,28 +4,27 @@ internal class VisualVariableConverter : JsonConverter { public override VisualVariable? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - var newOptions = new JsonSerializerOptions(options) - { - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull - }; - Utf8JsonReader cloneReader = reader; + var cloneReader = reader; - if (JsonSerializer.Deserialize>(ref reader, newOptions) is not - IDictionary temp) + if (JsonSerializer.Deserialize>(ref reader, + GeoBlazorSerialization.JsonSerializerOptions) is not IDictionary temp) { return null; } - if (temp.TryGetValue("type", out object? typeValue)) + if (temp.TryGetValue("type", out var typeValue)) { switch (typeValue?.ToString()) { case "size": - return JsonSerializer.Deserialize(ref cloneReader, newOptions); + return JsonSerializer.Deserialize(ref cloneReader, + GeoBlazorSerialization.JsonSerializerOptions); case "color": - return JsonSerializer.Deserialize(ref cloneReader, newOptions); + return JsonSerializer.Deserialize(ref cloneReader, + GeoBlazorSerialization.JsonSerializerOptions); case "opacity": - return JsonSerializer.Deserialize(ref cloneReader, newOptions); + return JsonSerializer.Deserialize(ref cloneReader, + GeoBlazorSerialization.JsonSerializerOptions); case null: return null; } @@ -36,10 +35,7 @@ internal class VisualVariableConverter : JsonConverter public override void Write(Utf8JsonWriter writer, VisualVariable value, JsonSerializerOptions options) { - var newOptions = new JsonSerializerOptions(options) - { - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull - }; - writer.WriteRawValue(JsonSerializer.Serialize(value, typeof(object), newOptions)); + writer.WriteRawValue(JsonSerializer.Serialize(value, typeof(object), + GeoBlazorSerialization.JsonSerializerOptions)); } } \ No newline at end of file diff --git a/src/dymaptic.GeoBlazor.Core/dymaptic.GeoBlazor.Core.csproj b/src/dymaptic.GeoBlazor.Core/dymaptic.GeoBlazor.Core.csproj index 7211386f0..32c02ba05 100644 --- a/src/dymaptic.GeoBlazor.Core/dymaptic.GeoBlazor.Core.csproj +++ b/src/dymaptic.GeoBlazor.Core/dymaptic.GeoBlazor.Core.csproj @@ -96,7 +96,7 @@ - + @@ -110,8 +110,9 @@ - + diff --git a/test/dymaptic.GeoBlazor.Core.SourceGenerator.Tests/CoreSourceGeneratorTests.cs b/test/dymaptic.GeoBlazor.Core.SourceGenerator.Tests/CoreSourceGeneratorTests.cs index c6a707088..6b61a0b0b 100644 --- a/test/dymaptic.GeoBlazor.Core.SourceGenerator.Tests/CoreSourceGeneratorTests.cs +++ b/test/dymaptic.GeoBlazor.Core.SourceGenerator.Tests/CoreSourceGeneratorTests.cs @@ -56,7 +56,7 @@ public void TestCanTriggerESBuildInDebugMode() "Expected a GBSourceGen diagnostic from the generator."); Assert.IsTrue(diagnostics.Any(d => d.GetMessage() - .Contains("Core ESBuild process completed successfully.")), + .Contains("Command 'dotnet ESBuild.dll -c Debug' completed successfully.")), "Expected a Core ESBuild process completed successfully."); } @@ -100,7 +100,7 @@ public void TestCanTriggerESBuildInReleaseMode() "Expected a GBSourceGen diagnostic from the generator."); Assert.IsTrue(diagnostics.Any(d => d.GetMessage() - .Contains("Core ESBuild process completed successfully.")), + .Contains("Command 'dotnet ESBuild.dll -c Release' completed successfully.")), "Expected a Core ESBuild process completed successfully."); } diff --git a/test/dymaptic.GeoBlazor.Core.Test.Automation.SourceGeneration/GenerateTests.cs b/test/dymaptic.GeoBlazor.Core.Test.Automation.SourceGeneration/GenerateTests.cs index 3c3f8e62c..e442f2465 100644 --- a/test/dymaptic.GeoBlazor.Core.Test.Automation.SourceGeneration/GenerateTests.cs +++ b/test/dymaptic.GeoBlazor.Core.Test.Automation.SourceGeneration/GenerateTests.cs @@ -123,8 +123,14 @@ private void Generate(SourceProductionContext context, ImmutableArray Date: Thu, 29 Jan 2026 16:27:25 -0600 Subject: [PATCH 13/89] tests passing --- .gitignore | 1 + Directory.Build.props | 3 - Directory.Build.targets | 6 - Dockerfile | 1 - badge_fullmethodcoverage.svg | 2 +- badge_linecoverage.svg | 2 +- badge_methodcoverage.svg | 2 +- build-scripts/GBTest.cs | 146 +++++ build-scripts/GeoBlazorBuild.cs | 13 +- build-scripts/GeoBlazorCover.cs | 58 -- build-scripts/ScriptBuilder.cs | 40 +- build-tools/BuildAppSettings.dll | Bin 10240 -> 10240 bytes build-tools/BuildAppSettings.exe | Bin 162304 -> 162304 bytes build-tools/BuildTemplates.dll | Bin 25088 -> 25088 bytes build-tools/BuildTemplates.exe | Bin 162304 -> 162304 bytes build-tools/ConsoleDialog.dll | Bin 11264 -> 11264 bytes build-tools/ConsoleDialog.exe | Bin 162304 -> 162304 bytes build-tools/ESBuild.dll | Bin 19456 -> 19456 bytes build-tools/ESBuild.exe | Bin 162304 -> 162304 bytes build-tools/ESBuildClearLocks.dll | Bin 8192 -> 8192 bytes build-tools/ESBuildClearLocks.exe | Bin 162304 -> 162304 bytes build-tools/ESBuildWaitForCompletion.dll | Bin 9728 -> 9728 bytes build-tools/ESBuildWaitForCompletion.exe | Bin 162816 -> 162816 bytes build-tools/FetchNuGetVersion.dll | Bin 9216 -> 9216 bytes build-tools/FetchNuGetVersion.exe | Bin 162304 -> 162304 bytes build-tools/GBTest.dll | Bin 0 -> 10752 bytes build-tools/GBTest.exe | Bin 0 -> 162304 bytes build-tools/GeoBlazorBuild.dll | Bin 40448 -> 39936 bytes build-tools/GeoBlazorBuild.exe | Bin 162304 -> 162304 bytes build-tools/ScriptBuilder.dll | Bin 0 -> 11776 bytes build-tools/ScriptBuilder.exe | Bin 0 -> 162304 bytes .../ESBuildGenerator.cs | 29 +- .../ProtobufSourceGenerator.cs | 16 +- .../Scripts/arcGisJsInterop.ts | 7 +- .../badge_fullmethodcoverage.svg | 6 +- .../badge_linecoverage.svg | 6 +- .../badge_methodcoverage.svg | 6 +- .../dymaptic.GeoBlazor.Core.csproj | 8 + .../CoreSourceGeneratorTests.cs | 10 +- .../GeoBlazorTestClass.cs | 152 ++--- .../TestConfig.cs | 567 +++++++++++------- ...ptic.GeoBlazor.Core.Test.Automation.csproj | 1 + .../Components/LayerListWidgetTests.razor | 11 +- .../Components/TestRunnerBase.razor | 2 +- .../Components/TestRunnerBase.razor.cs | 191 +++--- .../Components/WMSLayerTests.razor | 10 +- .../Pages/Index.razor.cs | 4 +- ...c.GeoBlazor.Core.Test.Blazor.Shared.csproj | 1 + .../wwwroot/testRunner.js | 37 +- 49 files changed, 787 insertions(+), 551 deletions(-) delete mode 100644 Directory.Build.targets create mode 100644 build-scripts/GBTest.cs delete mode 100644 build-scripts/GeoBlazorCover.cs create mode 100644 build-tools/GBTest.dll create mode 100644 build-tools/GBTest.exe create mode 100644 build-tools/ScriptBuilder.dll create mode 100644 build-tools/ScriptBuilder.exe diff --git a/.gitignore b/.gitignore index cf1071401..f64daf41e 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ .DS_Store esBuild.*.lock esBuild.log +esBuild.hash .esbuild-record.json CustomerTests.razor .claude/ diff --git a/Directory.Build.props b/Directory.Build.props index a17678e13..3efb61453 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -5,7 +5,6 @@ enable enable - 5.0.0.38 true Debug;Release;SourceGen Highlighting AnyCPU @@ -20,8 +19,6 @@ - - diff --git a/Directory.Build.targets b/Directory.Build.targets deleted file mode 100644 index ff2f4fea7..000000000 --- a/Directory.Build.targets +++ /dev/null @@ -1,6 +0,0 @@ - - - - - \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 17a29e3eb..f538017be 100644 --- a/Dockerfile +++ b/Dockerfile @@ -60,7 +60,6 @@ RUN --mount=type=cache,target=/root/.nuget/packages \ dotnet publish ./test/dymaptic.GeoBlazor.Core.Test.WebApp/dymaptic.GeoBlazor.Core.Test.WebApp/dymaptic.GeoBlazor.Core.Test.WebApp.csproj \ -c Release \ /p:UsePackageReference=false \ - /p:PipelineBuild=true \ /p:DebugSymbols=true \ /p:DebugType=portable \ /p:GeneratePack=false \ diff --git a/badge_fullmethodcoverage.svg b/badge_fullmethodcoverage.svg index b677d1c71..0a3eda2a4 100644 --- a/badge_fullmethodcoverage.svg +++ b/badge_fullmethodcoverage.svg @@ -132,7 +132,7 @@ - 23.3%23.3% + 1.6%1.6% diff --git a/badge_linecoverage.svg b/badge_linecoverage.svg index 342686d19..7f23f38a5 100644 --- a/badge_linecoverage.svg +++ b/badge_linecoverage.svg @@ -123,7 +123,7 @@ Coverage Coverage - 8.5%8.5% + 1.5%1.5% diff --git a/badge_methodcoverage.svg b/badge_methodcoverage.svg index d7128d523..7fd81285a 100644 --- a/badge_methodcoverage.svg +++ b/badge_methodcoverage.svg @@ -125,7 +125,7 @@ Coverage - 26.3%26.3% + 2.1%2.1% diff --git a/build-scripts/GBTest.cs b/build-scripts/GBTest.cs new file mode 100644 index 000000000..2ea8b7be5 --- /dev/null +++ b/build-scripts/GBTest.cs @@ -0,0 +1,146 @@ +#!/usr/bin/env dotnet + +using System.Diagnostics; +using System.Runtime.CompilerServices; + +bool cover = false; +string config = "Release"; +string? filter = null; + +for (int i = 0; i < args.Length; i++) +{ + switch (args[i].ToLowerInvariant()) + { + case "--cover": + cover = true; + break; + case "-c": + case "--configuration": + if (i + 1 < args.Length) + { + config = args[++i]; + } + break; + case "-f": + case "--filter": + if (i + 1 < args.Length) + { + filter = args[++i]; + } + break; + } +} + +string scriptsDir = GetScriptsDirectory(); + +string testProjectDir = Path.GetFullPath( + Path.Combine(scriptsDir, "..", "test", "dymaptic.GeoBlazor.Core.Test.Automation")); + +string testProjectFilePath = Path.Combine(testProjectDir, "dymaptic.GeoBlazor.Core.Test.Automation.csproj"); + +List buildArgs = +[ + "--project", testProjectFilePath, + "-c", config +]; + +if (filter != null) +{ + buildArgs = [..buildArgs, "--filter", filter]; +} + +Dictionary? environmentVariables = null; + +if (cover) +{ + environmentVariables = new Dictionary + { + ["COVER"] = "true" + }; +} + +await RunDotnetCommandWithOutputAsync(testProjectDir, "run", buildArgs, environmentVariables); +/// +/// Runs a dotnet command and captures both stdout and stderr output. +/// Output is written to the console in real-time. +/// +/// The working directory for the command. +/// The dotnet command (e.g., "build", "restore"). +/// The arguments to pass to the command. +/// Optional environment variables to set for the process. +/// The exit code of the process. +static async Task RunDotnetCommandWithOutputAsync(string workingDirectory, + string command, IEnumerable args, Dictionary? environmentVariables) +{ + var output = new List(); + + var psi = new ProcessStartInfo + { + FileName = "dotnet", + Arguments = $"{command} {string.Join(" ", args.Where(a => !string.IsNullOrWhiteSpace(a)))}", + WorkingDirectory = workingDirectory, + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + CreateNoWindow = true + }; + + if (environmentVariables != null) + { + foreach (var kvp in environmentVariables) + { + psi.Environment[kvp.Key] = kvp.Value; + } + } + + using var process = Process.Start(psi); + if (process == null) + { + return 1; + } + + process.OutputDataReceived += (_, e) => + { + if (e.Data != null) + { + Console.WriteLine(e.Data); + } + }; + + process.ErrorDataReceived += (_, e) => + { + if (e.Data != null) + { + Console.WriteLine(e.Data); + } + }; + + process.BeginOutputReadLine(); + process.BeginErrorReadLine(); + await process.WaitForExitAsync(); + + return process.ExitCode; +} + +/// +/// Gets the directory containing the build scripts. +/// When running as a .cs file, uses [CallerFilePath]. When running as a compiled DLL, +/// calculates the path relative to the DLL location. +/// +/// Automatically populated with the source file path at compile time. +/// The absolute path to the build-scripts directory. +static string GetScriptsDirectory([CallerFilePath] string? callerFilePath = null) +{ + // When running as a pre-compiled DLL, [CallerFilePath] contains the compile-time path + // which is invalid at runtime (especially in Docker containers). + // Detect this by checking if the file exists at the caller path. + if (!string.IsNullOrEmpty(callerFilePath) && File.Exists(callerFilePath)) + { + return Path.GetDirectoryName(callerFilePath)!; + } + + // Running as a DLL - use AppContext.BaseDirectory which points to the DLL location + // The DLL is in build-tools/, and scripts are in build-scripts/ (sibling directory) + string dllDirectory = AppContext.BaseDirectory.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); + return Path.Combine(Path.GetDirectoryName(dllDirectory)!, "build-scripts"); +} \ No newline at end of file diff --git a/build-scripts/GeoBlazorBuild.cs b/build-scripts/GeoBlazorBuild.cs index 9c5a2faad..806ba94d6 100644 --- a/build-scripts/GeoBlazorBuild.cs +++ b/build-scripts/GeoBlazorBuild.cs @@ -226,9 +226,6 @@ try { - // Set environment variable - Environment.SetEnvironmentVariable("PipelineBuild", "true"); - string version = customVersion ?? ""; bool customVersionSet = !string.IsNullOrEmpty(customVersion); @@ -237,7 +234,7 @@ WriteStepHeader(step, "Cleaning old build artifacts"); await RunDotnetCommand(coreProjectPath, "clean", - $"\"{Path.Combine(coreProjectPath, "dymaptic.GeoBlazor.Core.csproj")}\"", "/p:PipelineBuild=true"); + $"\"{Path.Combine(coreProjectPath, "dymaptic.GeoBlazor.Core.csproj")}\""); DeleteDirectoryIfExists(Path.Combine(coreProjectPath, "bin")); DeleteDirectoryIfExists(Path.Combine(coreProjectPath, "obj")); DeleteDirectoryContentsIfExists(Path.Combine(coreProjectPath, "wwwroot", "js")); @@ -246,7 +243,7 @@ await RunDotnetCommand(coreProjectPath, "clean", if (pro) { await RunDotnetCommand(proProjectPath, "clean", - $"\"{Path.Combine(proProjectPath, "dymaptic.GeoBlazor.Pro.csproj")}\"", "/p:PipelineBuild=true"); + $"\"{Path.Combine(proProjectPath, "dymaptic.GeoBlazor.Pro.csproj")}\""); DeleteDirectoryIfExists(Path.Combine(proProjectPath, "bin")); DeleteDirectoryIfExists(Path.Combine(proProjectPath, "obj")); DeleteDirectoryContentsIfExists(Path.Combine(proProjectPath, "obf")); @@ -376,7 +373,7 @@ await RunDotnetCommand(validatorProjectPath, "clean", stepStartTime = DateTime.Now; WriteStepHeader(step, "Restoring .NET Packages"); - await RunDotnetCommand(coreProjectPath, "restore", "/p:PipelineBuild=true"); + await RunDotnetCommand(coreProjectPath, "restore"); WriteStepCompleted(step, stepStartTime); step++; @@ -391,7 +388,6 @@ await RunDotnetCommand(validatorProjectPath, "clean", $"--no-restore", "-c", configuration, - $"/p:PipelineBuild=true", $"/p:GenerateDocs={generateDocs.ToString().ToLower()}", $"/p:GenerateXmlComments={generateXmlComments.ToString().ToLower()}", $"/p:CoreVersion={version}", @@ -471,7 +467,7 @@ await RunDotnetCommand(validatorProjectPath, "clean", stepStartTime = DateTime.Now; WriteStepHeader(step, "Restoring .NET Packages"); - await RunDotnetCommand(proProjectPath, "restore", "/p:PipelineBuild=true"); + await RunDotnetCommand(proProjectPath, "restore"); WriteStepCompleted(step, stepStartTime); step++; @@ -619,7 +615,6 @@ await RunDotnetCommand(validatorProjectPath, "clean", "--no-restore", $"-c", configuration, - $"/p:PipelineBuild=true", $"/p:GenerateDocs={generateDocs.ToString().ToLower()}", $"/p:GenerateXmlComments={generateXmlComments.ToString().ToLower()}", $"/p:CoreVersion={version}", diff --git a/build-scripts/GeoBlazorCover.cs b/build-scripts/GeoBlazorCover.cs deleted file mode 100644 index 656586009..000000000 --- a/build-scripts/GeoBlazorCover.cs +++ /dev/null @@ -1,58 +0,0 @@ -#!/usr/bin/env dotnet - - - -/// -/// Runs a dotnet command and captures both stdout and stderr output. -/// Output is also written to the console in real-time. -/// -/// The working directory for the command. -/// The dotnet command (e.g., "build", "restore"). -/// Additional arguments to pass to the command. -/// A tuple containing the exit code and a list of all output lines. -static async Task<(int ExitCode, List Output)> RunDotnetCommandWithOutputAsync(string workingDirectory, - string command, params string[] args) -{ - var output = new List(); - - var psi = new ProcessStartInfo - { - FileName = "dotnet", - Arguments = $"{command} {string.Join(" ", args.Where(a => !string.IsNullOrWhiteSpace(a)))}", - WorkingDirectory = workingDirectory, - RedirectStandardOutput = true, - RedirectStandardError = true, - UseShellExecute = false, - CreateNoWindow = true - }; - - using var process = Process.Start(psi); - if (process == null) - { - return (1, ["Failed to start dotnet"]); - } - - process.OutputDataReceived += (_, e) => - { - if (e.Data != null) - { - Console.WriteLine(e.Data); - output.Add(e.Data); - } - }; - - process.ErrorDataReceived += (_, e) => - { - if (e.Data != null) - { - Console.WriteLine(e.Data); - output.Add(e.Data); - } - }; - - process.BeginOutputReadLine(); - process.BeginErrorReadLine(); - await process.WaitForExitAsync(); - - return (process.ExitCode, output); -} \ No newline at end of file diff --git a/build-scripts/ScriptBuilder.cs b/build-scripts/ScriptBuilder.cs index 0662d2fbb..0b2cd32ab 100644 --- a/build-scripts/ScriptBuilder.cs +++ b/build-scripts/ScriptBuilder.cs @@ -28,14 +28,14 @@ bool excludeMode = false; HashSet scriptsToProcess = new(); -string scriptDir = GetScriptDirectory(); -string coreDir = Path.GetFullPath(Path.Combine(scriptDir, "..")); +string scriptsDir = GetScriptsDirectory(); +string coreDir = Path.GetFullPath(Path.Combine(scriptsDir, "..")); string outDir = Path.GetFullPath(Path.Combine(coreDir, "build-tools")); Trace.Listeners.Add(new TextWriterTraceListener(Console.Out)); Trace.WriteLine("Starting ScriptBuilder..."); -string[] scripts = Directory.GetFiles(scriptDir, "*.cs"); +string[] scripts = Directory.GetFiles(scriptsDir, "*.cs"); bool force = false; for (int i = 0; i < args.Length; i++) @@ -61,7 +61,7 @@ string currentBranch = GetCurrentGitBranch(coreDir); if (!force) { - if (!CheckIfNeedsBuild(recordFile, currentBranch, scriptDir, outDir, scripts)) + if (!CheckIfNeedsBuild(recordFile, currentBranch, scriptsDir, outDir, scripts)) { return 0; } @@ -88,7 +88,7 @@ } } - int returnCode = BuildScript(Path.GetFileName(script), scriptDir, outDir); + int returnCode = BuildScript(Path.GetFileName(script), scriptsDir, outDir); if (returnCode != 0) { return returnCode; @@ -105,10 +105,10 @@ /// Compiles a single C# script to a DLL using 'dotnet build'. /// /// The name of the script file (e.g., "ESBuild.cs"). -/// The directory containing the script. +/// The directory containing the script. /// The output directory for the compiled DLL. /// 0 on success, non-zero on failure. -static int BuildScript(string scriptName, string scriptDir, string outDir) +static int BuildScript(string scriptName, string scriptsDir, string outDir) { string[] args = [ @@ -124,7 +124,7 @@ static int BuildScript(string scriptName, string scriptDir, string outDir) { FileName = "dotnet", Arguments = string.Join(" ", args), - WorkingDirectory = scriptDir, + WorkingDirectory = scriptsDir, RedirectStandardInput = true, RedirectStandardOutput = true, RedirectStandardError = true, @@ -163,14 +163,26 @@ static int BuildScript(string scriptName, string scriptDir, string outDir) /// -/// Gets the directory containing this script file. -/// Uses [CallerFilePath] to resolve the path at compile time. +/// Gets the directory containing the build scripts. +/// When running as a .cs file, uses [CallerFilePath]. When running as a compiled DLL, +/// calculates the path relative to the DLL location. /// -/// Automatically populated with the source file path. -/// The directory path containing this script. -static string GetScriptDirectory([CallerFilePath] string? callerFilePath = null) +/// Automatically populated with the source file path at compile time. +/// The absolute path to the build-scripts directory. +static string GetScriptsDirectory([CallerFilePath] string? callerFilePath = null) { - return Path.GetDirectoryName(callerFilePath) ?? Environment.CurrentDirectory; + // When running as a pre-compiled DLL, [CallerFilePath] contains the compile-time path + // which is invalid at runtime (especially in Docker containers). + // Detect this by checking if the file exists at the caller path. + if (!string.IsNullOrEmpty(callerFilePath) && File.Exists(callerFilePath)) + { + return Path.GetDirectoryName(callerFilePath)!; + } + + // Running as a DLL - use AppContext.BaseDirectory which points to the DLL location + // The DLL is in build-tools/, and scripts are in build-scripts/ (sibling directory) + string dllDirectory = AppContext.BaseDirectory.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); + return Path.Combine(Path.GetDirectoryName(dllDirectory)!, "build-scripts"); } /// diff --git a/build-tools/BuildAppSettings.dll b/build-tools/BuildAppSettings.dll index 8421b5db22803f71dd3e8de2563ad1e7dc50ce32..173144457da87126586a127d8392024d5d801b57 100644 GIT binary patch delta 235 zcmZn&Xb70l!4j~%|G~x{18D)X4W|E|aeeXGZPD}a_>D;mH=mJiXVfq?N=mXYOtr8y zuuL&dvota=O*Tn3Gc-3cvam1&BEzIaqf}!P3?@Kq22^1V6fpv-0;z_OhCscEK(SPy4ig6B&D)hd FSpW&^OSu35 delta 235 zcmZn&Xb70l!Ls(?%ZD3#45S6Z#b53-)U$Ei96xEDXq~a$<}=dmj2e~}#wMw0rj{uN zX$F=?$(Cs*sU~S=mO!y21M}20GlNtE152}%q{-E?vMe42)z2o+kqr>|UtKZP@7hZ5 zMLqA1zNl7Cn*2w0vjS9b_9LhuNcFv+*-GkDOZPb)zG1)lyxdF{e@g}n24e;jhE#?$ z22%!0h7<+^Aj<&AGGa&ul4)Q$6CiB{3m; diff --git a/build-tools/BuildAppSettings.exe b/build-tools/BuildAppSettings.exe index 229401beb13769fcd7c711135ca987aac8a19c6c..c218c5fe0b19467fa5017cf2eb6f94da5db789ab 100644 GIT binary patch delta 99 zcmZqp!r1^sEsR^3BFgKpiFw#@m;dGet510RLGM2><{9 delta 99 zcmZqp!r1^sEsR^3BFgb8PXU`87vu67z}_c10c(YAsI-ff#pnqv>A{G dQI`aiHwWu51F}+q7^L0|s5fc*@^Yp~CIBaH6GH$1 diff --git a/build-tools/BuildTemplates.dll b/build-tools/BuildTemplates.dll index 7bb7243861de2a7912434659a4bb2583b59e3c23..b8ee8b033420c64123ad40fcb3fa76adccbea5d8 100644 GIT binary patch delta 237 zcmZoT!q{+xaY6^nhZE%sHum)R3&>nOJ@3oZ8uzt3_h_GaVWhuVBOr!V!_+7#$-*$z z!qUJp#W>B<$iOt&B-zZ++{nnn!VriIlM;)mJ9|!oWfwtkOriUfIL%%WCjx;HUp|K2Z|U0Re@APNJF6BM4(tIP=^VF@#e*` Gu`B@2p-e9T delta 237 zcmZoT!q{+xaY6@+-R>@S3-BqOnz!(3mGfL%*J6e>of|f51jMjvSXvmHq^6l# zrWm9dSQ;f;rkSLgq?uU)#gYunQ`5{0QVk3&%~FykuL(WD@~PhS_2i1M0D%QhS?&w4 z`Z~-zefCVfooejl8)54epn?nHpn^cv*$lTbm9FqUKpiFw#@i>CGvzV?0Qfx-^#A|> delta 99 zcmZqp!r1^sEsR^3T+00|87vr#8B7>b8PXU`87vu67z}_c10c(YAsI-ff#pnqv>A{G dQI`aiHwWu51F}+q7^L0|s5fc*OOj4M7(C(yD zhrTFJ+vHn%lS`D=DnJE=V1hu^34T2a%dWC<@CkGtDctO+{GZw1l);E02?#A13>i{^ zq$Ps^5T`I0Go%4&BOuR|A(_Dhh|Pd1%z+|CKvf{s5YiB+HxVe73e;i3V7$3sy^sX} DbazDL delta 235 zcmZpOXo#55!E%z-{LIFl4RQjyjJH!JoAX6b!>n||hOqpJSkpaOL@7Yvjm(cw`#=BP z_6BTT=oSTdwA7ywxYK$a0hGLTFI%b5UaGawJ5E(s`a4%T4?WTgTzNWB?QZ_?&|^+FZ^ Da$8VN diff --git a/build-tools/ConsoleDialog.exe b/build-tools/ConsoleDialog.exe index 1ec1433de9a541d7744f00ce9a805a17617d2965..4c4832dc73dcc995d2ef3eecd60081cb7f01f243 100644 GIT binary patch delta 99 zcmZqp!r1^sEsR^349fjY8H^Z`fY5@$kRcUFS~3^_aSDSmLmH4a0`g25k{L{Z*bJz` f94KN0R0UEEAq|0g6MKpiFw#@p-5nHre@@gWi6 delta 99 zcmZqp!r1^sEsR^349fj287vr#8B7>b8PXU`87vu67z}_c10c(YAsI-ff#pnqv>A{G dQI`aiHwWu51F}+q7^L0|s5fbQeK}Jj695@D6B_^k diff --git a/build-tools/ESBuild.dll b/build-tools/ESBuild.dll index 70d2889cf5721450e928be97a6182d0c248957bd..297d00f20923b4cf84a0f709b27e1e88bec250eb 100644 GIT binary patch delta 237 zcmZpe!Pqc^aY6^n?Fu%(u(4v%Ff~d_vM@}w zur#ntF;25IGB8axNj5VyH!`xYFa#pQq(q}sV-w@aUXE2PY0KZ=nS8=AKw$l1og>^_ zPkm>FH?G|1|x)mJ9|!oWfwtkOriUfIL%%WCjx;HUp|K2Z|U0Re@APNJF6BM4(tIP=^VF@#bLn GwJZR2=1q#77lnx!O7_HwLZagBR>YVrxk0D)DJeX^zR zD;?I~U3Euz_Un|%8cquppn?VOp@KlwUfU+G+IVmK;?4c*mu}nq(n*@d-;%+C!I;5> zA(bJG!IZ(0A%(#J$T9%3j2M!EWExn`1W20!c@T9;KzVbp4l^Jt6^KFV&47B7HV3<} GWdQ)@2~eg0 diff --git a/build-tools/ESBuild.exe b/build-tools/ESBuild.exe index 642aa0954e1a7ed5aad7fd4620b7758314092173..10f1dae38abb968de6eef6566f90061820cfb5b6 100644 GIT binary patch delta 99 zcmZqp!r1^sEsR^3zLfc!G8i!=0igwhAww#Vv}7;<;uHpBhBP2;1mu}ABr})*u^CW> gIZ(t1s0ySSLK*_~CIZD$fjUeWjJG?NGwo#p05gRWqyPW_ delta 99 zcmZqp!r1^sEsR^3zLfb}GFUJeGng=>GNdt>GFURCFc<(?20)e(Lo$#|1Iw8JX)_=X dqAm$2Zw}UB24tlIF-W}`P;b(9=W?dKOaNPp6W#y- diff --git a/build-tools/ESBuildClearLocks.dll b/build-tools/ESBuildClearLocks.dll index 0db7ca9493d113ead7992123066b240b098a946d..d6995b137b23ed44679a9dbe3e0efc0c3dbffdde 100644 GIT binary patch delta 235 zcmZp0XmFU&!E)nA06^B_&xHrdn7U zSf&`KSsEFbCYvOi8JZgzSy&hXkzrDzQL3?t@nkbGBNo-3IkzU~hy@7vg`alypPcW! zgZG88r>EoD$!Em2DnJF3E1-fv)lv4-gKPt*+<25Y@qg{+h2qDV{Y@E+7?Oa{g29j> z6-Zh#7yxk!gE2!IkTwGHOc|0HOn}%7sKOj5VgytLQVk&ufqD~xVyQqKCJe@#w@Z7n F005mXMdAPe delta 235 zcmZp0XmFU&!Lo44^)(xNN`wV&em}BYezSncKSm8p3uBYiG*inI zgERw6qh!l8lT?#5GfSXYl7V?@nwdeWfq|u2O44LAF(Ve^36M4e@*wJxfb!;G9cDmQDiDLzn*sGEZQd^J$pQc_ Cl2B9t diff --git a/build-tools/ESBuildClearLocks.exe b/build-tools/ESBuildClearLocks.exe index 659e4ba9774651c14fb24d18c78f854cae11b11b..44b48352369764b8d70a1655326d82375b5285af 100644 GIT binary patch delta 99 zcmZqp!r1^sEsR^3BFgKpiFw#@m;dGet510RLGM2><{9 delta 99 zcmZqp!r1^sEsR^3BFgb8PXU`87vu67z}_c10c(YAsI-ff#pnqv>A{G dQI`aiHwWu51F}+q7^L0|s5fc*@^Yp~CIBaH6GH$1 diff --git a/build-tools/ESBuildWaitForCompletion.dll b/build-tools/ESBuildWaitForCompletion.dll index e613ff689f35756c0692452f431f9c8e749dca46..0f1302315cb07aaedc69bbb8fe306d411f7fe38a 100644 GIT binary patch delta 235 zcmZqhY4DlQ!Llk(ZTrTa0C53EwSVhw&%Ni->^{G3nsZLj<~QOO88u9el9DV8Q!OkF zEK`irER76IlTDJ%49$&X?)Q-)*)6CgGNsxSwN7y(s*R6|HZpx#8FSSnD534`%wc7>m; E0Pk%`V*mgE delta 235 zcmZqhY4DlQ!Ls`5zs8L{0pbD+L}yQVxTwlu-lAXYj-)&M+WbcRBBO?-g|SI$nyF=q zL7IW3QL<&4NvcVjnI%vx$-q1{&CDRxz`)WhC28_gNo|%Anb!*@i%10s#NX-F+vv94 zd&1+qApL1FS(8ho4l6(fcf$mMsuh;q41V@N?zVp6C7wTpTA7*ZM1 z7)%)~8B!PwfGh(b%ZMQvNTz}1On|f*kOxth1e7-i>o5bdQh^wx-VCTWX*0XRPgVe& CPE4Ty diff --git a/build-tools/ESBuildWaitForCompletion.exe b/build-tools/ESBuildWaitForCompletion.exe index cfbaa34be684e8d1d7fffb67dc698307887a7187..f73c139978c5f47deaebbd326c471c66a4a75dc5 100644 GIT binary patch delta 99 zcmZqp!Px*rEsR^3R+RgjG8i!=0igwhAww#Vv}7;<;uHpBhBP2;1mu}ABr})*u^CW> gIZ(t1s0ySSLK*_~CIZD$fjUeWjJJO;XX0c503O~FV*mgE delta 99 zcmZqp!Px*rEsR^3R+Rf&GFUJeGng=>GNdt>GFURCFc<(?20)e(Lo$#|1Iw8JX)_=X dqAm$2Zw}UB24tlIF-W}`P;b)q@8wLKEC5F26P*A6 diff --git a/build-tools/FetchNuGetVersion.dll b/build-tools/FetchNuGetVersion.dll index a2829b8ce5de30a116f16332e1240d11c1a9ade2..78b34233ffd92c35a0129557e02dcd36b40793b3 100644 GIT binary patch delta 235 zcmZqhXz-ZO!4j(ze|lrj5fOpoXAJ|yxLmz=uFp-aJaX^<<^<7)Od6&}Nl6xlsTP(7 zmMO+*mPQ7q$tKBWhUP{_78Zs;WSEp_lxl2ZJULS$n&slHSt}=>kO&Z9^w{^|>gtJ3 zy>69T78}nkovb0bRRJo<#0wP!slMS~`CD&&$(d8oxO73M$@BcLjfY6xiv)SCztO9kpMVKCmjUEY%g E0PVj@^#A|> delta 235 zcmZqhXz-ZO!IFDR;nc>SBO(Gj_e}}>IA^2hs%3KXf|TYw+ngZ!kV(VR!q_A=&D1i* zAkDziDA_X2B-JF%%n~S;WMH0}W@eCTU|?yMk~BF}BAR8ld-v(dCnN#{zTNWk`F~N- zab@M+?!{+r&YP?uxm5uw7?=qa1gSow`9d*Z_a`0yy?XYW|4L@F`dczsFc>qKFr+f1 zF_w CSWnsj diff --git a/build-tools/FetchNuGetVersion.exe b/build-tools/FetchNuGetVersion.exe index 47f543af2d796eaa3ece54dd6b320e5519b0ac68..95ea93dfb5964be9968d396e578f8cfa1282f185 100644 GIT binary patch delta 99 zcmZqp!r1^sEsR^3BFgKpiFw#@m;dGet510RLGM2><{9 delta 99 zcmZqp!r1^sEsR^3BFgb8PXU`87vu67z}_c10c(YAsI-ff#pnqv>A{G dQI`aiHwWu51F}+q7^L0|s5fc*@^Yp~CIBaH6GH$1 diff --git a/build-tools/GBTest.dll b/build-tools/GBTest.dll new file mode 100644 index 0000000000000000000000000000000000000000..cd0906688f64a9f947c86f023248abcb6b88d636 GIT binary patch literal 10752 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 I|D_E4H|W@!G5`Po literal 0 HcmV?d00001 diff --git a/build-tools/GBTest.exe b/build-tools/GBTest.exe new file mode 100644 index 0000000000000000000000000000000000000000..529689071a9cb7bb610b4268228c455e686efa74 GIT binary patch literal 162304 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

-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 delta 6941 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 Wd}lKpiFw#@i>CGvzV?0Qfx-^#A|> delta 99 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

Qg6E`>MG1C|# zil^__ro#y^c?K;PHru}ZL}oqV&3?d2MBe4F6UegLd19?pV|FAhhG&wF&2^41S+^U+ z*^SRjQRl&w1=hjKiJ+dwRhL3!$$sJlkLA@A!&SMiNQJFrER*BI;4@NB(vZIi-)cBb zwp{~KI7>FJ7J0!Zhfc`Yx1V<|4XXE$#x?$?qg0~OTz0FYn@xT7-na#~BsR#YyO(U3 zf3(}11Bz|7dPZ&a==xYOO~l{JzwvD&Ow{>3N8DlI#+Fc;@bGm+!yQ+rr9&r8LG(cp zzcb)RE>wmw1k@JK&N?{*8-5`rB%#jl`5LDB*V*_cQLg%4dEC;@fFk^qhxrFg zDW!%YgT?4ivsesVvrc$D`)RKNQqB<(jUN#+Vy>6}U0AO_uGlFkx^8!7)57201ve7{ zDA-VMc{#`361Kf*F-5TYnyFxMJ}Z8w3J5~@>RuKTKEn07L@t>M&VmtMuNU>)Yp&p8 zDfFN#^xr;#4~MM5z)JD3|2-miQ_bKjD{MMAdd2M?jv6Bh=c*3qoJn^FHqU>J-uG|@ zMXEOR&lqZC=D3^Txu%aQXZdSN`}a5u>I9MJFo-*ZS&54g#&8%sz6ZNE*42`4p9Uo_ zDbrD;P8IC`-@_QYo)B64Xp%?Pq)0^zdI#@49mZgP4qOaD(4-rc7ecgrAQ%B;tEQa; z@!ncoyjFRxgq-|Xo@%@tn$8m7EjLt`ZiFiTXZDw7hXOK>w{^Zx32+s2wI8m)wCFImVF* z)n|@#Jw^h~*Z+fYVzCMs%d~r*1LC<~Qy$;}enpEhd2-N7(rZ%Seg766im#A=7}MJ? zUG^?RM(2pLr)KYOvG;;2`vVh>;l0RMOhrz?F3(H4H}|KZp_uD>N%wZWq*3SQdP#Gc zjn+uRp@xD$4$=G~LqRvsw-T37z@z5=>qeiR zUYvz|gE{wkzzhGS;}i8@%rcF3l%QDenwUnh?K?+coFBOh+GFg!qvV9pbihC;n;C+v zXApz^bE4swxYuA`hF>~Iy2vcQ^t@Auh8_BI<)itY`V^UAvvUxEg_K`XH7J8e*vB~j zX!8_yrpu=N(RetWT+O#A+?f28F02IG=6+%*BTJ`jJnyQikqZo2~( zW6yJJ7DO&j`u6{6j#G76I@lmOxRCcRpo5t?*8CdzgGIIgA;bvl@J%ZrlfaJRz6}e# zIj=j%oPLOGMmf&*+UhDNGkWphis8ziEISE7!+GHc>2zydE2N4lI0K-t88~5A;>=H! z%G0?Jn<$l>7QmHP+%4p!2EKnvaK#9>lFv?}l3_MbL`6O?b&SzX4B{Km+{rgLL?z

Kkbb@Py{U%=eWpQQ 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 Tzdze}nG;^=cY7fH5B~dacqQK? literal 0 HcmV?d00001 diff --git a/src/dymaptic.GeoBlazor.Core.SourceGenerator/ESBuildGenerator.cs b/src/dymaptic.GeoBlazor.Core.SourceGenerator/ESBuildGenerator.cs index f8924b4e8..2e199fc3a 100644 --- a/src/dymaptic.GeoBlazor.Core.SourceGenerator/ESBuildGenerator.cs +++ b/src/dymaptic.GeoBlazor.Core.SourceGenerator/ESBuildGenerator.cs @@ -46,12 +46,6 @@ public void Initialize(IncrementalGeneratorInitializationContext context) options["Configuration"] = configuration; } - if (configProvider.GlobalOptions.TryGetValue("build_property.PipelineBuild", - out string? pipelineBuild)) - { - options["PipelineBuild"] = pipelineBuild; - } - if (configProvider.GlobalOptions.TryGetValue("build_property.DesignTimeBuild", out string? designTimeBuild)) { @@ -82,14 +76,12 @@ private void FilesChanged(SourceProductionContext context, return; } - if (pipeline.Options.TryGetValue("PipelineBuild", out string? pipelineBuild) - && bool.TryParse(pipelineBuild, out bool pipelineBuildBool) - && pipelineBuildBool) + if (!_isDesignTimeBuild) { - // If the pipeline build is enabled, we skip the ESBuild process. - // This is to avoid race conditions where the files are not ready on time, and we do the build separately. + // If this is a full compilation, we call ESBuild directly from Core.csproj earlier in the process, + // so we want to exit out here. ProcessHelper.Log(nameof(ESBuildGenerator), - "Skipping ESBuild process as PipelineBuild is set to true.", + "ESBuild Source Generation skipped during full compilation.", DiagnosticSeverity.Info, context); @@ -109,6 +101,7 @@ private void FilesChanged(SourceProductionContext context, if (_showDialog) { ProcessHelper.CloseDialog(_sessionId); + ProcessHelper.CloseDialog(_proSessionId); } } @@ -119,11 +112,6 @@ private bool SetProjectDirectoryAndConfiguration(Dictionary opti { _corePath = Path.GetFullPath(projectDirectory); - ProcessHelper.Log(nameof(ESBuildGenerator), - $"Project directory set to {_corePath}", - DiagnosticSeverity.Info, - context, _showDialog, _sessionId); - if (_corePath.Contains("GeoBlazor.Pro")) { // we are inside the Pro submodule, we should also set the Pro path to build the Pro JavaScript files @@ -213,7 +201,7 @@ await ProcessHelper.Execute("Core", ProcessHelper.Log(nameof(ESBuildGenerator), "Starting Pro ESBuild process...", DiagnosticSeverity.Info, - context, _showDialog, _sessionId); + context, _showDialog, _proSessionId); string[] proArgs = [..esBuildArgs, "--pro"]; @@ -221,7 +209,7 @@ await ProcessHelper.Execute("Core", { await ProcessHelper.Execute("Pro", BuildToolsPath!, "dotnet", - proArgs, context, _showDialog, _sessionId); + proArgs, context, _showDialog, _proSessionId); proBuildSuccess = true; })); } @@ -245,7 +233,7 @@ await ProcessHelper.Execute("Pro", ProcessHelper.Log(nameof(ESBuildGenerator), "Pro ESBuild process failed", DiagnosticSeverity.Error, - context, _showDialog, _sessionId); + context, _showDialog, _proSessionId); } } } @@ -283,4 +271,5 @@ private void ClearESBuildLocks(SourceProductionContext context) // Generate a unique session ID for this build session private readonly string _sessionId = $"{nameof(ESBuildGenerator)}_{Guid.NewGuid():N}"; + private readonly string _proSessionId = $"{nameof(ESBuildGenerator)}_{Guid.NewGuid():N}"; } \ No newline at end of file diff --git a/src/dymaptic.GeoBlazor.Core.SourceGenerator/ProtobufSourceGenerator.cs b/src/dymaptic.GeoBlazor.Core.SourceGenerator/ProtobufSourceGenerator.cs index 4478948c0..849723747 100644 --- a/src/dymaptic.GeoBlazor.Core.SourceGenerator/ProtobufSourceGenerator.cs +++ b/src/dymaptic.GeoBlazor.Core.SourceGenerator/ProtobufSourceGenerator.cs @@ -43,10 +43,10 @@ syntaxNode is ClassDeclarationSyntax or StructDeclarationSyntax or RecordDeclara options["CoreProjectPath"] = projectDirectory; } - if (configProvider.GlobalOptions.TryGetValue("build_property.PipelineBuild", - out var pipelineBuild)) + if (configProvider.GlobalOptions.TryGetValue("build_property.DesignTimeBuild", + out var designTimeBuild)) { - options["PipelineBuild"] = pipelineBuild; + options["DesignTimeBuild"] = designTimeBuild; } if (configProvider.GlobalOptions.TryGetValue("build_property.ShowSourceGenDialogs", @@ -70,15 +70,15 @@ private void FilesChanged(SourceProductionContext context, pipeline) { pipeline.Options.TryGetValue("CoreProjectPath", out _corePath); - pipeline.Options.TryGetValue("PipelineBuild", out string? pipelineBuildString); + pipeline.Options.TryGetValue("DesignTimeBuild", out string? designTimeBuildString); - bool pipelineBuild = pipelineBuildString is not null - && bool.TryParse(pipelineBuildString, out bool pipelineBuildBool) - && pipelineBuildBool; + bool designTimeBuild = designTimeBuildString is not null + && bool.TryParse(designTimeBuildString, out bool designTimeBuildBool) + && designTimeBuildBool; pipeline.Options.TryGetValue("ShowSourceGenDialogs", out string? showDialogString); - bool showDialog = !pipelineBuild + bool showDialog = designTimeBuild && showDialogString is not null && bool.TryParse(showDialogString, out bool showDialogBool) && showDialogBool; diff --git a/src/dymaptic.GeoBlazor.Core/Scripts/arcGisJsInterop.ts b/src/dymaptic.GeoBlazor.Core/Scripts/arcGisJsInterop.ts index b3dd51765..48068fef2 100644 --- a/src/dymaptic.GeoBlazor.Core/Scripts/arcGisJsInterop.ts +++ b/src/dymaptic.GeoBlazor.Core/Scripts/arcGisJsInterop.ts @@ -44,8 +44,6 @@ import Polyline from "@arcgis/core/geometry/Polyline"; import Popup from "@arcgis/core/widgets/Popup"; import PopupTemplate from "@arcgis/core/PopupTemplate"; import Portal from "@arcgis/core/portal/Portal"; -import * as promiseUtils from "@arcgis/core/core/promiseUtils"; -import * as projectionEngine from "@arcgis/core/geometry/projection"; import Query from "@arcgis/core/rest/support/Query"; import RasterStretchRenderer from "@arcgis/core/renderers/RasterStretchRenderer"; import RouteParameters from "@arcgis/core/rest/support/RouteParameters"; @@ -112,8 +110,7 @@ export { buildJsGraphic, buildJsSymbol, reactiveUtils, - geometryEngine, - projectionEngine + geometryEngine }; export const popupTemplateRefs: Record = {}; @@ -230,8 +227,6 @@ export async function buildArcGisMapView(abortSignal: AbortSignal, id: string, d loadProtobuf(); } - await projectionEngine.load(); - checkConnectivity(id); if (abortSignal.aborted) { diff --git a/src/dymaptic.GeoBlazor.Core/badge_fullmethodcoverage.svg b/src/dymaptic.GeoBlazor.Core/badge_fullmethodcoverage.svg index f25f77942..2a5dcdcd8 100644 --- a/src/dymaptic.GeoBlazor.Core/badge_fullmethodcoverage.svg +++ b/src/dymaptic.GeoBlazor.Core/badge_fullmethodcoverage.svg @@ -1,4 +1,4 @@ - +