Skip to content

Commit b04af81

Browse files
authored
Merge pull request #236 from Tronald/develop
2.23.1.1
2 parents 0e93934 + da6deab commit b04af81

File tree

8 files changed

+1062
-23
lines changed

8 files changed

+1062
-23
lines changed

CoordinateSharp.Magnetic/CoordinateSharp.Magnetic.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ For more information, please contact Signature Group, LLC at this address: sales
4747
<TargetFrameworks>net40; netstandard1.3; netstandard1.4; netstandard2.0; netstandard2.1; net50; net60; net70; net80</TargetFrameworks>
4848
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
4949
<GenerateDocumentationFile>true</GenerateDocumentationFile>
50-
<Version>1.1.12.0</Version>
50+
<Version>1.1.13.0</Version>
5151
<Authors>Signature Group, LLC</Authors>
5252
<Company />
5353
<PackageProjectUrl>https://github.com/Tronald/CoordinateSharp</PackageProjectUrl>
@@ -61,7 +61,7 @@ For more information, please contact Signature Group, LLC at this address: sales
6161
<PackageIconUrl></PackageIconUrl>
6262
<PackageId>CoordinateSharp.Magnetic</PackageId>
6363
<Title>CoordinateSharp.Magnetic</Title>
64-
<AssemblyVersion>1.1.12.0</AssemblyVersion>
64+
<AssemblyVersion>1.1.13.0</AssemblyVersion>
6565
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
6666
<SignAssembly>true</SignAssembly>
6767
<PackageIcon>128x128.png</PackageIcon>

CoordinateSharp/Celestial/Solar/SunCalculations.cs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -104,11 +104,7 @@ public static void CalculateSunTime(double lat, double lng, DateTime date, Celes
104104
{
105105
// No sunset this date
106106
c.sunCondition = CelestialStatus.NoSet;
107-
}
108-
else
109-
{
110-
111-
}
107+
}
112108
}
113109

114110
//Sat day and night time spans within 24 hours period

CoordinateSharp/CoordinateSharp.csproj

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,26 +50,26 @@ Please visit http://coordinatesharp.com/licensing or contact Signature Group, LL
5050
<TargetFrameworks>net40; netstandard1.3; netstandard1.4; netstandard2.0; netstandard2.1; net50; net60; net70; net80</TargetFrameworks>
5151
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
5252
<GenerateDocumentationFile>true</GenerateDocumentationFile>
53-
<Version>2.22.1.1</Version>
53+
<Version>2.23.1.1</Version>
5454
<Authors>Signature Group, LLC</Authors>
5555
<Company />
5656
<PackageProjectUrl>https://github.com/Tronald/CoordinateSharp</PackageProjectUrl>
5757
<PackageLicenseUrl></PackageLicenseUrl>
5858
<Copyright>Copyright 2024</Copyright>
5959
<Description>CoordinateSharp is a high powered, lightweight .NET library that can convert geographical coordinates, perform distance logic, and calculate location based sun, moon, and magnetic information with minimal code.</Description>
60-
<PackageReleaseNotes>-Adds Day and Night TimeSpan properties to the CelestialInfo class for easier calculation of total day and night hours for a date at a specified location.</PackageReleaseNotes>
60+
<PackageReleaseNotes>-Adds the ability to densify polylines to mitigate geofence spherical distortions over long distances.</PackageReleaseNotes>
6161
<PackageTags>Conversion; Latitude; Longitude; Coordinates; Geography; Sun; Moon; Solar; Lunar; Time; MGRS; UTM; EPSG:3857; ECEF; GEOREF; Web Mercator;</PackageTags>
6262
<!-- <PackageLicenseExpression>AGPL-3.0-or-later</PackageLicenseExpression>-->
6363
<PackageLicenseFile>License.txt</PackageLicenseFile> <PackageIconUrl></PackageIconUrl>
6464
<PackageId>CoordinateSharp</PackageId>
6565
<Title>CoordinateSharp</Title>
66-
<AssemblyVersion>2.22.1.1</AssemblyVersion>
66+
<AssemblyVersion>2.23.1.1</AssemblyVersion>
6767
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
6868
<SignAssembly>true</SignAssembly>
6969
<PackageIcon>128x128.png</PackageIcon>
7070
<AssemblyOriginatorKeyFile>CoordinateSharp Strong Name.snk</AssemblyOriginatorKeyFile>
7171
<DelaySign>false</DelaySign>
72-
<FileVersion>2.22.1.1</FileVersion>
72+
<FileVersion>2.23.1.1</FileVersion>
7373
<RepositoryUrl>https://github.com/Tronald/CoordinateSharp</RepositoryUrl>
7474
<PackageReadmeFile>README.md</PackageReadmeFile>
7575
<SymbolPackageFormat>snupkg</SymbolPackageFormat>

CoordinateSharp/GeoFence/GeoFence.cs

Lines changed: 154 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -42,17 +42,21 @@ or shipping CoordinateSharp with a closed source product.
4242
4343
Please visit http://coordinatesharp.com/licensing or contact Signature Group, LLC to purchase a commercial license, or for any questions regarding the AGPL 3.0 license requirements or free use license: sales@signatgroup.com.
4444
*/
45+
46+
// Ignore Spelling: Densify
47+
4548
using System.Collections.Generic;
4649
using System;
50+
using System.Linq;
4751

4852
namespace CoordinateSharp
49-
{
53+
{
5054
/// <summary>
5155
/// The GeoFence class is used to help check if points/coordinates are inside or near a specified polygon/polyline,
5256
/// </summary>
5357
[Serializable]
5458
public partial class GeoFence
55-
{
59+
{
5660
private readonly List<Point> _points = new List<Point>();
5761

5862
/// <summary>
@@ -108,7 +112,7 @@ public GeoFence(List<Coordinate> coordinates)
108112
/// </summary>
109113
public List<Point> Points
110114
{
111-
get { return _points; }
115+
get { return _points; }
112116
}
113117
private Coordinate ClosestPointOnSegment(Point a, Point b, Coordinate p, DateTime dt, EagerLoad eg)
114118
{
@@ -136,7 +140,8 @@ private Coordinate ClosestPointOnSegment(Point a, Point b, Coordinate p, DateTim
136140
/// </summary>
137141
/// <param name="point">Point to test</param>
138142
/// <remarks>
139-
/// Points sitting on the edge of a polygon may return true or false.
143+
/// This method utilizes 2D ray casting techniques and does not inherently account for the curvature of the Earth. To mitigate the impact of Earth shape distortion on polygons or polylines that span long distances, users should employ densification.
144+
/// Points sitting on the edge of a polygon may return true or false.
140145
/// </remarks>
141146
/// <returns>bool</returns>
142147
/// <example>
@@ -189,6 +194,9 @@ public bool IsPointInPolygon(Coordinate point)
189194
/// </summary>
190195
/// <param name="point">Point to test</param>
191196
/// <param name="range">Range in meters</param>
197+
/// <remarks>
198+
/// This method utilizes 2D ray casting techniques and does not inherently account for the curvature of the Earth. To mitigate the impact of Earth shape distortion on polygons or polylines that span long distances, users should employ densification.
199+
/// </remarks>
192200
/// <returns>bool</returns>
193201
/// <example>
194202
/// The following example shows how to determine if a coordinate is within 1000 meters of
@@ -223,7 +231,7 @@ public bool IsPointInRangeOfLine(Coordinate point, double range)
223231
if (c.Get_Distance_From_Coordinate(point).Meters <= range)
224232
return true;
225233
}
226-
234+
227235
return false;
228236
}
229237

@@ -232,6 +240,9 @@ public bool IsPointInRangeOfLine(Coordinate point, double range)
232240
/// </summary>
233241
/// <param name="point">Point to test</param>
234242
/// <param name="range">Range is a distance object</param>
243+
/// <remarks>
244+
/// This method utilizes 2D ray casting techniques and does not inherently account for the curvature of the Earth. To mitigate the impact of Earth shape distortion on polygons or polylines that span long distances, users should employ densification.
245+
/// </remarks>
235246
/// <returns>bool</returns>
236247
/// <example>
237248
/// The following example shows how to determine if a coordinate is within 1 km of
@@ -261,12 +272,13 @@ public bool IsPointInRangeOfLine(Coordinate point, Distance range)
261272
return false;
262273

263274
return IsPointInRangeOfLine(point, range.Meters);
264-
}
265-
275+
}
276+
266277
/// <summary>
267278
/// Gets distance from nearest polyline in shape
268-
/// </summary>
279+
/// </summary>
269280
/// <param name="point">Coordinate</param>
281+
/// <remarks>This method utilizes 2D ray casting techniques and does not inherently account for the curvature of the Earth. To mitigate the impact of Earth shape distortion on polygons or polylines that span long distances, users should employ densification.</remarks>
270282
/// <returns>Distance</returns>
271283
public Distance DistanceFromNearestPolyLine(Coordinate point)
272284
{
@@ -279,18 +291,148 @@ public Distance DistanceFromNearestPolyLine(Coordinate point)
279291
{
280292
Coordinate c = ClosestPointOnSegment(_points[i], _points[i + 1], point, point.GeoDate, point.EagerLoadSettings);
281293

282-
if (d == null) { d= new Distance(point, c); }
294+
if (d == null) { d = new Distance(point, c); }
283295
else
284296
{
285297
Distance nd = new Distance(point, c);
286298
if (nd.Meters < d.Meters) { d = nd; }
287299
}
288-
300+
289301
}
290302

291303
return d;
292304
}
293305

294-
295-
}
306+
/// <summary>
307+
/// Densifies the polygon by adding additional points along each edge at specified intervals using ellipsoidal (Vincenty) logic.
308+
/// </summary>
309+
/// <param name="distance">The distance between points along the polygon's edges. This distance determines how frequently new points are inserted into the polygon</param>
310+
/// <remarks>
311+
/// This method is particularly useful for large polygons where the curvature of the Earth can cause
312+
/// significant distortion in geographic calculations. By adding more points at regular intervals,
313+
/// the polygon better conforms to the curved surface of the Earth, reducing errors in area calculations,
314+
/// perimeter calculations, and point-in-polygon tests.
315+
///
316+
/// The function automatically calculates intermediate points based on the great-circle distance between
317+
/// existing vertices, ensuring that the new points adhere to the true geographic shape of the polygon.
318+
/// This is essential for maintaining geographic integrity when performing spatial operations or visualizations.
319+
///
320+
/// Note: The densification process increases the number of vertices in the polygon, which may impact performance
321+
/// and memory usage in spatial computations and data storage. Optimal use of this function depends on the required
322+
/// precision and the geographic extent of the application.
323+
/// </remarks>
324+
/// <example>
325+
/// Here is how you might use this function to densify a polygon representing a large geographic area:
326+
/// <code>
327+
/// //Create a four point GeoFence around Utah
328+
/// List&lt;GeoFence.Point&gt; points = new List&lt;GeoFence.Point&gt;();
329+
/// points.Add(new GeoFence.Point(41.003444, -109.045223));
330+
/// points.Add(new GeoFence.Point(41.003444, -102.041524));
331+
/// points.Add(new GeoFence.Point(36.993076, -102.041524));
332+
/// points.Add(new GeoFence.Point(36.993076, -109.045223));
333+
/// points.Add(new GeoFence.Point(41.003444, -109.045223));
334+
///
335+
/// GeoFence gf = new GeoFence(points);
336+
///
337+
/// gf.Densify(new Distance(10, DistanceType.Kilometers));
338+
///
339+
/// //The gf.Points list now contains additional points at intervals of approximately 10 kilometers.
340+
/// </code>
341+
/// </example>
342+
public void Densify(Distance distance)
343+
{
344+
Densify(distance, Shape.Ellipsoid);
345+
}
346+
347+
/// <summary>
348+
/// Densifies the polygon by adding additional points along each edge at specified intervals.
349+
/// </summary>
350+
/// <param name="distance">The distance between points along the polygon's edges. This distance determines how frequently new points are inserted into the polygon</param>
351+
/// <param name="shape">Specify earth shape. Sphere is more efficient, but less precise than ellipsoid.</param>
352+
/// <remarks>
353+
/// This method is particularly useful for large polygons where the curvature of the Earth can cause
354+
/// significant distortion in geographic calculations. By adding more points at regular intervals,
355+
/// the polygon better conforms to the curved surface of the Earth, reducing errors in area calculations,
356+
/// perimeter calculations, and point-in-polygon tests.
357+
///
358+
/// The function automatically calculates intermediate points based on the great-circle distance between
359+
/// existing vertices, ensuring that the new points adhere to the true geographic shape of the polygon.
360+
/// This is essential for maintaining geographic integrity when performing spatial operations or visualizations.
361+
///
362+
/// Note: The densification process increases the number of vertices in the polygon, which may impact performance
363+
/// and memory usage in spatial computations and data storage. Optimal use of this function depends on the required
364+
/// precision and the geographic extent of the application.
365+
/// </remarks>
366+
/// <example>
367+
/// Here is how you might use this function to densify a polygon representing a large geographic area:
368+
/// <code>
369+
/// //Create a four point GeoFence around Utah
370+
/// List&lt;GeoFence.Point&gt; points = new List&lt;GeoFence.Point&gt;();
371+
/// points.Add(new GeoFence.Point(41.003444, -109.045223));
372+
/// points.Add(new GeoFence.Point(41.003444, -102.041524));
373+
/// points.Add(new GeoFence.Point(36.993076, -102.041524));
374+
/// points.Add(new GeoFence.Point(36.993076, -109.045223));
375+
/// points.Add(new GeoFence.Point(41.003444, -109.045223));
376+
///
377+
/// GeoFence gf = new GeoFence(points);
378+
///
379+
/// gf.Densify(new Distance(10, DistanceType.Kilometers), Shape.Sphere);
380+
///
381+
/// //The gf.Points list now contains additional points at intervals of approximately 10 kilometers.
382+
/// </code>
383+
/// </example>
384+
public void Densify(Distance distance, Shape shape)
385+
{
386+
if (_points.Count < 2)
387+
{
388+
throw new InvalidOperationException("You cannot perform densification a Geofence that has less than 2 points.");
389+
}
390+
//Store the original points for reference as _points will be modified
391+
List<Point> ogpoints = new List<Point>(_points);
392+
393+
//Create a collection of point collections to insert into the polygon
394+
List<List<Point>> inserts = new List<List<Point>>();
395+
396+
//Iterate the polygon to create densification points
397+
for (int x = 0; x < ogpoints.Count - 1; x++)
398+
{
399+
var p1 = ogpoints[x];
400+
var p2 = ogpoints[x + 1];
401+
402+
List<Point> ipoints = new List<Point>();
403+
404+
//Coordinate to move
405+
Coordinate mc = new Coordinate(p1.Latitude, p1.Longitude, new EagerLoad(false));
406+
407+
//Destination
408+
Coordinate dc = new Coordinate(p2.Latitude, p2.Longitude, new EagerLoad(false));
409+
410+
while (new Distance(mc, dc).Meters > distance.Meters)
411+
{
412+
mc.Move(dc, distance, shape);
413+
ipoints.Add(new Point(mc.Latitude.ToDouble(), mc.Longitude.ToDouble()));
414+
}
415+
416+
inserts.Add(ipoints);
417+
}
418+
419+
//Clear existing collection
420+
_points.Clear();
421+
422+
//Create new points collection.
423+
for (int x = 0; x < ogpoints.Count - 1; x++)
424+
{
425+
var p = ogpoints[x];
426+
_points.Add(p);
427+
foreach (var dp in inserts[x])
428+
{
429+
_points.Add(dp);
430+
}
431+
}
432+
433+
//Add last point to close shape.
434+
_points.Add(ogpoints.Last());
435+
}
436+
437+
}
296438
}

CoordinateSharp_UnitTests/CoordinateSharp.UnitTests.csproj

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@
5454
<None Remove="CoordinatePartData\Signed.txt" />
5555
<None Remove="CoordinatePartData\SignedDDM.txt" />
5656
<None Remove="CoordinatePartData\SignedDMS.txt" />
57+
<None Remove="GeoFenceData\ColoradoEllipse.txt" />
58+
<None Remove="GeoFenceData\ColoradoSphere.txt" />
5759
<None Remove="MagneticData\MagneticFields2020.txt" />
5860
</ItemGroup>
5961

@@ -196,6 +198,12 @@
196198
<Content Include="CoordinatePartData\SignedDMS.txt">
197199
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
198200
</Content>
201+
<Content Include="GeoFenceData\ColoradoEllipse.txt">
202+
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
203+
</Content>
204+
<Content Include="GeoFenceData\ColoradoSphere.txt">
205+
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
206+
</Content>
199207
<Content Include="MagneticData\MagneticFields2020.txt">
200208
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
201209
</Content>

CoordinateSharp_UnitTests/GeoFence.cs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System.Text;
44
using Microsoft.VisualStudio.TestTools.UnitTesting;
55
using CoordinateSharp;
6+
using System.IO;
67
namespace CoordinateSharp_UnitTests
78
{
89
[TestClass]
@@ -132,7 +133,51 @@ public void Haversine_Precision()
132133
Assert.AreEqual(0, c.Longitude.ToDouble() - gd.Last.Longitude.ToDouble(), .000001);
133134
}
134135

136+
/// <summary>
137+
/// Tests densify logic to ensure proper point placement.
138+
/// </summary>
139+
[TestMethod]
140+
public void Densify()
141+
{
142+
//Create a four point GeoFence around Utah
135143

144+
List<GeoFence.Point> points = new List<GeoFence.Point>();
145+
146+
points.Add(new GeoFence.Point(41.003444, -109.045223));
147+
points.Add(new GeoFence.Point(41.003444, -102.041524));
148+
points.Add(new GeoFence.Point(36.993076, -102.041524));
149+
points.Add(new GeoFence.Point(36.993076, -109.045223));
150+
points.Add(new GeoFence.Point(41.003444, -109.045223));
151+
152+
GeoFence ellipseTest = new GeoFence(points);
153+
GeoFence sphereTest = new GeoFence(new List<GeoFence.Point>(points));
154+
155+
156+
ellipseTest.Densify(new Distance(5, DistanceType.Kilometers));
157+
sphereTest.Densify(new Distance(5, DistanceType.Kilometers), Shape.Sphere);
158+
159+
string[] ellipseTestPoints = File.ReadAllText("GeoFenceData\\ColoradoEllipse.txt").Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
160+
string[] sphereTestPoints = File.ReadAllText("GeoFenceData\\ColoradoSphere.txt").Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
161+
162+
for(int x =0;x< ellipseTestPoints.Length;x++)
163+
{
164+
string[] point = ellipseTestPoints[x].Split(',');
165+
double lat = double.Parse(point[0]);
166+
double lng = double.Parse(point[1]);
167+
Assert.AreEqual(ellipseTest.Points[x].Latitude, lat, .00000000001);
168+
Assert.AreEqual(ellipseTest.Points[x].Longitude, lng, .00000000001);
169+
}
170+
171+
for (int x = 0; x < sphereTestPoints.Length; x++)
172+
{
173+
string[] point = sphereTestPoints[x].Split(',');
174+
double lat = double.Parse(point[0]);
175+
double lng = double.Parse(point[1]);
176+
Assert.AreEqual(sphereTest.Points[x].Latitude, lat, .00000000001);
177+
Assert.AreEqual(sphereTest.Points[x].Longitude, lng, .00000000001);
178+
}
179+
}
180+
136181
/// <summary>
137182
/// Ensures Vincenty precision withing bounds
138183
/// </summary>

0 commit comments

Comments
 (0)