0

I want to generate Trimble GGF Geoid files from my software.

Can anyone provide me with a format specification for the binary ggf files used by Trimble?

I found a basic description on the OpenStreetMap wiki:

ORIGIN: top left
0 0 TNL GRID FILE\0 XXX\0*
1 0 TNL GRID FILE
0x30 8-> ymin (°) (Min Latitude in decimal degrees)
0x38 8-> ymax (°) (Max Latitude in decimal degrees)
0x40 8-> xmin (°) (Min Longitude in decimal degrees)
0x48 8-> xmax (°) (Max Longitude in decimal degrees)
0x50 8-> dy (°)    (Step size for latitudes, in decimal degrees)
0x58 8-> dx (°)  (Step size for longitudes, in decimal degrees)
0x60 4->  (vNY, nrows)
0x64 4->  (vNX, ncols)
       North Pole
       South Pole
       Missing Value (nodata)
       Scale
       Flags*
0x92+ data*

But some information is missing:

  • How is the data between 0x68 and 0x92 supposed to look?
  • How are the geoid heigth's supposes to be stored in the data* section?
    • Column-wise?
    • Row-wise?
    • South-West to North-East or the other way around?
  • 1
    Could you mafe it? im going crazy searching for an application to create local geoids, i can do it for leica but not for trimble and topcon – Joan Gonzalez Sep 20 '18 at 10:10
  • Yes, I was able to create a python script to generate ggf files. I can post it on monday, when I am back at the office. – steloe Sep 21 '18 at 20:32
0

As requested by Joan Gonzalez:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from dms import *
import copy
import struct
import binascii

import numpy as np

def dms2decdeg(grad,min,sek):
    grad=float(grad)
    min=float(min)
    sek=float(sek)
    return (grad + (min * 1/60) + (sek * 1/60 * 1/60))

def calculateRowsCols(grid_west_start,grid_south_start,grid_east_end,grid_north_end,gridwidth,gridheight):
    my_grid_west_start = copy.deepcopy(grid_west_start)
    my_grid_south_start = copy.deepcopy(grid_south_start)
    rows=0
    cols=0
    while my_grid_west_start.to_dd()<grid_east_end.to_dd():
        my_grid_west_start.add(0,0,gridwidth)
        cols+=1
    while my_grid_south_start.to_dd()<grid_north_end.to_dd():
        my_grid_south_start.add(0,0,gridheight)
        rows+=1
    return rows,cols

gridwidth_seconds = 30.0
gridheight_seconds = 30.0

#define Area
grid_west_start = dms(15.329297)
grid_east_end = dms(15.411587)
grid_south_start = dms(41.702492)
grid_north_end = dms(41.763123)

zeilen,spalten = calculateRowsCols(grid_west_start,grid_south_start,grid_east_end,grid_north_end,gridwidth_seconds,gridheight_seconds)
print "Zeilen: ",zeilen," Spalten: ",spalten

print "south: " + str(grid_south_start.to_dd()) + " " + str(dms2decdeg(50,36,30))


#Undulations in this Grid are stored from south to north (result from other workflow...)
#-> First row in grid is southernmost row
undulationen = np.empty([zeilen, spalten])
zeilen_akt=0
while zeilen_akt<zeilen:
    #GET UNDULATIONS FROM SOMEWHERE

print "first ",undulationen[0,0]," ",undulationen[0,-1]
print "last  ",undulationen[-1,0]," ",undulationen[-1,-1]

'''
https://wiki.openstreetmap.org/wiki/Trimble_GGF

ORIGIN: top left
0 0 TNL GRID FILE\0 XXX\0*
1 0 TNL GRID FILE
0x30 8-> ymin (°) (Min Latitude in decimal degrees)
0x38 8-> ymax (°) (Max Latitude in decimal degrees)
0x40 8-> xmin (°) (Min Longitude in decimal degrees)
0x48 8-> xmax (°) (Max Longitude in decimal degrees)
0x50 8-> dy (°)    (Step size for latitudes, in decimal degrees)
0x58 8-> dx (°)  (Step size for longitudes, in decimal degrees)
0x60 4->  (vNY, nrows)
0x64 4->  (vNX, ncols)
       North Pole <-- dec: 68
       South Pole
       Missing Value (nodata)
       Scale
       Flags*
0x92+ data*    <-- dec: 146

Data is stored as 4byte LE floats, the only known exception is OSU91.GGF (2byte integer format, probably some bit is set in the flags)
'''
with open("test.ggf", "wb") as f:
    print "#########GGF###########"

    for _ in range(48):
        f.write(bytearray(struct.pack('<c', '#')))

    f.write(bytearray(struct.pack('<d', float(grid_south_start.to_dd())))) #0x30 8-> ymin (°) (Min Latitude in decimal degrees)
    f.write(bytearray(struct.pack('<d', float(grid_north_end.to_dd())))) #0x38 8-> ymax (°) (Max Latitude in decimal degrees)
    f.write(bytearray(struct.pack('<d', float(grid_west_start.to_dd())))) #0x40 8-> xmin (°) (Min Longitude in decimal degrees)
    f.write(bytearray(struct.pack('<d', float(grid_east_end.to_dd())))) #0x48 8-> xmax (°) (Max Longitude in decimal degrees)
    f.write(bytearray(struct.pack('<d', float(dms2decdeg(0,0,gridheight_seconds))))) #0x50 8-> dy (°)    (Step size for latitudes, in decimal degrees)
    f.write(bytearray(struct.pack('<d', float(dms2decdeg(0,0,gridwidth_seconds))))) #0x58 8-> dx (°)  (Step size for longitudes, in decimal degrees)
    f.write(bytearray(struct.pack('<i', int(zeilen)))) #0x60 4->  (vNY, nrows)
    f.write(bytearray(struct.pack('<i', int(spalten)))) #0x64 4->  (vNX, ncols)

    #copy flags header from arbitrary existing 'official' ggf.
    with open("ggf_flags_header.ggf", "rb") as h:
        for _ in range((42)):
            f.write(h.read(1))

    zeilen_akt=0

    #Undulations in GGF File are written from north to south and west to east
    while zeilen_akt<zeilen:

        spalten_akt=0
        while spalten_akt<spalten:

            und = undulationen[zeilen-zeilen_akt-1,spalten_akt]
            f.write(bytearray(struct.pack('<f', float(und))))

            spalten_akt+=1
        zeilen_akt+=1

I cheated a little bit because I only copied the headers between 0x68 and 0x92 from existing files. I still have no Idea what the headers in this area are supposed to do.

For the script to run, you need to define the grid area (rows 34-37) and provide the undulation values (row 50).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.