PostGIS implements all the Simple Feature types specified by OGIS. The PostGIS objects are all 3-dimensional, so the representations are slightly different than those given in the OGIS standard. The PostGIS object types are as follows:
In addition, a " Box3d" type is included for indexing, querying and retrieval purposes.
The external representation for all PostGIS objects is the OGIS " Well Known Text" (WKT) representation. SQL selects return the GIS objects in WKT format, and SQL inserts must represent GIS objects in WKT. Examples of the WKT representations are as follows:
The internal objects representation stores all objects with three coordinates, but the external interface allows users to insert and retrieve 2-D objects without supplying a third coordinate. For example, a 2-D object can be inserted into the database:
template=# INSERT INTO GEOMETRIES (GEOM) VALUES ('POINT(2 3)'); INSERT 2415151 1
The same feature will be retrieved as a 2-D feature when selected out of the database:
template=# SELECT * FROM GEOMETRIES; geom -------- POINT(2 3) (1 row)
The internal representation information is useful to people who
intend to write C functions to directly manipulate the backend objects
at the server. Reading postgis.h
will give the exact type definitions
used, and reading postgis.c
will give some examples of how to manipulate
the objects.
In the most basic sense, a GEOMETRY type is a list of sub-objects and a bounding volume. In an ideal world, the type should be defined as:
typedef struct { int32 size; // postgres variable-length type requirement int32 type; // this type of geometry bool is3d; // true if the points are 3d (only for output) BOX3D bvol; // bounding volume of all the geo objects int32 nobjs; // how many sub-objects in this object int32 objType[1]; // type of object int32 objOffset[1]; // offset (in bytes) into this structure where the object is located char objData[1]; // store for actual objects } GEOMETRY;
However, because of the way PostgreSQL handles memory allocation for objects, the type is actually organized as:
typedef struct { int32 size; // postgres variable-length type requirement int32 type; // this type of geometry bool is3d; // true if the points are 3d (only for output) BOX3D bvol; // bounding volume of all the geo objects int32 nobjs; // how many sub-objects in this object char data[?]; //info about the sub-objects } GEOMETRY;
This is because a Postgres type is a just a chuck of memory -
it cannot contain any pointers. In a normal program objType
and objData
would be pointers to separate chunks of memory for these two lists
(in fact, objData
would likely be a list of pointers to other memory
locations). That would look something like:
Since we cannot have pointers, the geometry structure actually has pseudo-pointers to its data portion, and looks like:
There are several important observations:
bvol
and all the objects are double-aligned
(there is unused space between is3d
and bvol
, there could be unused
space between objOffset[n]
and obect 0, and there could
be unused space between object i and object i+1. objType[0]
is always &geometry->objType[0]
.objOffset[0]
is sizeof(int32)*geometry->nobjs
after objType[0]
.objOffset[0]
bytes
into the structure .sizeof(int32)*geometry->nobjs
bytes after
objOffset[0]
(could be 4 more bytes along if this isn't
double-aligned) .objOffset[1]
bytes
into the structure.Normally, one will want to find the location of objOffset[0] so its accessible;
Int32 *offsets; offsets = (int32 *) ( ((char *) &(geom1->objType[0] ))+ sizeof(int32) * geom1->nobjs ) ;
Next, one might want to get object i;
Char *obji; oi = (char *) geom1 +offsets[i] ; typei= geom1->objType[i];
Then one can re-cast the object;
POLYGON3D *poly; LINE3D *line; POINT3D *point; if (typei == POINTTYPE) //point { point = (LINE3D *) oi; ... } if (type1 == LINETYPE) //line { line = (LINE3D *) oi; ... } if (type1 == POLYGONTYPE) //POLYGON { poly = (POLYGON3D *) oi; ... }
What does everything mean?
Total size of the structure
Generic type for this Structure
This is mostly used so output knows how the object was originally entered into the system.
TRUE iff, when the object was created, any of the points were given in 3D. For example,
MULTIPOINT(1 1,2 2) -> is3d = FALSE
MULTIPOINT( 1 1, 2 2, 3 3 3) -> is3d = TRUE
Since POINT( 1 1) is stored as a POINT(1 1 0), we use the is3d flag during output to give POINT( 1 1) or POINT( 1 1 0). Its not used for anything else.
Bounding Volume for all the points in this geometry
How many point/line/polygons are in this geometry
The type of each of the sub-objects
#define POINTTYPE 1 #define LINETYPE 2 #define POLYGONTYPE 3
Offset, in bytes, into the structure where the object i is actually stored.
Data block with all the objects stored in it.
The geometry type tries really hard to consistently reproduce objects. For example, if you insert a MULTIPOINT(), you?ll get a MULTIPOINT() when you select.
This is more difficult than you might expect because, internally, these objects are stored the same:
MULTIPOINT( 1 1, 2 2, 3 3)
GEOMETRYCOLLECTION(POINT(1 1), POINT(2 2), POINT(3 3) )
GEOMETRYCOLLECTION(MULTIPOINT( 1 1, 2 2, 3 3))
GEOMETRYCOLLECTION(MULTIPOINT( 1 1, 2 2), POINT( 3 3) )
Every GEOMETRY is a set (GEOMETRYCOLLECTION) of sub-objects (point, line, polygon). So, truthfully, the 2nd example is the best OGC WKT of the internal structure.
Therefore, if you start with a MULTI-geometry (MULTIPOINT, MULTILINE, MULTIPOLYGON), the type member will record that fact. On output, it does a simple conversion:
GEOMETRYCOLLECTION(POINT(1 1),POINT(2 2))
becomes MULTIPOINT(1
1 , 2 2)
GEOMETRYCOLLECTION(LINESTRING(1 1,2 2),LINESTRING(10 10, 20 20))
becomes MULTILINESTRING((1 1,2 2), (10 10, 20 20))
GEOMETRYCOLLECTION(POLYGON((0 0, 0 1, 1 1, 0 0)), POLYGON((0
0, 0 10, 10 10 , 0 0) )
becomes MULTIPOLYGON( ((0 0, 0 1, 1 1, 0
0)), ((0 0, 0 10, 10 10 , 0 0)))
Single point, line, or polygon objects are returned as single point, line or polygon objects.
Unfortunately, GEOMETRYCOLLECTIONS() are not always returned the same as they are entered. They are always returned as a set of base types.
For example:
GEOMETRYCOLLECTION(POINT(1 1),POINT(2 2),POINT(3 3))
becomes
GEOMETRYCOLLECTION(POINT(1 1),POINT(2 2),POINT(3 3))
GEOMETRYCOLLECTION(MULTIPOINT(1 1,2 2,3 3))
becomes GEOMETRYCOLLECTION(POINT(1
1),POINT(2 2),POINT(3 3))
This is the base geometry used by all the other sub-points. Its simply the X,Y,Z location represented as a double-precision floating point number. 2D locations are represented with the Z location set to 0.0. See above for a discussion on how to tell the difference between a 2D point, and 3D points with Z=0.0.
typedef struct { double x,y,z; } POINT3D;
A lines is a set of connected points. The line moves from points[0]->points[1]->points[2]
typedef struct { int32 npoints; // how many points in the line int32 junk; // double-word alignment POINT3D points[1]; // array of actual points } LINE3D;
A polygon is a set of rings. Each ring is a closed line.
Npoints[i]
is the number of points in ring i. &points[0]
is at &polygon ->npoints[0]
+ sizeof(int32)*polygon->nrings
&points[0]
, ensure it is
double-alignedFor example:
POINT3D *pts; pts = (POINT3D *) ( (char *)&(poly->npoints[poly->nrings] ) ); if ((int32) pts1 != (((int32)pts1>>3)<<3) ) pts1 = (POINT3D *) ((char *) pts1 + 4);
Therefore,
points[0]
to points[ npoint[0]
-1]
points[npoint[0]]
to points[npoints[0] + npoints[1] -1]
typedef struct { int32 nrings; // how many rings in this polygon int32 npoints[1]; //how many points in each ring /* could be 4 byes of filler here to make sure points[] is double-word aligned*/ POINT3D points[1]; // array of actual points } POLYGON3D;
Representation of a long diagonal of a cube, from the lower-left bottom (LLB) to the upper-right top (URT).
Minimum X is box3d->LLB.x Minimum Y is box3d->LLB.y Minimum Z is box3d->LLB.z Maximum X is box3d->URT.x Maximum y is box3d->URT.y Maximum Z is box3d->URT.z typedef struct { POINT3D LLB,URT; /* corner POINT3Ds on long diagonal */ } BOX3D;