Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
GeoPosition.cs
1using System;
2using System.Diagnostics;
3using System.Globalization;
4using System.Text;
5using System.Text.RegularExpressions;
8
9namespace Waher.Runtime.Geo
10{
14 [DebuggerDisplay("{XmlValue}")]
15 [TypeName(TypeNameSerialization.None)]
16 public sealed class GeoPosition
17 {
22 public static readonly Regex XmlGeoPositionPattern = new Regex(@"(?'Lat'-?\d+(\.\d+)?),(?'Lon'-?\d+(\.\d+)?)(,(?'Alt'-?\d+(\.\d+)?))?", RegexOptions.Singleline | RegexOptions.Compiled);
23
28 public static readonly Regex GpsGeoPositionPattern = new Regex(@"^\s*(?'LatDeg'\d+)\s*°?(\s*(?'LatMin'\d+)'(\s*(?'LatSec'\d+(\.\d+)?)"")?)?\s*(?'LatSign'[nNsS])\s*(?'LonDeg'\d+)\s*°?(\s*(?'LonMin'\d+)'(\s*(?'LonSec'\d+(\.\d+)?)"")?)?\s*(?'LonSign'[eEwW])(\s*,)?\s*((Alt|Altitude)\:?\s*)?((?'Alt'-?\d+(\.\d+)?)\s*(?'AltUnit'(m|ft))?)?\s*$", RegexOptions.Singleline | RegexOptions.Compiled);
29
30 private double latitude;
31 private double longitude;
32 private double? altitude;
33
37 public GeoPosition()
38 {
39 }
40
46 public GeoPosition(double Latitude, double Longitude)
47 {
48 this.latitude = Latitude;
49 this.longitude = Longitude;
50 this.altitude = null;
51 }
52
59 public GeoPosition(double Latitude, double Longitude, double? Altitude)
60 {
61 this.latitude = Latitude;
62 this.longitude = Longitude;
63 this.altitude = Altitude;
64 }
65
69 public double Latitude
70 {
71 get => this.latitude;
72 set
73 {
74 if (value < -90 || value > 90)
75 throw new ArgumentOutOfRangeException(nameof(this.Latitude), "Latitude must be between -90 and 90 degrees.");
76
77 this.latitude = value;
78 }
79 }
80
84 public double Longitude
85 {
86 get => this.longitude;
87 set
88 {
89 if (value < -180 || value > 180)
90 throw new ArgumentOutOfRangeException(nameof(this.Longitude), "Longitude must be between -180 and 180 degrees.");
91
92 this.longitude = value;
93 }
94 }
95
99 public double? Altitude
100 {
101 get => this.altitude;
102 set => this.altitude = value;
103 }
104
108 public double NormalizedLatitude
109 {
110 get
111 {
112 if (this.latitude < -90 || this.latitude > 90)
113 throw new InvalidOperationException("Latitude must be between -90 and 90 degrees.");
114
115 return (this.latitude + 90) / 180;
116 }
117 }
118
123 {
124 get
125 {
126 if (this.longitude < -180 || this.longitude > 180)
127 throw new InvalidOperationException("Longitude must be between -180 and 180 degrees.");
128
129 return (this.longitude + 180) / 360;
130 }
131 }
132
139 public static GeoPosition Parse(string s)
140 {
141 if (TryParse(s, out GeoPosition Value))
142 return Value;
143 else
144 throw new FormatException("Invalid geo-position string: " + s);
145 }
146
154 public static bool TryParse(string s, out GeoPosition Value)
155 {
156 if (TryParseXml(s, out Value))
157 return true;
158
159 if (TryParseGps(s, out Value))
160 return true;
161
162 return false;
163 }
164
171 public static bool TryParseXml(string s, out GeoPosition Value)
172 {
173 int i = s.IndexOf(',');
174 if (i < 0 ||
175 !TryParse(s.Substring(0, i), out double Latitude) ||
176 Latitude < -90 ||
177 Latitude > 90)
178 {
179 Value = null;
180 return false;
181 }
182
183 i++;
184
185 int j = s.IndexOf(',', i);
186 double Longitude;
187 double? Altitude;
188
189 if (j < 0)
190 {
191 if (!TryParse(s.Substring(i), out Longitude) ||
192 Longitude < -180 ||
193 Longitude > 180)
194 {
195 Value = null;
196 return false;
197 }
198
199 Altitude = null;
200 }
201 else
202 {
203 if (!TryParse(s.Substring(i, j - i), out Longitude) ||
204 Longitude < -180 ||
205 Longitude > 180)
206 {
207 Value = null;
208 return false;
209 }
210
211 if (!TryParse(s.Substring(j + 1), out double d))
212 {
213 Value = null;
214 return false;
215 }
216
217 Altitude = d;
218 }
219
220 Value = new GeoPosition(Latitude, Longitude, Altitude);
221
222 return true;
223 }
224
225 private static bool TryParse(string s, out double Value)
226 {
227 return double.TryParse(s.Replace(".", System.Globalization.NumberFormatInfo.CurrentInfo.NumberDecimalSeparator), out Value);
228 }
229
230 internal static string ToString(double Value)
231 {
232 string s = Value.ToString(CultureInfo.InvariantCulture);
233 return Graph.TrimLabel(s);
234 }
235
236 internal static string ToString(bool Value)
237 {
238 return Value ? "true" : "false";
239 }
240
247 public static bool TryParseGps(string s, out GeoPosition Value)
248 {
249 Value = null;
250
251 Match M = GpsGeoPositionPattern.Match(s);
252 if (!M.Success || M.Index > 0 || M.Length < s.Length)
253 return false;
254
255 if (!TryParse(M.Groups["LatDeg"], M.Groups["LatMin"], M.Groups["LatSec"],
256 M.Groups["LatSign"], -90, 90, out double Latitude))
257 {
258 return false;
259 }
260
261 if (!TryParse(M.Groups["LonDeg"], M.Groups["LonMin"], M.Groups["LonSec"],
262 M.Groups["LonSign"], -180, 180, out double Longitude))
263 {
264 return false;
265 }
266
267 string AltitudeStr = M.Groups["Alt"].Value;
268 if (string.IsNullOrEmpty(AltitudeStr))
269 Value = new GeoPosition(Latitude, Longitude);
270 else
271 {
272 if (!TryParse(AltitudeStr, out double Altitude))
273 return false;
274
275 string Unit = M.Groups["AltUnit"].Value;
276
277 if (!string.IsNullOrEmpty(Unit))
278 {
279 switch (Unit.ToLower())
280 {
281 case "m":
282 break;
283
284 case "ft":
285 Altitude *= 0.3048;
286 break;
287
288 default:
289 return false;
290 }
291 }
292
293 Value = new GeoPosition(Latitude, Longitude, Altitude);
294 }
295
296 return true;
297 }
298
299 private static bool TryParse(Group Deg, Group Minute, Group Second, Group Sign,
300 double Min, double Max, out double Value)
301 {
302 int i;
303
304 if (Second is null || string.IsNullOrEmpty(Second.Value))
305 Value = 0;
306 else if (!TryParse(Second.Value, out Value))
307 return false;
308
309 Value /= 60;
310
311 if (!string.IsNullOrEmpty(Minute?.Value))
312 {
313 if (!int.TryParse(Minute.Value, out i))
314 return false;
315
316 Value += i;
317 }
318
319 Value /= 60;
320
321 if (!int.TryParse(Deg.Value, out i))
322 return false;
323
324 Value += i;
325
326 if (Sign is null || string.IsNullOrEmpty(Sign.Value) || Sign.Value.Length != 1)
327 return false;
328
329 char ch = char.ToUpper(Sign.Value[0]);
330
331 if (ch == 'S' || ch == 'W')
332 Value = -Value;
333 else if (ch != 'N' && ch != 'E')
334 return false;
335
336 if (Value < Min || Value > Max)
337 return false;
338
339 return true;
340 }
341
345 public string XmlValue
346 {
347 get
348 {
349 StringBuilder sb = new StringBuilder();
350
351 sb.Append(ToString(this.latitude));
352 sb.Append(',');
353 sb.Append(ToString(this.longitude));
354
355 if (this.altitude.HasValue)
356 {
357 sb.Append(',');
358 sb.Append(ToString(this.altitude.Value));
359 }
360
361 return sb.ToString();
362 }
363 }
364
368 public string NormalizedValue
369 {
370 get
371 {
372 StringBuilder sb = new StringBuilder();
373
374 sb.Append(ToString(this.NormalizedLatitude));
375 sb.Append(',');
376 sb.Append(ToString(this.NormalizedLongitude));
377
378 if (this.altitude.HasValue)
379 {
380 sb.Append(',');
381 sb.Append(ToString(this.altitude.Value));
382 }
383
384 return sb.ToString();
385 }
386 }
387
391 public string HumanReadable
392 {
393 get
394 {
395 StringBuilder sb = new StringBuilder();
396
397 Append(this.latitude, 'N', 'S', sb);
398 sb.Append(' ');
399 Append(this.longitude, 'E', 'W', sb);
400
401 if (this.altitude.HasValue)
402 {
403 sb.Append(", ");
404 sb.Append(ToString(this.altitude.Value));
405 sb.Append(" m");
406 }
407
408 return sb.ToString();
409 }
410 }
411
412 private static void Append(double Nr, char PosChar, char NegChar, StringBuilder sb)
413 {
414 int Degrees;
415 int Minutes;
416 double Seconds;
417 char Char;
418
419 if (Nr >= 0)
420 Char = PosChar;
421 else
422 {
423 Char = NegChar;
424 Nr = -Nr;
425 }
426
427 Degrees = (int)Nr;
428 Nr -= Degrees;
429 Nr *= 60;
430
431 Minutes = (int)Nr;
432 Nr -= Minutes;
433 Nr *= 60;
434
435 Seconds = Nr;
436
437 if (Seconds < 1e-10)
438 Seconds = 0;
439 else if (Seconds > 60 - 1e-10)
440 {
441 Seconds = 0;
442 Minutes++;
443 if (Minutes == 60)
444 {
445 Minutes = 0;
446 Degrees++;
447 }
448 }
449
450 sb.Append(Degrees);
451 sb.Append("° ");
452
453 if (Minutes != 0 || Seconds != 0)
454 {
455 sb.Append(Minutes);
456 sb.Append("' ");
457
458 if (Seconds != 0)
459 {
460 sb.Append(ToString(Seconds));
461 sb.Append("\" ");
462 }
463 }
464
465 sb.Append(Char);
466 }
467
469 public override string ToString()
470 {
471 return this.XmlValue;
472 }
473
475 public override bool Equals(object obj)
476 {
477 return
478 obj is GeoPosition P &&
479 this.latitude == P.latitude &&
480 this.longitude == P.longitude &&
481 this.altitude == P.altitude;
482 }
483
485 public override int GetHashCode()
486 {
487 int Result = this.latitude.GetHashCode();
488 Result ^= Result << 5 ^ this.longitude.GetHashCode();
489
490 if (this.altitude.HasValue)
491 Result ^= Result << 5 ^ this.altitude.Value.GetHashCode();
492
493 return Result;
494 }
495
502 public static bool operator ==(GeoPosition A, GeoPosition B)
503 {
504 if (A is null ^ B is null)
505 return false;
506
507 if (A is null)
508 return true;
509
510 return A.Equals(B);
511 }
512
519 public static bool operator !=(GeoPosition A, GeoPosition B)
520 {
521 if (A is null ^ B is null)
522 return true;
523
524 if (A is null)
525 return false;
526
527 return !A.Equals(B);
528 }
529
530 }
531}
Contains information about a position in a geo-spatial coordinate system.
Definition: GeoPosition.cs:17
static readonly Regex GpsGeoPositionPattern
Pattern for parsing a geo-position GPS strings. The names groups Lat, Lon and Alt can be used to extr...
Definition: GeoPosition.cs:28
double Longitude
Longitude in degrees.
Definition: GeoPosition.cs:85
override string ToString()
Definition: GeoPosition.cs:469
double NormalizedLongitude
Normalized longitude. The range [-180,180] is linearly mapped to [0,1].
Definition: GeoPosition.cs:123
double? Altitude
Altitude in meters. Can be null.
Definition: GeoPosition.cs:100
static bool TryParse(string s, out GeoPosition Value)
Tries to parse a string representation of a GeoPosition. If first attempts the XML format....
Definition: GeoPosition.cs:154
double NormalizedLatitude
Normalized longitude. The range [-90,90] is linearly mapped to [0,1].
Definition: GeoPosition.cs:109
override bool Equals(object obj)
Definition: GeoPosition.cs:475
string NormalizedValue
String-representation of the normalized geo-position.
Definition: GeoPosition.cs:369
GeoPosition(double Latitude, double Longitude)
Contains information about a position in a geo-spatial coordinate system.
Definition: GeoPosition.cs:46
string XmlValue
String-representation of the geo-position, for use in XML.
Definition: GeoPosition.cs:346
static bool TryParseXml(string s, out GeoPosition Value)
Tries to parse an XML string representation of a GeoPosition.
Definition: GeoPosition.cs:171
double Latitude
Latitude in degrees.
Definition: GeoPosition.cs:70
static bool TryParseGps(string s, out GeoPosition Value)
Tries to parse a GPS string representation of a GeoPosition.
Definition: GeoPosition.cs:247
static bool operator!=(GeoPosition A, GeoPosition B)
Checks if two geo-positions are not equal.
Definition: GeoPosition.cs:519
GeoPosition(double Latitude, double Longitude, double? Altitude)
Contains information about a position in a geo-spatial coordinate system.
Definition: GeoPosition.cs:59
static GeoPosition Parse(string s)
Parse a string representation of a GeoPosition. If first attempts the XML format. If not successful,...
Definition: GeoPosition.cs:139
static readonly Regex XmlGeoPositionPattern
Pattern for parsing a geo-position XML values. The names groups Lat, Lon and Alt can be used to extra...
Definition: GeoPosition.cs:22
static bool operator==(GeoPosition A, GeoPosition B)
Checks if two geo-positions are equal.
Definition: GeoPosition.cs:502
string HumanReadable
Human-readable string-representation of the geo-position.
Definition: GeoPosition.cs:392
GeoPosition()
Contains information about a position in a geo-spatial coordinate system.
Definition: GeoPosition.cs:37
Base class for graphs.
Definition: Graph.cs:88
static string TrimLabel(string Label)
Trims a numeric label, removing apparent roundoff errors.
Definition: Graph.cs:1727
TypeNameSerialization
How the type name should be serialized.