wiki:rfc64_triangle_polyhedralsurface_tin

Version 2 (modified by meier, 8 years ago) ( diff )

--

Author: Avyav Kumar Singh
Contact: avyavkumar at gmail dot com
Status: Proposed

Summary

As of now, the OGRGeometry class (found at http://www.gdal.org/classOGRGeometry.html) (the base class from which all the subtypes are derived) is limited to OGRCompoundCurve, OGRCircularString, OGRLinearRing, OGRMultiLineString, OGRMultiPoint, OGRMultiPolygon, OGRMultiCurve, OGRSimpleCurve, OGRCurvePolygon and OGRPolygon.

This RFC addresses the addition of the following new geometries in OGRGeometry -

Triangle - A subset of polygons, the fundamental difference is that of 3 nodes only and ONLY ONE exterior boundary and NO interior polygons.

PolyhedralSurface - A 3D figure made exclusively of Polygons.

TriangulatedSurface - A subset of PolyhedralSurface; a 3D figure which consists exclusively of Triangles.

Reference documents


The following was consulted for the geometries -


Core changes


Some prelimenary work had already been done prior to this proposal, such as including the necessary WKB codes in <ogr_core.h>. Additionally, the SFCGAL library has been interfaced through OGR methods for implementing some methods from the API. For that, SFCGAL methods are introduced in OGRGeometry to convert SFCGAL geometries <-> OGR geometries.

static sfcgal_geometry_t* OGRexportToSFCGAL(OGRGeometry *poGeom);
static OGRGeometry* SFCGALexportToOGR(sfcgal_geometry_t* _geometry);

Besides SFCGAL, GEOS methods are still used in some cases, but with the following limitations - a Triangle is converted to a Polygon with one exterior ring; Polyhedral Surfaces and Triangulated Surfaces are converted to MultiPolygons. (each Triangle in a Triangulated Surface is converted to a Polygon as described previously)

The API for the new geometries introduced includes -

  • Overwriting existing methods for Polygon in the case of Triangle API. A complete API is provided below -
    class CPL_DLL OGRTriangle : public OGRPolygon
    {
      public:
        OGRTriangle();
        OGRTriangle(const OGRPoint &p, const OGRPoint &q, const OGRPoint &r);
        OGRTriangle(const OGRTriangle &other);
        OGRTriangle& operator=(const OGRTriangle& other);
        virtual ~OGRTriangle();
        const char *getGeometryName() const;
        virtual OGRwkbGeometryType getGeometryType() const;
    
        // IWks Interface
        virtual int WkbSize() const;
        virtual OGRErr importFromWkb( unsigned char *, int = -1, OGRwkbVariant=wkbVariantOldOgc );
        virtual OGRErr exportToWkb( OGRwkbByteOrder, unsigned char *, OGRwkbVariant=wkbVariantOldOgc ) const;
        virtual OGRErr importFromWkt( char ** );
        virtual OGRErr exportToWkt( char ** ppszDstText, OGRwkbVariant=wkbVariantOldOgc ) const;
    
        // New methods interfaced through SFCGAL or rewritten from OGRPolygon/OGRCurvePolygon/OGRGeometry
        virtual OGRGeometry *Boundary() const CPL_WARN_UNUSED_RESULT;
        virtual double Distance3D(const OGRGeometry *poOtherGeom) const;
        virtual OGRErr       Centroid( OGRPoint * poPoint ) const;
        virtual OGRBoolean  IsSimple() const;
        virtual OGRBoolean  IsRing() const;
        virtual OGRErr addRing  (OGRCurve *poNewRing);
        virtual OGRErr      PointOnSurface( OGRPoint * poPoint ) const;
        virtual OGRGeometry *Polygonize() const CPL_WARN_UNUSED_RESULT;
        virtual OGRGeometry *SymDifference( const OGRGeometry *poOtherGeom) const CPL_WARN_UNUSED_RESULT;
        virtual OGRBoolean  Touches( const OGRGeometry * ) const;
        virtual double      get_Area() const;
    };
    
  • The PolyhedralSurface API is derived from OGRSurface. Internally, it uses an OGRMultiPolygon to store all the Polygons comprising the Polyhedral Surface. Most of the implementations of the methods just reference corresponding OGRMultiPolygon methods with checks to ensure that conditions are maintained.
    class CPL_DLL OGRPolyhedralSurface : public OGRSurface
    {
      protected:
        OGRMultiPolygon oMP;
        virtual OGRSurfaceCasterToPolygon      GetCasterToPolygon() const;
        virtual OGRSurfaceCasterToCurvePolygon GetCasterToCurvePolygon() const;
        OGRErr exportToWktInternal (char ** ppszDstText, OGRwkbVariant eWkbVariant, const char* pszSkipPrefix ) const;
    
      public:
        OGRPolyhedralSurface();
        OGRPolyhedralSurface(const OGRPolyhedralSurface &poGeom);
        virtual ~OGRPolyhedralSurface();
        OGRPolyhedralSurface& operator=(const OGRPolyhedralSurface& other);
    
        // IWks Interface
        virtual int WkbSize() const;
        virtual const char *getGeometryName() const;
        virtual OGRwkbGeometryType getGeometryType() const;
        virtual OGRErr importFromWkb( unsigned char *, int=-1, OGRwkbVariant=wkbVariantOldOgc );
        virtual OGRErr exportToWkb( OGRwkbByteOrder, unsigned char *, OGRwkbVariant=wkbVariantOldOgc ) const;
        virtual OGRErr importFromWkt( char ** );
        virtual OGRErr exportToWkt( char ** ppszDstText, OGRwkbVariant=wkbVariantOldOgc ) const;
    
        // IGeometry methods
        virtual int getDimension() const;
    
        virtual void empty();
    
        virtual OGRGeometry *clone() const;
        virtual void getEnvelope(OGREnvelope * psEnvelope) const;
        virtual void getEnvelope(OGREnvelope3D * psEnvelope) const;
    
        virtual void flattenTo2D();
        virtual OGRErr transform(OGRCoordinateTransformation*);
        virtual OGRBoolean Equals(OGRGeometry*) const;
        virtual double get_Area() const;
        virtual OGRErr PointOnSurface(OGRPoint*) const;
    
        OGRMultiPolygon* CastToMultiPolygon();
        virtual OGRBoolean hasCurveGeometry(int bLookForNonLinear = FALSE) const;
        virtual OGRErr addGeometry( const OGRGeometry * );
        virtual OGRErr addGeometryDirectly(OGRGeometry *poNewGeom);
        virtual int getNumGeometries();
        virtual OGRGeometry* getGeometry(int i);
    
        virtual OGRBoolean  IsEmpty() const;
        virtual void setCoordinateDimension( int nDimension );
        virtual void set3D( OGRBoolean bIs3D );
        virtual void setMeasured( OGRBoolean bIsMeasured );
        virtual void swapXY();
        virtual double Distance3D(const OGRGeometry *poOtherGeom) const;
        virtual OGRErr removeGeometry( int iIndex, int bDelete = TRUE );
    };
    
  • The Triangulated Surface API is similar to Polyhedral Surface, and the MultiPolygon class was tweaked slightly to include methods to run which consisted of subgeometries of the form Triangle. (A MultiPolygon is strictly a collection of Polygons). These methods are internal to OGRMultiPolygon and cannot be accessed by a public user. For instance, the OGRMultiPolygon::addGeomtryDirectly method has a check that the subgeometry added to it should be of the type POLYGON. Rather than mess around with the existing function, a new function has been written which does not implement this check -
    /************************************************************************/
    /*                         _addGeometryDirectly()                       */
    /*      Only to be used in conjunction with OGRTriangulatedSurface.     */
    /*                        DO NOT USE IT ELSEWHERE.                      */
    /************************************************************************/
    
    OGRErr OGRMultiPolygon::_addGeometryDirectly( OGRGeometry * poNewGeom )
    {
        if ( wkbFlatten(poNewGeom->getGeometryType()) != wkbTriangle)
            return OGRERR_UNSUPPORTED_GEOMETRY_TYPE;
    
        if( poNewGeom->Is3D() && !Is3D() )
            set3D(TRUE);
    
        if( poNewGeom->IsMeasured() && !IsMeasured() )
            setMeasured(TRUE);
    
        if( !poNewGeom->Is3D() && Is3D() )
            poNewGeom->set3D(TRUE);
    
        if( !poNewGeom->IsMeasured() && IsMeasured() )
            poNewGeom->setMeasured(TRUE);
    
        OGRGeometry** papoNewGeoms = (OGRGeometry **) VSI_REALLOC_VERBOSE( papoGeoms,
                                                 sizeof(void*) * (nGeomCount+1) );
        if( papoNewGeoms == NULL )
            return OGRERR_FAILURE;
    
        papoGeoms = papoNewGeoms;
        papoGeoms[nGeomCount] = poNewGeom;
        nGeomCount++;
    
        return OGRERR_NONE;
    }
    
  • The Triangulated Surface API is as follows -
    class CPL_DLL OGRTriangulatedSurface : public OGRPolyhedralSurface
    {
      public:
        OGRTriangulatedSurface();
        OGRTriangulatedSurface(const OGRTriangulatedSurface &other);
        ~OGRTriangulatedSurface();
    
        OGRTriangulatedSurface& operator=(const OGRTriangulatedSurface& other);
        virtual const char *getGeometryName() const;
        virtual OGRwkbGeometryType getGeometryType() const;
    
        // IWks Interface
        virtual int WkbSize() const;
        virtual OGRErr importFromWkb(unsigned char *, int = -1, OGRwkbVariant=wkbVariantOldOgc);
        virtual OGRErr exportToWkb(OGRwkbByteOrder, unsigned char *, OGRwkbVariant=wkbVariantOldOgc) const;
        virtual OGRErr importFromWkt(char **);
        virtual OGRErr exportToWkt(char ** ppszDstText, OGRwkbVariant=wkbVariantOldOgc) const;
    
        virtual OGRGeometry *clone() const;
        virtual OGRErr addGeometry( const OGRGeometry * );
        virtual OGRErr addGeometryDirectly(OGRGeometry *poNewGeom);
        OGRMultiPolygon* CastToMultiPolygon();
    };
    

Geometry types

The new geometry WKB values can be seen as below -

Geometry Type 2D Z M ZM
PolyhedralSurface 0015 1015 2015 3015
TIN 0016 1016 2016 3016
Triangle 0017 1017 2017 3017


Changes in drivers

Some drivers were modified to be compatible with the new geometries. These include ->

  • PostGIS - No changes done to the driver explicitly, but it has been ensured that PG <-> OGR compatibility has been maintained. PostGIS 3D functions work on OGR, simple scripts work, for example from autotest/ogr/ogr_pg.py, we have -
    wkt_list = ['POLYHEDRALSURFACE (((0 0 0,0 0 1,0 1 1,0 1 0,0 0 0)),((0 0 0,0 1 0,1 1 0,1 0 0,0 0 0)),((0 0 0,1 0 0,1 0 1,0 0 1,0 0 0)),((1 1 0,1 1 1,1 0 1,1 0 0,1 1 0)),((0 1 0,0 1 1,1 1 1,1 1 0,0 1 0)),((0 0 1,1 0 1,1 1 1,0 1 1,0 0 1)))',
                    'TIN (((0 0 0,0 0 1,0 1 0,0 0 0)),((0 0 0,0 1 0,1 1 0,0 0 0)))',
                    'TRIANGLE ((48 36 84,32 54 64,86 11 54,48 36 84))' ]
    
    for i in range(0,3):
            gdaltest.pg_ds.ExecuteSQL( "INSERT INTO zgeoms (field_no, wkb_geometry) VALUES (%d,GeomFromEWKT('%s'))" % ( i, wkt_list[i] ) )
    
  • ShapeFile - TriangleStrip and TriangleFan objects in ShapeFile are parsed to OGRTriangulatedSurface. According to ESRI specs,
    • A TriangleStrip is a linked strip of triangles, where every vertex (after the first two) completes a new triangle. A new triangle is always formed by connecting the new vertex with its two immediate predecessors.
    • A TriangleFan is a linked fan of triangles, where every vertex (after the first two) completes a new triangle. A new triangle is always formed by connecting the new vertex with its immediate predecessor and the first vertex of the part.
    • Two different OGRTriangulatedSurface are maintained for each TriStrip and TriFan. If both are present in the file read, then an OGRGeometryCollection object is the result which consists of both OGRTriangulatedSurface which correspond to TriStrip and TriFan. (This is not strictly legal, since a TriangulatedSurface should not be a subgeometry of GeometryCollection)
  • GML - GML has been modified for both input and output -> Triangle, PolyhedralSurface and TriangulatedSurface are capable of being read/written from/to a GML document. Sample examples include -
    'TRIANGLE ((0 0,0 1,0 1,0 0))' is parsed to -
    '<gml:Triangle>
        <gml:exterior>
            <gml:LinearRing>
                <gml:posList>0 0 0 1 0 1 0 0</gml:posList>
            </gml:LinearRing>
        </gml:exterior>
    </gml:Triangle>'
    
    <gml:PolyhedralSurface>
       <gml:polygonPatches>
           <gml:PolygonPatch>
               <gml:exterior>
                   <gml:LinearRing>
                       <gml:posList srsDimension="3">1 2 3 4 5 6 7 8 9 1 2 3</gml:posList>
                   </gml:LinearRing>
               </gml:exterior>
           </gml:PolygonPatch>
           <gml:PolygonPatch>
               <gml:exterior>
                   <gml:LinearRing>
                       <gml:posList srsDimension="3">10 11 12 13 14 15 16 17 18 10 11 12</gml:posList>
                   </gml:LinearRing>
               </gml:exterior>
               <gml:interior>
                   <gml:LinearRing>
                       <gml:posList srsDimension="3">19 20 21 22 23 24 25 26 27 19 20 21</gml:posList>
                   </gml:LinearRing>
               </gml:interior>
           </gml:PolygonPatch>
       </gml:polygonPatches>
    </gml:PolyhedralSurface>"""
    
    gets parsed to 'POLYHEDRALSURFACE Z (((1 2 3,4 5 6,7 8 9,1 2 3)),((10 11 12,13 14 15,16 17 18,10 11 12),(19 20 21,22 23 24,25 26 27,19 20 21)))'
    
    Each PolygonPatch/Patch corresponds to one Polygon in a PolyhedralSurface.
    
    Finally, 'POLYHEDRALSURFACE EMPTY' parses to
    '<gml:PolyhedralSurface>
        <gml:polygonPatches>
        </gml:polygonPatches>
    </gml:PolyhedralSurface>'
    
  • DXF changes include converting a PolyFaceMesh (a subtype of PolyLine) to PolyhedralSurface. This is illustrated by a bug on the GDAL trac - https://trac.osgeo.org/gdal/ticket/6246. A PolyFace Mesh consists of points defined initially using specific codes, then these points are described as part of a polygon (a polygon can have four points at the maximum). Reading the PolyFace Mesh is supported in OGR as of now, but write support for it as well (though not implemented by me in this changeset) should be possible as well now.

Documentation

Using standard Doxygen documentation procedure.

Testing

Rigorous testing of all the use cases I can think of. Please feel free to add your own test to the autotest suite to check the stability of the code.

Implementation

Done by Avyav Kumar Singh, under the Google Summer of Code 2016 program.

Attachments (1)

Download all attachments as: .zip

Note: See TracWiki for help on using the wiki.