Skip to content

Commit e6f1fad

Browse files
committed
Add decimal degrees (DD) and degrees minutes seconds (DMS) converters
1 parent e27db69 commit e6f1fad

File tree

7 files changed

+145
-15
lines changed

7 files changed

+145
-15
lines changed
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
namespace OxyPlot.Core.Cartography.Tests
2+
{
3+
public class CartographyHelperTest
4+
{
5+
[Fact]
6+
public void DecimalDegreesToDegreesMinutesSecondsTest()
7+
{
8+
var dms = CartographyHelper.DecimalDegreesToDegreesMinutesSeconds(38.8897, true, 0);
9+
Assert.Equal("38\u00b053\u203223\u2033N", dms);
10+
11+
var dms1 = CartographyHelper.DecimalDegreesToDegreesMinutesSeconds(-77.0089, false, 0);
12+
Assert.Equal("77\u00b000\u203232\u2033W", dms1);
13+
}
14+
15+
[Fact]
16+
public void DegreesMinutesSecondsToDecimalDegreesTests()
17+
{
18+
var dec = CartographyHelper.DegreesMinutesSecondsToDecimalDegrees(38, 53, 23, 'N');
19+
Assert.Equal(38.8897, dec, 4);
20+
21+
var dec1 = CartographyHelper.DegreesMinutesSecondsToDecimalDegrees(77, 0, 32, 'W');
22+
Assert.Equal(-77.0089, dec1, 4);
23+
}
24+
25+
[Fact]
26+
public void DegreesMinutesSecondsToDecimalDegreesTests2()
27+
{
28+
var dec = CartographyHelper.DegreesMinutesSecondsToDecimalDegrees("38\u00b053\u203223\u2033N");
29+
Assert.Equal(38.8897, dec, 4);
30+
31+
var dec1 = CartographyHelper.DegreesMinutesSecondsToDecimalDegrees("77\u00b000\u203232\u2033W");
32+
Assert.Equal(-77.0089, dec1, 4);
33+
}
34+
}
35+
}

OxyPlot.Core.Cartography.Tests/OxyPlot.Core.Cartography.Tests.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,8 @@
2121
</PackageReference>
2222
</ItemGroup>
2323

24+
<ItemGroup>
25+
<ProjectReference Include="..\OxyPlot.Core.Cartography\OxyPlot.Core.Cartography.csproj" />
26+
</ItemGroup>
27+
2428
</Project>

OxyPlot.Core.Cartography.Tests/UnitTest1.cs

Lines changed: 0 additions & 11 deletions
This file was deleted.

OxyPlot.Core.Cartography/Axes/LatitudeWebMercatorAxis.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ public LatitudeWebMercatorAxis()
3030
AbsoluteMinimum = -_maxValue;
3131
AbsoluteMaximum = _maxValue;
3232
Key = "Latitude";
33+
StringFormat = "00.0###°";
3334

3435
// Hack as 'UpdateActualMaxMin' method is internal
3536
_updateActualMaxMinMethod = typeof(Axis).GetMethod("UpdateActualMaxMin",

OxyPlot.Core.Cartography/Axes/LongitudeAxis.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ public LongitudeAxis() : base()
1515
this.AbsoluteMinimum = -_maxDefaultValue;
1616
this.AbsoluteMaximum = _maxDefaultValue;
1717
Key = "Longitude";
18+
StringFormat = "00.0###°";
1819
}
1920

2021
/// <summary>

OxyPlot.Core.Cartography/Utilities/CartographyHelper.cs

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,106 @@ public static void TileToLatLon(double x, double y, int zoom, out double latitud
8181
latitude = lat * 180.0 / Math.PI;
8282
}
8383

84+
/// <summary>
85+
/// Converts decimal degrees (DD) to degrees minutes seconds coordinates (DMS).
86+
/// <para>
87+
/// <see href="https://en.wikipedia.org/wiki/Decimal_degrees"/>
88+
/// </para>
89+
/// <see href="https://en.wikipedia.org/wiki/ISO_6709#Representation_at_the_human_interface_(Annex_D)"/>
90+
/// </summary>
91+
/// <param name="decimalDegrees">The decimal degrees (DD) coordinate</param>
92+
/// <param name="isLatitude">True if latitude, false if longitude</param>
93+
/// <param name="secondsDecimal">Decimal roundings of seconds part</param>
94+
/// <returns>The degrees minutes seconds coordinates (DMS), e.g. 50°40′46.461″N</returns>
95+
public static string DecimalDegreesToDegreesMinutesSeconds(double decimalDegrees, bool isLatitude, int secondsDecimal)
96+
{
97+
// see https://en.wikipedia.org/wiki/ISO_6709#Representation_at_the_human_interface_(Annex_D)
98+
var card = isLatitude ? (decimalDegrees > 0 ? "N" : "S") : (decimalDegrees > 0 ? "E" : "W");
99+
var d = Math.Truncate(decimalDegrees);
100+
var delta = Math.Abs(decimalDegrees - d);
101+
var m = Math.Truncate(60 * delta);
102+
var s = Math.Round(3600.0 * delta - 60.0 * m, secondsDecimal);
103+
return $"{Math.Abs(d):00}\u00b0{m:00}\u2032{s:00}\u2033{card}";
104+
}
105+
106+
/// <summary>
107+
/// Converts degrees minutes seconds (DMS) to decimal degrees (DD) coordinates.
108+
/// <para>
109+
/// <see href="https://en.wikipedia.org/wiki/Decimal_degrees"/>
110+
/// </para>
111+
/// <see href="https://en.wikipedia.org/wiki/ISO_6709#Representation_at_the_human_interface_(Annex_D)"/>
112+
/// </summary>
113+
/// <param name="degrees"></param>
114+
/// <param name="minutes"></param>
115+
/// <param name="seconds"></param>
116+
/// <param name="cardinal"></param>
117+
/// <returns>The decimal degrees (DD) coordinates</returns>
118+
/// <exception cref="ArgumentException"></exception>
119+
public static double DegreesMinutesSecondsToDecimalDegrees(int degrees, int minutes, double seconds, char cardinal)
120+
{
121+
double sign;
122+
if (cardinal.Equals('N') || cardinal.Equals('E'))
123+
{
124+
sign = 1;
125+
}
126+
else if (cardinal.Equals('S') || cardinal.Equals('W'))
127+
{
128+
sign = -1;
129+
}
130+
else
131+
{
132+
throw new ArgumentException($"Cardinal value should be either 'N', 'S', 'E' or 'W', but is '{cardinal}'", nameof(cardinal));
133+
}
134+
135+
return (degrees + minutes / 60.0 + seconds / 3600.0) * sign;
136+
}
137+
138+
/// <summary>
139+
/// Converts degrees minutes seconds (DMS) to decimal degrees (DD) coordinates.
140+
/// <para>
141+
/// <see href="https://en.wikipedia.org/wiki/Decimal_degrees"/>
142+
/// </para>
143+
/// <see href="https://en.wikipedia.org/wiki/ISO_6709#Representation_at_the_human_interface_(Annex_D)"/>
144+
/// </summary>
145+
/// <param name="degreesMinutesSeconds">
146+
/// The degrees minutes seconds coordinate, formated as DD°MM′SS.SSS″C e.g. 38°53′23″N
147+
/// <para>
148+
/// ° = \u00b0, ′ = \u2032, ″ = \u2033
149+
/// </para>
150+
/// </param>
151+
/// <returns>The decimal degrees (DD) coordinates</returns>
152+
public static double DegreesMinutesSecondsToDecimalDegrees(string degreesMinutesSeconds)
153+
{
154+
var parts = degreesMinutesSeconds.Split('\u00b0', '\u2032', '\u2033');
155+
if (parts.Length != 4)
156+
{
157+
throw new ArgumentException("The coordinate should be in DMS format, i.e. DD°MM′SS.SSS″C", nameof(degreesMinutesSeconds));
158+
}
159+
160+
if (!int.TryParse(parts[0], out int d))
161+
{
162+
throw new ArgumentException($"Could not parse degrees part, got {parts[0]}. The coordinate should be in DMS format, i.e. DD°MM′SS.SSS″C", nameof(degreesMinutesSeconds));
163+
}
164+
165+
if (!int.TryParse(parts[1], out int m))
166+
{
167+
throw new ArgumentException($"Could not parse minutes part, got {parts[1]}. The coordinate should be in DMS format, i.e. DD°MM′SS.SSS″C", nameof(degreesMinutesSeconds));
168+
}
169+
170+
if (!double.TryParse(parts[2], out double s))
171+
{
172+
throw new ArgumentException($"Could not parse seconds part, got {parts[2]}. The coordinate should be in DMS format, i.e. DD°MM′SS.SSS″C", nameof(degreesMinutesSeconds));
173+
}
174+
175+
var card = parts[3];
176+
if (card.Length != 1)
177+
{
178+
throw new ArgumentException($"Could not parse cardinal part, got {parts[3]}. The coordinate should be in DMS format, i.e. DD°MM′SS.SSS″C", nameof(degreesMinutesSeconds));
179+
}
180+
181+
return DegreesMinutesSecondsToDecimalDegrees(d, m, s, card[0]);
182+
}
183+
84184
/// <summary>
85185
/// Generate the storage path from a uri.
86186
/// </summary>

SimpleDemo/ViewModels/MapViewModel.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -63,13 +63,13 @@ private PlotModel CreateMapPlotView()
6363
Position = AxisPosition.Left,
6464
Minimum = 51.42,
6565
Maximum = 51.62,
66-
Title = "Latitude"
66+
Title = "Latitude",
6767
});
6868

6969
var tileMapImageProvider = new HttpTileMapImageProvider(SynchronizationContext.Current)
7070
{
71-
//Url = "http://tile.openstreetmap.org/{Z}/{X}/{Y}.png",
72-
Url = "https://server.arcgisonline.com/arcgis/rest/services/World_Imagery/MapServer/tile/{Z}/{Y}/{X}", // https://developers.arcgis.com/documentation/mapping-apis-and-services/data-hosting/services/image-tile-service/
71+
Url = "http://tile.openstreetmap.org/{Z}/{X}/{Y}.png",
72+
//Url = "https://server.arcgisonline.com/arcgis/rest/services/World_Imagery/MapServer/tile/{Z}/{Y}/{X}", // https://developers.arcgis.com/documentation/mapping-apis-and-services/data-hosting/services/image-tile-service/
7373
//Url = "https://maptiles.finncdn.no/tileService/1.0.3/norortho/{Z}/{X}/{Y}.png",
7474
//Url = "https://maptiles.finncdn.no/tileService/1.0.3/normap/{Z}/{X}/{Y}.png",
7575

@@ -116,7 +116,7 @@ private PlotModel CreateMapPlotView()
116116
// Add the tile map annotation
117117
model.Annotations.Add(new MapTileAnnotation(streamImg, tileMapImageProvider)
118118
{
119-
CopyrightNotice = "OpenStreetMap",
119+
CopyrightNotice = "© OpenStreetMap contributors",
120120
MinZoomLevel = 0,
121121
MaxZoomLevel = 19, // max OpenStreetMap value
122122
IsTileGridVisible = true,

0 commit comments

Comments
 (0)