OSM Map On Garmin/TRK Subfile Format

From OpenStreetMap Wiki
Jump to navigation Jump to search

OSM Map On Garmin

The TRK subfile format is found in Garmin's ADM files which are produced by marine chart plotters. A binary analysis of the ADM format reveals that its structure is identical to the IMG file format. The TRK sub file contains GPS tracks with coordinates, a timestamp, the depth, and the water temperature.

Parsetrk is a tool to decode TRK subfiles. Because TRK files typically do not exist on their own but are rather contained with ADM files they have to be separated by another tool first. This could be done with e.g. Splitimg or Mkgmap.

Most subfile types (as observed yet) share a common header. The first two bytes of that header define the header length. In case of TRK files this length is set to 0 and it does not have such a header. Furthermore, this would imply that the header length field does not include the field itself. All values found within the TRK subfile are encoded in little Endian format.

TRK Headers

The TRK file contains a header of variable length, a list of track points, and a trailer. The header is of variable length because the track name found in the header is of variable length. Thus for simplicity, the header is split into a first header of type adm_trk_header_t and a second header of type adm_trk_header2_t which directly follows the track name.

typedef struct adm_trk_header
{
  uint16_t hl;            //<! 0x000 common header length, = 0
  uint32_t len;           /*<! 0x002 total length (including this header
                                     + trk_header2 + all trackpoints) */
  int32_t a;
  char b;      // probably describes how many block descriptions are there
  int32_t c;
  int16_t d;
  uint32_t len1;          //<! 0x011 len - 15
  // block descriptions - probably dynamic
  uint32_t startbyteHeaderDescription // byte position where header description information starts 
  uint32_t numberOfHeaders // how many headers are present
  uint32_t startbyteDataDescription // byte position where data information starts
  uint32_t numberOfHeaders // how many datas are present
  uint32_t startbyteData // position where data starts
  uint32_t numberOfData // how many blocks of data? usally one?
  // sequence of data values and length
  // Values starting with 300 seem to be header, values with 500 seems to be repeated data 
  // this is probably dependent on what was actually recorded and may differ in size
  // this is determined by the previous information
  uint16_t trackname̠header // header data identifier 300 -> trackname
  uint16̜̜̜_t size           // size of trackname
  uint16_t unknownheader1 // header data identifier 301
  uint16̜̜̜_t size1           // size 2
  uint16_t unknownheader2 // header data identifier 302
  uint16̜̜̜_t size2           // size 1
  uint16_t unknownheader3 // header data identifier 303
  uint16̜̜̜_t size3           // size 1
  uint16_t unknownheader4 // header data identifier 304
  uint16̜̜̜_t size4           // size 4
  uint16_t latid // data identifier 500 -> lat
  uint16̜̜̜_t sizeLat           // size 4
  uint16_t lonid // data identifier 501 -> lon
  uint16̜̜̜_t sizeLon           // size 4
  uint16_t timestampid // data identifier 502 -> timestamp
  uint16̜̜̜_t sizeTimestamp           // size 4
  uint16_t depthid // data identifier 503 -> depth
  uint16̜̜̜_t sizeDepth           // size 4
  uint16_t unknownid // data identifier 504 -> ?
  uint16̜̜̜_t sizeUnknown           // size 1
  uint16_t temperatureid // data identifier 505 -> water temperature
  uint16̜̜̜_t sizeTemperatur           // size 4
  char name[];            //<! 0x059 track name
} __attribute__((packed)) adm_trk_header_t;

typedef struct adm_trk_header2
{
  uint16_t num_tp;        //<! number of trackpoints
  int32_t x;
  int16_t y;
} __attribute__((packed)) adm_trk_header2_t;

Most of the fields are unknown, yet. len contains the total length of the TRK header + all track points. In other words it actually points to the first byte behind all data which is the location of the trailer.

Important is the field name_len which contains the number of characters of the track name. The name is NOT \0-terminated. The second header adm_trk_header2_t starts at name_len bytes behind the first header, i.e. sizeof(adm_trk_header_t) (= 89) + name_len.

adm_trk_header2_t contains the field num_tp which is the number of track points found in the track. Since it is a 16 bit field it limits the number of track points to 65535.

Track Points

The first track point starts directly after the second track header. Each track point has a fixed length of 21 bytes of the following format.

#define ADM_EPOCH ((time_t) 631062000L+3600)
#define ADM_LON_SCALE 11930463.0783    //<! tolerance 1.016E-5 - -6.299E-6
#define ADM_LAT_SCALE ADM_LON_SCALE

typedef struct adm_track_point
{
  int32_t lat;         /*<! latitude and longitude linearly scaled by
                         ADM_LAT_SCALE and ADM_LON_SCALE */
  int32_t lon;
  int32_t timestamp;   /*<! timestamp starting at ADM_EPOCH after the epoch of
                         1.1.1970 */
  int32_t depth;       /*<! scaled depth, could be 2 int16_t fields as well
                         with the second one being the depth */
  char d;              //<! 0 or 1 at first point
  int32_t tempr;       //<! temperature
} __attribute__((packed)) adm_track_point_t; 

Latitude lat and longitude lon contain the geographic coordinates on the WGS84 ellipsoid scaled by the constant factors ADM_LAT_SCALE and ADM_LON_SCALE. This gives an accuracy of not roughly +-1 meter. To convert the timestamp to a Unix timestamp simply add the constant value ADM_EPOCH.

double lat = adm_track_point_t.lat / ADM_LAT_SCALE;
double lon = adm_track_point_t.lon / ADM_LON_SCALE;
time_t unix_time = adm_track_point_t.timestamp + ADM_EPOCH;

Trailer

The trailer seems always to be 10 bytes long probably of the following format.

typedef struct adm_trk_trailer
{
  uint16_t a;             //!< always 0x0001
  uint32_t b;             //!< always 0x0000000a
  uint32_t c;             //1< unknown, checksum?
} __attribute__((packed)) adm_trk_trailer_t;