Intro to Hacking LandXML Surfaces

LandXML Surface Background

The LandXML standard is an open standard used by CAD and GIS software that allows the transfer of data between different proprietary software formats. The standard was originally launched in 2000, with the latest standard format being version 2.0, but we will be working with the more common format version of LandXML 1.2.

LandXML Surface Schema LandXML Surface Schema

LandXML Surface Definition Schema Surface Definition

When you strip all of the optional formatting away, the LandXML Surface format is relatively simple with a collection of points and a unique id followed by triangles defined based on the point id.

      <Definition surfType="TIN">
          <P id="13202">539321.086 1423047.314 13.266</P>
          <P id="13203">539289.057 1422960.960 14.705</P>
          <P id="13204">539275.012 1422945.435 22.171</P>
          <P id="13205">539273.976 1422947.540 22.653</P>
          <P id="13206">539289.328 1422953.332 14.841</P>
          <F>13066 13049 13050</F>
          <F>13067 13053 13049</F>
          <F>13300 13296 13050</F>
          <F>13296 13066 13050</F>
          <F>13073 13054 13072</F>
          <F>12199 13072 13054</F>
          <F>12207 12199 13054</F>

Digging a Little Deeper

Now that we have a better understanding of the basic format, let's put that to use. The LandXML format is at its core a simple xml file, so we are going to use the lxml library to read and manipulate the LandXML surface. If you don't have the lxml library, just pip install lxml and you are ready to get started.

If you have any experience working with AutoCAD's Civil 3D or Microstation InRoads, you might recognize that they both have slightly different flavors of the LandXML surface format. One difference is the definition of internal faces that Civil 3D defines for its surfaces. Notice the "i" designation below for certain faces. Civil 3D ignores those triangle faces with an "i", while most other CAD software reads it as a valid triangle face.

          <F n="25870 25867 25874">13066 13049 13050</F>
          <F i="1" n="0 25866 25870">13067 13053 13049</F>
          <F n="26299 25874 25868">13300 13296 13050</F>
          <F n="26132 25878 25879">13319 13198 13056</F>
          <F n="25887 25885 25882">13059 13058 13057</F>
          <F i="1" n="25892 25881 25883">13083 13059 13057</F>
          <F i="1" n="25882 25884 25931">13083 13057 13079</F>

If we wanted to read a LandXML surface and remove those internal or "i" faces, we could use the following code...

import lxml.etree as et

with open("landxml_in_file", 'rb') as in_file:
    with open("landxml_out_file", 'rb') as out_file:
        xml ='iso-8859-1').encode('ascii')
        doc = et.XML(xml)
        surfaces = doc.getchildren()[-1]

        # LandXML could contain multiple surfaces
        for surface in surfaces:
            definition = surface.getchildren()[-1]  #Selecting surface definition
            faces = definition.getchildren()[-1]    #Selecting faces

            for face in faces:
                # If faces field includes an 'i' key, then it should be
                # removed from the surface
                if 'i' in face.attrib:

        # Creating output landxml without internal faces and enabling 
        # pretty print which is easier to read and edit.
        out_xml = et.tostring(doc, encoding="UTF-8", pretty_print=True)

One final note about the LandXML format that is important is that MicroStation's InRoads program does not actually import the faces or triangles into the surface. It does import the points themselves. So if the triangles themselves are important to the accuracy of your surface (which they often are), make sure that you include 3D breaklines as features in your LandXML. Civil3D does read the actual triangles themselves as you would expect.

In the next post, we will walk through a more in depth example on how to convert an ESRI TIN to a LandXML surface using open source tools.

Comments !