File Format: ITF

ITF stands for "Intermediate TIN Format."  It is a simple, efficient, geospatially aware standard file format for Triangular Irregular Networks (TINs).

Motivation and history

Before ITF, there was no standard, documented file format for geospatial TINs.  The format was proposed and implemented by Ben Discoe via the Virtual Terrain Project in 2003.

In the CAD world, an AutoCAD DXF could be used to store a TIN, but DXF is very inefficient, seldom georeferenced, and does not support a clean topology of shared edges.  In the GIS world, ArcGIS has no TIN format, and ArcInfo has a TIN format which is proprietary (tnxy.adf, tnz.adf, tnod.adf), undocumented, and not widely used.  (As a side note, when a reverse-engineered description of ArcInfo TIN came to light in 2007, its structure was found to be extremely similar to ITF, except spread out in a directory of files.)

Current format spec: ITF Version 2.0

The file begins with a header which contains the following:

Field Length
in Bytes
Description
Identifier 5 A marker which indicates that this is an ITF file.
For version 2.0 of the format, it contains the characters "tin02".
Num_Vertices 4 (int) Number of vertices.
Num_Triangles 4 (int) Number of triangles.
Data_Start 4 (int) File offset where the data starts.  A parser should seek to this position to read it.
CRS_Length 4 (int) Length in bytes of the ASCII string which contains the CRS.
CRS CRS_Length Description of a coordinate reference system (CRS) in the OpenGIS Well-Known Text (WKT) format.
Extents 8 (double) * 4 Horizontal extents: left, top, right, bottom
4 (float) * 2 Vertical extents: minimum height, maximum height

Next is the data itself, vertices then triangles.  Each vertex is encoded this way:

Field Length Description
X 8 (double) X coordinate (easting)
Y 8 (double) Y coordinate (northing)
Z 4 (float) Z coordinate (elevation)

Next is the triangles, which each contain the indices of their three vertices:

Field Length Description
Vertex 0 4 (int) Index of the first corner.
Vertex 1 4 (int) Index of the second corner.
Vertex 2 4 (int) Index of the third corner.

All numeric values use little-endian encoding (Intel, not Motorola).
The ASCII string for the CRS does not include a NULL at the end.

Notes

Note that the use of Data_Start in the header allows for the possibility of fields being added to the header in the future, retaining full backward compatibility.  Parsers expecting an older format will still find the header fields and data exactly as expected.

Previous Versions

For version 1.0 of the format, the Identifier contains the characters "tin01".  The 'Extents' in the header are not present.

Sample File

Code

Here is an excerpt from the vtdata C/C++ open-source library which shows how simple it is to read and write ITF 1.0:

bool vtTin::Read(const char *fname)
{
   FILE *fp = fopen(fname, "rb");
   if (!fp)
      return
false;

   int
i, verts, tris, data_start, proj_len;
   char marker[5];
   fread(marker, 5, 1, fp);
   fread(&verts, 4, 1, fp);
   fread(&tris, 4, 1, fp);
   fread(&data_start, 4, 1, fp);
   fread(&proj_len, 4, 1, fp);
   if (proj_len > 4000)
      return false;

   char
wkt_buf[4000], *wkt = wkt_buf;
   fread(wkt, proj_len, 1, fp);
   wkt_buf[proj_len] = 0;
   OGRErr err = m_proj.importFromWkt((char **) &wkt);
   if (err != OGRERR_NONE)
      return false;

   fseek(fp, data_start, SEEK_SET);

   // read verts

   DPoint2 p;
   float z;
   for (i = 0; i < verts; i++)
   {
      fread(&p.x, 8, 2, fp); // 2 doubles
      fread(&z, 4, 1, fp); // 1 float
      AddVert(p, z);
   }

   // read tris

   int tribuf[3];
   for (i = 0; i < tris; i++)
   {
      fread(tribuf, 4, 3, fp); // 3 ints
      AddTri(tribuf[0], tribuf[1], tribuf[2]);
   }

   fclose(fp);
   return true;
}

bool
vtTin::Write(const char *fname) const
{
   FILE *fp = fopen(fname, "wb");
   if (!fp)
      return false;

   char
*wkt;
   OGRErr err = m_proj.exportToWkt(&wkt);
   if (err != OGRERR_NONE)
   {
      fclose(fp);
      return false;
   }

   int
proj_len = strlen(wkt);
   int data_start = 5 + 4 + 4 + 4 + + 4 + proj_len;
   int i;
   int verts = NumVerts();
   int tris = NumTris();
   fwrite("tin01", 5, 1, fp);
   fwrite(&verts, 4, 1, fp);
   fwrite(&tris, 4, 1, fp);
   fwrite(&data_start, 4, 1, fp);
   fwrite(&proj_len, 4, 1, fp);
   fwrite(wkt, proj_len, 1, fp);
   OGRFree(wkt);

   // write verts
   for (i = 0; i < verts; i++)
   {
      fwrite(&m_vert[i].x, 8, 2, fp); // 2 doubles
      fwrite(&m_z[i], 4, 1, fp); // 1 float
   }

  
// write tris

   for (i = 0; i < tris*3; i++)
   {
      fwrite(&m_tri[i], 4, 1, fp); // 1 int
   }

  
fclose(fp);
   return true;
}