본문 바로가기
.NET C#

C# GPX(GPS Exchange Format) Paser

by 태디 2023. 1. 13.
728x90

스타라바에 기록된 자전거 라이딩 로그(경로)

https://www.strava.com/

 

Strava | 달리기, 사이클링, 하이킹 앱 - 트레이닝, 추적, 공유

 

www.strava.com

gpx 파일을 다운로드 받을 수 있다.

<?xml version="1.0" encoding="UTF-8"?>
<gpx creator="StravaGPX" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd" 
version="1.1" xmlns="http://www.topografix.com/GPX/1/1">
 <metadata>
  <time>2013-07-19T11:55:35Z</time>
 </metadata>
 <trk>
  <name>오전 라이딩</name>
  <type>1</type>
  <trkseg>
   <trkpt lat="41.9323470" lon="-87.6441970">
    <ele>182.2</ele>
    <time>2013-07-19T11:55:35Z</time>
   </trkpt>
   <trkpt lat="41.9323870" lon="-87.6449280">
    <ele>183.2</ele>
    <time>2013-07-19T14:42:01Z</time>
   </trkpt>
   <trkpt lat="41.9322950" lon="-87.6448010">
    <ele>183.2</ele>
    <time>2013-07-19T14:42:19Z</time>
   </trkpt>
   <trkpt lat="41.9322280" lon="-87.6446340">
    <ele>183.2</ele>
    <time>2013-07-19T14:42:45Z</time>
   </trkpt>
   <trkpt lat="41.9323360" lon="-87.6448060">
    <ele>183.2</ele>
    <time>2013-07-19T14:43:08Z</time>
   </trkpt>
   <trkpt lat="41.9326000" lon="-87.6441310">
    <ele>181.9</ele>
    <time>2013-07-19T14:43:30Z</time>
   </trkpt>
   <trkpt lat="41.9329520" lon="-87.6444520">
    <ele>182.3</ele>
    <time>2013-07-19T14:44:22Z</time>
   </trkpt>
   <trkpt lat="41.9328460" lon="-87.6437660">
   ....

*.gpx 파일을 열어보면 xml 스키 안에 특정 시간 간격으로 (예:1초) 기본적으로 기록되는 시각, 경도와 위도 그리고 주변기기와 연동하면 케이던스(rpm:1분동안 페달링 한 횟수), 심장박동수, 파워(w:와트) 정보등을 확인할 수 있다.

기록되는 형식은 gpx, kml, Tcx, kmz 등 여러가지 포맷이 있다. 또 자전거 컴퓨터(gps 속도계)의 제조사에 따라 저장되는 파일 형식이 다를 수 있다.

아래 코드는 gpx 파일에서 데이터를 추출하는 파서(Paser)이다.

 /// <summary>
 ///  *.gpx 파일 파싱
 /// </summary>
 public class GpxPaser
 {
     List<GpsLogData> listGpxLog = null;

     public string FileName { get; set; }
     public string TimeType { get; set; }

     public GpxPaser(string fileName, string timeType)
     {
         this.FileName = fileName;
         this.TimeType = timeType;

         listGpxLog = new List<GpsLogData>();
     }

     /// <summary>
     /// Gpx 데이터 파싱
     /// </summary>
     /// <param name="listRideDate"></param>
     /// <returns></returns>
     public List<GpsLogData> Paser(List<RideInfo> listRideDate)
     {
         DateTimeHelper.DateFormat dateformat = DateTimeHelper.DateFormat.CONVERT_NOMAL;

         switch (TimeType)
         {
             case "UTC":
                 dateformat = DateTimeHelper.DateFormat.CONVERT_LOCAL_TO_UTC;
                 break;
             case "LOCAL":
                 dateformat = DateTimeHelper.DateFormat.CONVERT_UTC_TO_LOCAL;
                 break;
             case "NOMAL":
                 dateformat = DateTimeHelper.DateFormat.CONVERT_NOMAL;
                 break;
         }

         XmlDocument xml = new XmlDocument();
         xml.Load(FileName);

         string utcTime = string.Empty;
         string localTime = string.Empty;

         string rideDateOrigen = string.Empty;
         string overlap = string.Empty; 
         XmlNodeList metaNodes = xml.GetElementsByTagName("metadata");

         if (metaNodes.Count > 0)
         {
             foreach (XmlNode node in metaNodes)
             {
                 utcTime = node["time"] != null ? node["time"].InnerText.ToString() : string.Empty;
                 rideDateOrigen = DateTimeHelper.ConvertTimeTotime(dateformat, utcTime, "R", 0);
             }
         }
         else
         {
             XmlNodeList time = xml.GetElementsByTagName("time");
             utcTime = time[0].InnerText;
             rideDateOrigen = DateTimeHelper.ConvertTimeTotime(dateformat, utcTime, "R", 0);
         }

         // 중복체크
         foreach (RideInfo day in listRideDate)
         {
             if (day.RIDE_DATE_ORIGEN == rideDateOrigen)
             {
                 overlap = "Y";
                 break;
             }
         }

         string name = string.Empty;
         XmlNodeList trkNode = xml.GetElementsByTagName("trk");
         foreach (XmlNode node in trkNode)
         {
             name = node["name"] != null ? node["name"].InnerText : string.Empty;
         }

         var nsmgr = new XmlNamespaceManager(xml.NameTable);

         // Path 검색을 위한 NameSpace 설정
         nsmgr.AddNamespace("i", "http://www.topografix.com/GPX/1/1");
         nsmgr.AddNamespace("gpxtpx", "http://www.garmin.com/xmlschemas/TrackPointExtension/v1");

         XmlNodeList nodes = xml.SelectNodes("//i:trkpt", nsmgr);

         if (nodes.Count == 0)
             nodes = xml.GetElementsByTagName("trkpt");

         // log No
         int daySeq = 0;
         foreach (XmlNode node in nodes)
         {
             daySeq++;

             GpsLogCalculate gpsCalc = new GpsLogCalculate();
             GpsLogData log = new GpsLogData();

             log.TITLE = name;
             log.RIDE_DATE = rideDateOrigen.Substring(0, 10);
             log.LAT = Convert.ToDouble(node.Attributes["lat"].Value);
             log.LNG = Convert.ToDouble(node.Attributes["lon"].Value);
             log.ELE = node["ele"] != null ? Math.Round(Convert.ToDouble(node["ele"].InnerText), 1) : 0;
             log.RIDE_DATE_ORIGEN = rideDateOrigen;

             utcTime = node["time"] != null ? node["time"].InnerText : string.Empty;
             localTime = DateTimeHelper.ConvertTimeTotime(dateformat, utcTime, "L", daySeq);
             log.LOG_TIME_ORIGEN = localTime;
             log.LOG_TIME = utcTime != string.Empty ? localTime.Substring(0, 19) : localTime;

             string gpxtx = "i:extensions/gpxtpx:TrackPointExtension/gpxtpx:{0}";

             // 기온 : Xml Path 
             var atemp = node.SelectSingleNode(string.Format(gpxtx, "atemp"), nsmgr);
             log.ATEMP = atemp != null ? Convert.ToDouble(atemp.InnerText) : 0;

             // 케이던스 : Xml Path 
             var cad = node.SelectSingleNode(string.Format(gpxtx, "cad"), nsmgr);
             log.CAD = cad != null ? Convert.ToDouble(cad.InnerText) : 0;

             // 심박수 : Xml Path 
             var heart = node.SelectSingleNode(string.Format(gpxtx, "hr"), nsmgr);
             log.HEART = heart != null ? Convert.ToDouble(heart.InnerText) : 0;

             // 좌표간 거리, 속도 계산
             if (listGpxLog.Count >= 1)
             {
                 // row Count
                 int iCount = listGpxLog.Count - 1;

                 // 좌표 위경도
                 double lat = listGpxLog[iCount].LAT;
                 double lng = listGpxLog[iCount].LNG;

                 // 좌표간 로그 시작시간과 끝시간
                 string startDate = listGpxLog[iCount].LOG_TIME;
                 string endDate = log.LOG_TIME;

                 // 좌표간 거리계산
                 double km = gpsCalc.Distance(lat, lng, log.LAT, log.LNG);

                 // 거리
                 log.KM = Utils.Common.NaNValue(km);

                 if (startDate != string.Empty && endDate != string.Empty)
                 {
                     // 좌표 A=>B까지 속도(km/h)
                     log.SPEED_KMH = Math.Round((km > 0 ? GpsLogCalculate.GetKph(km, startDate, endDate) : 0), 1);

                     // 좌표 A=>B까지 시간                    
                     log.DIFF_TIME = DateTimeHelper.GetTimeSpan(startDate, endDate).Seconds;
                 }
                 else
                 {
                     log.SPEED_KMH = 0;
                     log.DIFF_TIME = 0;
                 }
             }

             log.DAY_SEQ = daySeq.ToString();
             log.OVERLAP = overlap;   // DB 중복체크

             if(log.SPEED_KMH <= 300)
                 listGpxLog.Add(log);

             log = null;
         }

         // Way Point
         XmlNodeList wptNodes = xml.GetElementsByTagName("wpt");

         if (wptNodes.Count > 0)
         {
             int wptSeq = 0;

             foreach (XmlNode wpt in wptNodes)
             {
                 GpsLogData wp = new GpsLogData();

                 wptSeq++;

                 wp.LAT = Convert.ToDouble(wpt.Attributes["lat"].Value);
                 wp.LNG = Convert.ToDouble(wpt.Attributes["lon"].Value);
                 wp.ELE = wpt["ele"] != null ? Convert.ToDouble(wpt["ele"].InnerText) : 0;
                 wp.TITLE = wpt["name"].InnerText;
                 wp.RIDE_DATE_ORIGEN = rideDateOrigen;
                 wp.WAYPOINT = "Y";
                 wp.DAY_SEQ = wptSeq.ToString();

                 listGpxLog.Add(wp);
             }
         }

         return listGpxLog;
     }

https://taedi.kr/811

추출하면 위와 같은 형태의 데이터를 얻을 수 있다.

 

gpx : https://ko.wikipedia.org/wiki/GPX (위키백과)

 

 

 

댓글