csmapcontrol

csmapcontrol Git Source Tree

Root/MapControl/Track.cs

1//
2// csmapcontrol - a C# map control - http://projects.ciarang.com/p/csmapcontrol
3// Copyright (C) 2010-11, Ciaran Gultnieks
4//
5// This program is free software: you can redistribute it and/or modify
6// it under the terms of the GNU Affero General Public License as published by
7// the Free Software Foundation, either version 3 of the License, or
8// (at your option) any later version.
9//
10// This program is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13// GNU Affero General Public License for more details.
14//
15// You should have received a copy of the GNU Affero General Public License
16// along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18using System;
19using System.Collections.Generic;
20using System.Xml;
21using System.Drawing;
22using System.Text;
23using System.IO;
24
25namespace csmapcontrol
26{
27
28public class TrackPoint : ICloneable
29{
30public double Lat;
31public double Lon;
32public double Elevation;
33public DateTime Time;
34
35public object Clone()
36{
37TrackPoint copy=new TrackPoint();
38copy.Lat=Lat;
39copy.Lon=Lon;
40copy.Elevation=Elevation;
41copy.Time=Time;
42return copy;
43}
44
45}
46
47public class Track
48{
49
50public List<TrackPoint> Points;
51public string Name;
52
53/// <summary>
54/// Create a new empty Track.
55/// </summary>
56public Track()
57{
58Points=new List<TrackPoint>();
59}
60
61/// <summary>
62/// Create a new Track by loading from a GPX file.
63/// </summary>
64/// <param name="filespec">The filespec of a GPX file.</param>
65public Track(string gpxfile)
66{
67Points=new List<TrackPoint>();
68XmlDocument x=new XmlDocument();
69x.Load(gpxfile);
70if(x.DocumentElement.Name!="gpx")
71throw new ApplicationException("Document element should be 'gpx'");
72string ns="http://www.topografix.com/GPX/1/0";
73string ver=x.DocumentElement.GetAttribute("version");
74if(ver!=null && ver=="1.1")
75ns="http://www.topografix.com/GPX/1/1";
76XmlNamespaceManager nsmgr=new XmlNamespaceManager(x.NameTable);
77nsmgr.AddNamespace("gpx", ns);
78if(x.GetElementsByTagName("name").Count>0)
79Name=x.GetElementsByTagName("name")[0].InnerText;
80else
81Name=Path.GetFileNameWithoutExtension(gpxfile);
82foreach(XmlNode point in x.SelectNodes("//gpx:trkpt",nsmgr))
83{
84TrackPoint p=new TrackPoint();
85p.Lat=double.Parse(point.SelectSingleNode("@lat").Value);
86p.Lon=double.Parse(point.SelectSingleNode("@lon").Value);
87p.Elevation=double.Parse(point.SelectSingleNode("gpx:ele",nsmgr).InnerText);
88p.Time=DateTime.Parse(point.SelectSingleNode("gpx:time",nsmgr).InnerText).ToUniversalTime();
89Points.Add(p);
90}
91}
92
93/// <summary>
94/// Get a GPX representation of the track data.
95/// </summary>
96/// <returns>GPX data. (UTF-16 encoded)</returns>
97public string ToGPX()
98{
99const string gpx="http://www.topografix.com/GPX/1/1";
100StringBuilder sb=new StringBuilder();
101using(XmlWriter writer=XmlWriter.Create(sb))
102{
103writer.WriteStartElement("gpx",gpx);
104writer.WriteAttributeString("version","1.1");
105writer.WriteAttributeString("creator","CSMapControl");
106
107Bounds b=CalculateBounds();
108writer.WriteStartElement("bounds",gpx);
109writer.WriteAttributeString("minlat",b.MinLat.ToString());
110writer.WriteAttributeString("maxlat",b.MaxLat.ToString());
111writer.WriteAttributeString("minlon",b.MinLon.ToString());
112writer.WriteAttributeString("maxlon",b.MaxLon.ToString());
113
114writer.WriteStartElement("trk",gpx);
115writer.WriteStartElement("name",gpx);
116writer.WriteValue(Name);
117writer.WriteEndElement();
118
119writer.WriteStartElement("trkseg",gpx);
120foreach(TrackPoint p in Points)
121{
122writer.WriteStartElement("trkpt",gpx);
123writer.WriteAttributeString("lat",p.Lat.ToString());
124writer.WriteAttributeString("lon",p.Lon.ToString());
125writer.WriteStartElement("ele",gpx);
126writer.WriteValue(p.Elevation.ToString());
127writer.WriteEndElement();
128writer.WriteStartElement("time",gpx);
129writer.WriteValue(p.Time.ToString("s")+"Z");
130writer.WriteEndElement();
131writer.WriteEndElement();
132}
133
134writer.WriteEndElement();
135}
136return sb.ToString();
137}
138
139/// <summary>
140/// The start time of the track. Returns DateTime.MinValue if the
141/// track has no points.
142/// </summary>
143public DateTime StartTime
144{
145get
146{
147if(Points.Count==0)
148return DateTime.MinValue;
149return Points[0].Time;
150}
151}
152
153/// <summary>
154/// The end time of the track. Returns DateTime.MinValue if the
155/// track has no points.
156/// </summary>
157public DateTime EndTime
158{
159get
160{
161if(Points.Count==0)
162return DateTime.MinValue;
163return Points[Points.Count-1].Time;
164}
165}
166
167/// <summary>
168/// Split this Track at the specified time.
169/// </summary>
170/// <param name="splittime">The time to split at.</param>
171/// <returns>An array containing two new Track objects, the first being everything
172/// from this Track up to and including splittime, and the second being the
173/// remainder.</returns>
174public Track[] Split(DateTime splittime)
175{
176Track[] retval=new Track[2];
177retval[0]=new Track();
178retval[1]=new Track();
179retval[0].Name=Name+" Part 1";
180retval[1].Name=Name+" Part 2";
181foreach(TrackPoint p in Points)
182if(p.Time<=splittime)
183retval[0].Points.Add((TrackPoint)p.Clone());
184else
185retval[1].Points.Add((TrackPoint)p.Clone());
186return retval;
187}
188
189/// <summary>
190/// Remove the trackpoint at the given time.
191/// </summary>
192/// <param name="removetime">The time at which the point will be removed. Any
193/// points at this exact time will be removed.</param>
194public void RemovePoint(DateTime removetime)
195{
196List<TrackPoint> toremove=new List<TrackPoint>();
197foreach(TrackPoint p in Points)
198if(p.Time==removetime)
199toremove.Add(p);
200foreach(TrackPoint p in toremove)
201Points.Remove(p);
202}
203
204public class Bounds
205{
206public double MinLat;
207public double MinLon;
208public double MaxLat;
209public double MaxLon;
210}
211
212/// <summary>
213/// Calculate the bounds of this track.
214/// </summary>
215/// <returns>The bounds of the track, or null if the track contains no
216/// data at all.</returns>
217public Bounds CalculateBounds()
218{
219Bounds bounds=null;
220foreach(TrackPoint p in Points)
221{
222if(bounds==null)
223{
224bounds=new Bounds();
225bounds.MinLat=bounds.MaxLat=p.Lat;
226bounds.MinLon=bounds.MaxLon=p.Lon;
227}
228else
229{
230if(p.Lat<bounds.MinLat)
231bounds.MinLat=p.Lat;
232if(p.Lat>bounds.MaxLat)
233bounds.MaxLat=p.Lat;
234if(p.Lon<bounds.MinLon)
235bounds.MinLon=p.Lon;
236if(p.Lon>bounds.MaxLon)
237bounds.MaxLon=p.Lon;
238}
239}
240return bounds;
241}
242
243/// <summary>
244/// Calculate the length of the track.
245/// </summary>
246/// <returns>The length in metres.</returns>
247public double CalculateLength ()
248{
249TrackPoint last = null;
250double dist = 0;
251foreach (TrackPoint p in Points) {
252if (last != null) {
253dist += distance (last.Lat, last.Lon, p.Lat, p.Lon);
254}
255last = p;
256}
257return dist;
258}
259
260/// <summary>
261/// Calculate geodesic distance (in m) between two points specified by
262/// latitude/longitude (in numeric degrees) using Vincenty inverse
263/// formula for ellipsoids.
264///
265/// Code based on a Javascript version by © 2002-2008 Chris Veness.
266///
267/// That code contains the following: "You are welcome to re-use these scripts
268/// [without any warranty express or implied] provided you retain my copyright notice
269/// and when possible a link to my website (under a LGPL license)."
270///
271/// The originating web page: http://www.movable-type.co.uk/scripts/latlong-vincenty.html
272///
273/// In turn, this python conversion was done by Maarten Sneep and released
274/// under the GPL V2 at http://www.xs4all.nl/~msneep/software/whereabouts-readme.html
275///
276/// In turn, it was converted to C# by Ciaran Gultnieks.
277/// </summary>
278/// <param name="lat1">Latitude of first point.</param>
279/// <param name="lon1">Longitude of first point.</param>
280/// <param name="lat2">Latitude of second point.</param>
281/// <param name="lon2">Longitude of second point.</param>
282/// <returns>The distance in metres.</returns>
283private double distance (double lat1, double lon1, double lat2, double lon2)
284{
285
286double a = 6378137;
287// in m
288double b = 6356752.3142;
289// in m
290double f = 1 / 298.257223563;
291double deg2rad = Math.PI / 180;
292lat1 *= deg2rad;
293lon1 *= deg2rad;
294lat2 *= deg2rad;
295lon2 *= deg2rad;
296
297double L = lon2 - lon1;
298double U1 = Math.Atan ((1 - f) * Math.Tan (lat1));
299double U2 = Math.Atan ((1 - f) * Math.Tan (lat2));
300double sinU1 = Math.Sin (U1);
301double cosU1 = Math.Cos (U1);
302double sinU2 = Math.Sin (U2);
303double cosU2 = Math.Cos (U2);
304double Lambda = L;
305double LambdaP = 2 * Math.PI;
306int iterLimit = 20;
307
308double sinLambda, cosLambda, sinSigma = 0, cosSigma = 0;
309double cosSqAlpha = 0, sinAlpha, cos2SigmaM = 0;
310double C, sigma = 0;
311
312while (Math.Abs (Lambda - LambdaP) > 1e-12 && iterLimit > 0) {
313sinLambda = Math.Sin (Lambda);
314cosLambda = Math.Cos (Lambda);
315sinSigma = Math.Sqrt (Math.Pow (cosU2 * sinLambda, 2) + Math.Pow (cosU1 * sinU2 - sinU1 * cosU2 * cosLambda, 2));
316if (sinSigma == 0)
317return 0;
318cosSigma = sinU1 * sinU2 + cosU1 * cosU2 * cosLambda;
319sigma = Math.Atan2 (sinSigma, cosSigma);
320sinAlpha = cosU1 * cosU2 * sinLambda / sinSigma;
321cosSqAlpha = 1 - Math.Pow (sinAlpha, 2);
322try {
323
324cos2SigmaM = cosSigma - 2 * sinU1 * sinU2 / cosSqAlpha;
325} catch (Exception) {
326cos2SigmaM = 0;
327}
328C = f / 16 * cosSqAlpha * (4 + f * (4 - 3 * cosSqAlpha));
329LambdaP = Lambda;
330Lambda = L + (1 - C) * f * sinAlpha * (sigma + C * sinSigma * (cos2SigmaM + C * cosSigma * (-1 + 2 * Math.Pow (cos2SigmaM, 2))));
331iterLimit -= 1;
332
333}
334
335if (iterLimit == 0)
336return 0;
337
338double uSq = cosSqAlpha * (Math.Pow (a, 2) - Math.Pow (b, 2)) / Math.Pow (b, 2);
339double A = 1 + uSq / 16384 * (4096 + uSq * (-768 + uSq * (320 - 175 * uSq)));
340double B = uSq / 1024 * (256 + uSq * (-128 + uSq * (74 - 47 * uSq)));
341double deltaSigma = B * sinSigma * (cos2SigmaM + B / 4 * (cosSigma * (-1 + 2 * Math.Pow (cos2SigmaM, 2)) - B / 6 * cos2SigmaM * (-3 + 4 * Math.Pow (sinSigma, 2)) * (-3 + 4 * Math.Pow (cos2SigmaM, 2))));
342return b * A * (sigma - deltaSigma);
343
344}
345
346}
347}
348

Archive Download this file

Branches