postgis/doc/postgis.xml
Paul Ramsey 76a87e711b Typo in SQL example fixed.
git-svn-id: http://svn.osgeo.org/postgis/trunk@59 b70326c6-7e19-0410-871a-916f4a2858ee
2001-08-30 22:24:17 +00:00

884 lines
52 KiB
XML

<?xml version="1.0"?>
<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook V3.1//EN">
<book>
<title>PostGIS Manual</title>
<bookinfo>
<editor><firstname>Paul</firstname><surname>Ramsey</surname><affiliation><orgname><ulink url="http://www.refractions.net">Refractions Research Inc</ulink></orgname><address><street>209 - 560 Johnson Street</street><city>Victoria</city><state>British Columbia</state><country>Canada</country><email>pramsey@refractions.net</email></address></affiliation></editor><abstract>
<para>PostGIS is an extension to the PostgreSQL object-relational database
system which allows GIS (Geographic Information Systems) objects to be stored
in the database. PostGIS includes support for GiST indexes, and functions for
basic analysis of GIS objects.</para>
</abstract>
</bookinfo>
<chapter>
<title>Introduction</title>
<para>PostGIS is developed by Refractions Research Inc, as a research
project into spatial database technology. Refractions is a GIS and database
consulting company in Victoria, British Columbia, specializing in data
integration and custom software development. We plan on supporting and
developing PostGIS to support a range of important GIS functionality, including
advanced topological constructs (coverages, surfaces, networks), desktop user
interface tools for viewing and editing GIS data, and web-based access
tools.</para>
<sect1>
<title>Credits</title>
<variablelist>
<varlistentry>
<term>Dave Blasby &lt;dblasby@refractions.net&gt;</term>
<listitem><para>The principal developer of PostGIS. Dave maintains the
server side objects and index support, as well as the server side analytical
functions.</para></listitem>
</varlistentry>
<varlistentry>
<term>Paul Ramsey &lt;pramsey@refractions.net&gt;</term>
<listitem><para>Maintains the JDBC objects and keeps track of the documentation and packaging.</para></listitem>
</varlistentry>
<varlistentry>
<term>Jeff Lounsbury &lt;jeffloun@refractions.net&gt;</term>
<listitem><para>Maintains the Shape loader/dumper.</para></listitem>
</varlistentry>
</variablelist>
</sect1>
<sect1>
<title>More Information</title>
<itemizedlist>
<listitem><para>The latest software, documentation and news items are available at the PostGIS web site, <ulink url="http://postgis.refractions.net">http://postgis.refractions.net</ulink>.</para></listitem>
<listitem><para>More information about the PostgreSQL database server is available at the PostgreSQL main site <ulink url="http://www.postgreql.org">http://www.postgreql.org</ulink>.</para></listitem>
<listitem><para>More information about GiST indexing is available at the GiST development site, <ulink url="http://www.sai.msu.su/~megera/postgres/gist">http://www.sai.msu.su/~megera/postgres/gist</ulink>.</para>
</listitem>
</itemizedlist>
</sect1>
</chapter>
<chapter>
<title>Installation</title>
<sect1 id="PGInstall">
<title>PostGIS</title>
<para>The PostGIS module is a extension to the PostgreSQL backend server.
As such, PostGIS <emphasis>requires</emphasis> a full copy of the PostgreSQL
source tree in order to compile. The PostgreSQL source code is available at<ulink url="http://www.postgresql.org">http://www.postgresql.org</ulink>.</para>
<para>PostGIS 0.5 has been built and tested against PostgreSQL 7.1.2. It
will probably work with any of the 7.1.x versions. It will
<emphasis>not</emphasis> work with any version prior to 7.1.</para>
<orderedlist>
<listitem><para>Before you can compile the postgis server modules, you must
compile and install the PostgreSQL package. </para></listitem>
<listitem><para>Retrieve the PostGIS source archive from http://postgis.refractions.net/postgis-0.5.tar.gz. Uncompress and untar the
archive in the "contrib" directory of the PostgreSQL source tree.</para><literallayout>cd [postgresql source tree]/contrib
zcat postgis-0.5.tar.gz | tar xvf -</literallayout>
</listitem>
<listitem><para>Once your PostgreSQL installation is up-to-date, enter the
"postgis" directory, and run the compile and install commands. </para><literallayout>cd ./postgis-0.5
make
make install</literallayout>
</listitem>
<listitem><para>Before you can use the PostGIS objects in your database, you
must load the object and function definitions. </para><literallayout>psql -d [yourdatabase] &lt; postgis.sql</literallayout>
<para>The PostGIS server extensions are now loaded and ready to
use.</para>
</listitem>
</orderedlist>
</sect1>
<sect1>
<title>JDBC</title>
<para>The JDBC extensions provide Java objects corresponding to the
internal PostGIS types. These objects can be used to write Java clients which
query the PostGIS database and draw or do calculations on the GIS data in
PostGIS.</para>
<orderedlist>
<listitem><para>Enter the "jdbc" sub-directory of the PostGIS distribution.
</para></listitem>
<listitem><para>Edit the "Makefile" to provide the correct paths of your java
compiler (JAVAC) and interpreter (JAVA).
</para></listitem>
<listitem><para>Run the "make" command. Copy the "postgis.jar" file to
wherever you keep your java libraries.</para>
</listitem>
</orderedlist>
</sect1>
<sect1>
<title>Loader/Dumper</title>
<para>The data loader should compile very simple.</para><literallayout>cd postgis-0.5/loader
make</literallayout><para>The loader is called "shp2pgsql" and converts ESRI Shape files into SQL suitable for loading in PostGIS/PostgreSQL.</para>
</sect1>
</chapter>
<chapter>
<title>Frequently Asked Questions</title>
<qandaset>
<qandaentry>
<question><para>What kind of geometric objects can I store?</para></question>
<answer><para>You can store point, line, polygon, multipoint, multiline,
multipolygon, and geometrycollections. These are specified in the Open GIS Well
Known Text Format (with 3d extentions).</para></answer>
</qandaentry>
<qandaentry>
<question><para>How do I insert a GIS object into the database?</para></question>
<answer><para>First, you need to create a table with a column of type "geometry"
to hold your GIS data. Connect to your database with "psql" and try the
following SQL: <emphasis>CREATE TABLE gtest ( ID int4, NAME varchar(20), GEOM
geometry)</emphasis>. If the table creation fails, you probably have not loaded
the PostGIS functions and objects into this database. See the
<link linkend="PGInstall">installation instructions</link>.</para>
<para>Then, you can insert a geometry into the table using a SQL insert
statement. The GIS object itself is formatted using the OpenGIS Consortium
"well-known text" format: <emphasis>INSERT INTO gtest (ID, NAME, GEOM) VALUES
(1, 'First Geometry', 'LINESTRING(2 3,4 5,6 5,7 8)')</emphasis>. For more
information about other GIS objects, see the <link linkend="RefObject">object
reference</link>.</para>
<para>To view your GIS data in the table: <emphasis>SELECT * FROM
gtest</emphasis>. The return value should look something like this:</para> <literallayout> id | name | geom
----+----------------+-----------------------------
1 | First Geometry | LINESTRING(2 3,4 5,6 5,7 8)
(1 row)
</literallayout>
</answer>
</qandaentry>
<qandaentry>
<question><para>How do I construct a spatial query?</para></question>
<answer><para>There are a number of spatial operators available to PostgreSQL,
and several of them have been implemented by PostGIS in order to provide
indexing support. However, all the operators have been implemented with the
following important simplifying assumption: <emphasis>all features shall be
represented by their bounding boxes</emphasis>.</para>
<para>We recognize that using bounding boxes to proxy for features is a
limiting assumption, but it was an important one in moving from the conception
of a PostgreSQL spatial database to the implementation. Using bounding boxes
makes queries faster, indexes smaller, and operators simpler.</para>
<para>The most important spatial operator from a user's perspective is
the "&amp;&amp;" operator, which tests whether one feature's bounding box
overlaps that of another. An example of a spatial query using &amp;&amp; is:
<emphasis>SELECT * FROM GTEST WHERE GEOM &amp;&amp; 'BOX3D(3 4,4
5)'::box3d</emphasis>. Note that the bounding box used for querying must be
explicitly declared as a "box3d" using the "::box3d" casting operation.</para>
</answer>
</qandaentry>
<qandaentry>
<question><para>How do I speed up spatial queries on large tables?</para></question>
<answer><para>Fast queries on large tables is the <emphasis>raison
d'etre</emphasis> of spatial databases (along with transaction support) so
having a good index in important.</para>
<para>To build a spatial index on a table with a "geometry" column, use
the "CREATE INDEX" function as follows: <emphasis>CREATE INDEX [indexname] ON
[tablename] USING GIST ( [geometrycolumn] gist_geometry_ops) WITH (
islossy)</emphasis>.</para>
<para>The "USING GIST" option tells the server to use a GiST (Generalized
Search Tree) index. The reference to "gist_geometry_ops" tells the server to
use a particular set of comparison operators for building the index: the
"gist_geometry_ops" are part of the PostGIS extension. Finally, the "islossy"
option tells the server that the features being indexed can be proxied by a
smaller data structure -- in the case of geometries, the features are
represented in the index by their bounding boxes.</para>
</answer>
</qandaentry>
<qandaentry>
<question><para>How can I get my search to return things that really are inside
the search box, not just overlapping bounding boxes?</para></question>
<answer><para>The '&amp;&amp;' operator only checks bounding box overlaps, but
you can use the "truly_inside()" function to get only those feature which
<emphasis>actually</emphasis> intersect the search box. For example, by
combining the use of "&amp;&amp;" for a fast index search and truly_inside()
for an accurate final check of the result set, you can get only those features
inside the search box (note that this <emphasis>only</emphasis> works for
search boxes right now, not any arbitrary geometry):</para>
<para><emphasis>SELECT * FROM [TABLE] WHERE [GEOM_COLUMN] &amp;&amp;
[BOX3d] AND truly_inside([GEOM_COLUMN],[BOX3d]);</emphasis></para>
</answer>
</qandaentry> <qandaentry>
<question><para>Why aren't R-Tree's recommended?</para></question>
<answer><para>R-Trees have two limitations which make them undesirable for use
with GIS feature in the PostgreSQL database (note that these limitations are
due to the PostgreSQL implementations, not the R-Tree concept in
general):</para>
<itemizedlist>
<listitem><para>Building an R-Tree index on a large table of geometries can
take over twice as long as a GiST index on the same table.</para>
</listitem>
<listitem><para>R-Tree indexes in PostgreSQL cannot handle features which are
larger than 8K in size. GiST indexes can, using the "lossy" trick of
substituting the bounding box for the feature itself.</para>
</listitem>
</itemizedlist>
</answer>
</qandaentry>
</qandaset>
</chapter>
<chapter>
<title>Using PostGIS</title>
<sect1 id="RefObject">
<title>GIS Objects</title>
<para>The GIS objects supported by PostGIS are the "Simple Features"
defined by the OpenGIS Consortium (OGC). Note that PostGIS currently support
the features and the representation APIs, but the various comparison and
convolution operators given in the OGC "Simple Features for SQL"
specification.</para>
<para>Examples of the text representations of the features are as
follows:</para>
<itemizedlist>
<listitem><para>POINT(0 0 0)</para>
</listitem>
<listitem><para>LINESTRING(0 0,1 1,1 2)</para>
</listitem>
<listitem><para>POLYGON((0 0 0,4 0 0,4 4 0,0 4 0,0 0 0),(1 1 0,2 1 0,2 2 0,1
2 0,1 1 0))</para>
</listitem>
<listitem><para>MULTIPOINT(0 0 0,1 2 1)</para>
</listitem>
<listitem><para>MULTILINESTRING((0 0 0,1 1 0,1 2 1),(2 3 1,3 2 1,5 4 1))</para>
</listitem>
<listitem><para>MULTIPOLYGON(((0 0 0,4 0 0,4 4 0,0 4 0,0 0 0),(1 1 0,2 1 0,2
2 0,1 2 0,1 1 0)),((-1 -1 0,-1 -2 0,-2 -2 0,-2 -1 0,-1 -1 0)))</para>
</listitem>
<listitem><para>GEOMETRYCOLLECTION(POINT(2 3 9),LINESTRING((2 3 4,3 4 5)))</para>
</listitem>
</itemizedlist>
<para>Note that in the examples above there are features with both
2-dimensional and 3-dimensional coordinates. PostGIS supports both 2d and 3d
coordinates -- if you describe a feature with 2D coordinates when you insert
it, the database will return that feature to you with 2D coordinates when you
extract it. See the sections on the 2d() and 3d() functions for information on
converting features to a particular coordinate dimension representation.</para>
</sect1>
<sect1>
<title>Creating a Table with Geometry</title>
<para>Creating a table with spatial data is done using the "CREATE TABLE"
SQL command as normal, declaring the spatial columns as type "geometry". Note
that:</para>
<itemizedlist>
<listitem><para>There is no distinction between points, lines and polygons at
the SQL datatype level. All GIS objects are declared as "geometry", no matter
when their underlying topology. This means that you can have points, lines, and
polygons all sharing the same spatial column of a database table. If you are
making assumptions about feature type homogeneity, you will have to enforce
them at the time of data loading or retrieval, the database will not do it for
you.</para>
</listitem>
<listitem><para>Unlike some commercial spatial databases, there is no
limitation on the number of spatial columns you can put in a table.</para>
</listitem>
<listitem><para>"Geometry" is a reserved word now (it is the GIS datatype) so
you cannot name your spatial column "geometry". Try "geom" or "parks" or
"frank".</para>
</listitem>
</itemizedlist>
<para>Here are some examples of "CREATE TABLE" commands using the
"geometry" type:</para>
<itemizedlist>
<listitem><para>CREATE TABLE bc_parks ( PID int4, PIN int4, PARK_NAME
varchar(80), PARK_DATE date, PARK_GEOM geometry)</para>
</listitem>
<listitem><para>CREATE TABLE road_segments ( GID int4, GEOM geometry, SURFACE
varchar(20), NUM_LANES int2)</para>
</listitem>
</itemizedlist>
</sect1>
<sect1>
<title>Loading GIS Data</title>
<para>Once you have created a spatial table, you are ready to upload GIS
data to the database. Currently, there are two ways to get data into a
PostGIS/PostgreSQL database: using formatted SQL statements or using the Shape
file loader/dumper.</para>
<sect2>
<title>Using SQL</title>
<para>If you can convert your data to a text representation, then using
formatted SQL might be the easiest way to get your data into PostGIS. As with
Oracle and other SQL databases, data can be bulk loaded by piping a large text
file full of SQL "INSERT" statements into the SQL terminal monitor.</para>
<para>A data upload file ("roads.sql" for example) might look like
this:</para> <literallayout>INSERT INTO ROADS_GEOM (ID,GEOM,NAME ) VALUES (1,'LINESTRING(191232 243118,191108 243242)','Jeff Rd');
INSERT INTO ROADS_GEOM (ID,GEOM,NAME ) VALUES (2,'LINESTRING(189141 244158,189265 244817)','Geordie Rd');
INSERT INTO ROADS_GEOM (ID,GEOM,NAME ) VALUES (3,'LINESTRING(192783 228138,192612 229814)','Paul St');
INSERT INTO ROADS_GEOM (ID,GEOM,NAME ) VALUES (4,'LINESTRING(189412 252431,189631 259122)','Graeme Ave');
INSERT INTO ROADS_GEOM (ID,GEOM,NAME ) VALUES (5,'LINESTRING(190131 224148,190871 228134)','Phil Tce');
INSERT INTO ROADS_GEOM (ID,GEOM,NAME ) VALUES (6,'LINESTRING(198231 263418,198213 268322)','Dave Cres');
</literallayout>
<para>The data file can be piped into PostgreSQL very easily using the
"psql" SQL terminal monitor:</para><literallayout>psql -d [database] -f roads.sql</literallayout>
</sect2>
<sect2>
<title>Using the Loader</title>
<para>The data loader converts ESRI Shape files into SQL suitable for insertion into a PostGIS/PostgreSQL database. The loader has several operating modes distinguished by command line flags:</para><variablelist><varlistentry><term>-d</term><listitem><para>Drops the database table before creating a new table with the data in the Shape file.</para></listitem></varlistentry><varlistentry><term>-a</term><listitem><para>Appends data from the Shape file into the database table. Note that to use this option to load multiple files, the files must have the same attributes and same data types.</para></listitem></varlistentry><varlistentry><term>-c</term><listitem><para>Creates a new table and populates it from the Shape file. <emphasis>This is the default mode.</emphasis></para></listitem></varlistentry><varlistentry><term>-dump</term><listitem><para>Creates a new table and populates it from the Shape file. This uses the PostgreSQL "dump" format for the output data and is much faster to load than the default "insert" SQL format. Use this for very large data sets.</para></listitem></varlistentry></variablelist><para>An example session using the loader to create an input file and uploading it might look like this:</para><literallayout>shp2pgsql shaperoads roadstable &gt; roads.sql
psql -d roadsdb -f roads.sql</literallayout><para>A conversion and upload can be done all in one step using UNIX pipes:</para><literallayout>shp2pgsql shaperoads roadstable | psql -d roadsdb</literallayout></sect2>
</sect1>
<sect1>
<title>Retrieving GIS Data</title>
<para>Data can be extracted from the database using either SQL or the
Shape file loader/dumper. In the section on SQL we will discuss some of the
operators available to do comparisons and queries on spatial tables.</para>
<sect2>
<title>Using SQL</title>
<para>The most straightforward means of pulling data out of the
database is to use a SQL select query and dump the resulting columns into a
parsable text file:</para> <literallayout>db=# SELECT * FROM ROADS_GEOM;
id | geom | name
1 | LINESTRING(191232 243118,191108 243242) | Jeff Rd
2 | LINESTRING(189141 244158,189265 244817 | Geordie Rd
3 | LINESTRING(192783 228138,192612 229814) | Paul St
4 | LINESTRING(189412 252431,189631 259122) | Graeme Ave
5 | LINESTRING(190131 224148,190871 228134) | Phil Tce
6 | LINESTRING(198231 263418,198213 268322) | Dave Cres
(6 rows)
db=#
</literallayout>
<para>However, there will be times when some kind of restriction is
necessary to cut down the number of fields returned. In the case of
attribute-based restrictions, just use the same SQL syntax as normal with a
non-spatial table. In the case of spatial restrictions, the following operators
are available/useful:</para>
<variablelist><varlistentry><term>&amp;&amp;</term><listitem><para>This operator tells whether the bounding box of one geometry
overlaps the bounding box of another.</para>
</listitem></varlistentry><varlistentry><term>~=</term><listitem><para>This operators tests whether two geometries are geometrically
identical. For example, if "POLYGON((0 0,1 1,1 0,0 0))" is the same as
"POLYGON((0 0,1 0,1 1,0 0))" (it is).</para>
</listitem></varlistentry><varlistentry><term>=</term><listitem><para>This operator is a little more naive, it only tests whether
the bounding boxes of to geometries are the same.</para>
</listitem></varlistentry></variablelist><para>Next, you can use these operators in queries. Note that when
specifying geometries and boxes on the SQL command line, you should explicitly
cast the string representations to their correct type using the "::" casting
operator. So, for example:</para><literallayout>SELECT * FROM ROADS_GEOM WHERE GEOM ~= 'LINESTRING(191232 243118,191108 243242)'::geometry;</literallayout>
<para>The above query would return the single record from the
"ROADS_GEOM" table in which the geometry was equal to that value.</para>
<para>When using the "&amp;&amp;" operator, you can specify either a
BOX3D as the comparison feature or a GEOMETRY. When you specify a GEOMETRY,
however, the bounding box will be used for the comparison.</para><literallayout>SELECT * FROM ROADS_GEOM WHERE GEOM &amp;&amp; 'POLYGON((191232 243117,191232 243119,191234 243117,191232 243117))'::geometry;</literallayout>
<para>The above query will use the bounding box of the polygon for
comparison purposes.</para>
<para>The most common spatial query will probably be a "frame-based"
query, used by client software, like data browsers and web mappers, to grab a
"map frame" worth of data for display. Using a "BOX3D" object for the frame,
such a query looks like this:</para> <literallayout>SELECT GEOM FROM ROADS_GEOM WHERE GEOM &amp;&amp; 'BOX3D(191232 243117,191232 243119)'::box3d;</literallayout>
<para>Note the use of the "::box3d" casting opertator at the end of the
SQL statement to ensure that the BOX3D string representation is properly cast
into a BOX3D object before the query executes.</para>
</sect2>
<sect2>
<title>Using the Dumper</title>
<para>This section to be written.</para>
</sect2>
</sect1>
<sect1>
<title>Building Indexes</title>
<para>Indexes are what make using a spatial database for large databases
possible. Without indexing, any search for a feature would require a
"sequential scan" of every record in the database. Indexing speeds up searching
by organizing the data into a search tree which can be quickly traversed to
find a particular record. PostgreSQL supports three kinds of indexes by
default: B-Tree indexes, R-Tree indexes, and GiST indexes. B-Trees are used for
data which can be sorted along one axis; for example, numbers, letters, dates.
GIS data cannot be rationally sorted along one axis (which is greater, (0,0) or
(0,1) or (1,0)?) so B-Tree indexing is of no use for us. Both R-Tree and GiST
indexing can be used to index GIS data, and PostGIS has implementations for
both. Note however, that <emphasis>only GiST indexes are supported and R-Trees
will likely be dropped in the future</emphasis>.</para>
<sect2>
<title>GiST Indexes</title>
<para>GiST stands for "Generalized Search Tree" and is a generalized
form of R-Tree indexing. In addition to GIS indexing, GiST is used to speed up
searches on all kinds of irregular data structures (integer arrays, spectral
data, etc) which are not amenable to normal B-Tree indexing.</para>
<para>Once a GIS data table exceeds a few thousand rows, you will want
to build an index to speed up spatial searches of the data (unless all your
searches are based on attributes, in which case you'll want to build a normal
index on the attribute fields).</para>
<para>The syntax for building a GiST index on a "geometry" column is as
follows:</para><literallayout>CREATE INDEX [indexname] ON [tablename] USING GIST
( [geometryfield] GIST_GEOMETRY_OPS ) WITH ( ISLOSSY );</literallayout>
<para>Building a spatial index is a computationally intensive exercise:
on tables of around 1 million rows, on a 300MHz Solaris machine, we have found
building a GiST index takes about 1 hour.</para>
<para>GiST indexes have two advantages over R-Tree indexes in
PostgreSQL. Firstly, GiST indexes build much faster than R-Trees; we have found
that it takes about 4 times more time to build an R-Tree than a GiST index on
an identical table. Secondly, GiST indexes support the concept of "lossiness"
which is important when dealing with GIS objects larger than the PostgreSQL 8K
page size. Lossiness allows PostgreSQL to store only the "important" part of an
object in an index -- in the case of GIS objects, just the bounding box. GIS
objects larger than 8K will cause R-Tree indexes to fail in the build
phase.</para>
</sect2>
<sect2>
<title>R-Tree Indexes</title>
<para>R-Tree indexes use rectangles to break up a spatial plane into
chunks for searching. Big rectangles are parents of smaller rectangles which
are in turn parents of yet smaller rectangles, and so on. The algorithms are
complex, but the principal is the same as that of the B-Tree: by traversing
from parent to child down the structure of rectangles, you can locate
particular spatial objects extremely quickly.</para>
<para>R-Trees are not recommended for GIS objects: they take much
longer to build, and do not support objects larger than 8K in size.</para>
<para>The syntax for building an R-Tree index on a "geometry" column is
as follows:</para> <literallayout>CREATE INDEX [indexname] ON [tablename] USING RTREE
( [geometryfield] RT_GEOMETRY_OPS );</literallayout>
</sect2>
<sect2>
<title>Using Indexes</title>
<para>Ordinarily, indexes invisibly speed up data access: once the
index is built, the query planner tranparently decides when to use index
information to speed up a query plan. Unfortunately, the PostgreSQL query
planner does not optimize the use of R-Tree and GiST indexes well, so very
often searches which should use a spatial index instead default to a sequence
scan of the whole table.</para>
<para>If you find your spatial indexes are not being used (or your
attribute indexes, for that matter) there are a couple things you can
do:</para>
<itemizedlist>
<listitem><para>Firstly, make sure you run the "VACUUM ANALYZE
[tablename]" command on the tables you are having problems with. "VACUUM
ANALYZE" gathers statistics about the number and distributions of values in a
table, to provide the query planner with better information to make decisions
around index usage.</para>
</listitem>
<listitem><para>Secondly, you can force the planner to use the index
information by using the "SET ENABLE_SEQSCAN=OFF" command. You should only use
this command sparingly, and only on spatially indexed queries: generally
speaking, the planner knows better than you do about when to use normal B-Tree
indexes. Once you have run your query, you should consider setting
"ENABLE_SEQSCAN" back on, so that other queries will utilize the planner as
normal.</para>
</listitem>
</itemizedlist>
</sect2>
</sect1>
<sect1>
<title>Java Clients (JDBC)</title>
<para>Java clients can access PostGIS "geometry" objects in the
PostgreSQL database either directly as text representations or using the JDBC
extension objects bundled with PostGIS. In order to use the extension objects,
the "postgis.jar" file must be in your CLASSPATH along with the
"postgresql.jar" JDBC driver package.</para> <programlisting>import java.sql.*;
import java.util.*;
import java.lang.*;
import org.postgis.*;
public class JavaGIS
{
public static void main(String[] args)
{
java.sql.Connection conn;
try {
/*
* Load the JDBC driver and establish a connection.
*/
Class.forName("org.postgresql.Driver");
String url = "jdbc:postgresql://localhost:5432/database";
conn = DriverManager.getConnection(url, "postgres", "");
/*
* Add the geometry types to the connection. Note that you
* must cast the connection to the pgsql-specific connection
* implementation before calling the addDataType() method.
*/
((org.postgresql.Connection)conn).addDataType("geometry","org.postgis.PGgeometry");
((org.postgresql.Connection)conn).addDataType("box3d","org.postgis.PGbox3d");
/*
* Create a statement and execute a select query.
*/
Statement s = conn.createStatement();
ResultSet r = s.executeQuery("select geom,id from geomtable");
while( r.next() )
{
/*
* Retrieve the geometry as an object then cast it to the geometry type.
* Print things out.
*/
PGgeometry geom = (PGgeometry)r.getObject(1);
int id = r.getInt(2);
System.out.println("Row " + id + ":");
System.out.println(geom.toString());
}
s.close();
conn.close();
} catch( Exception e ) {
e.printStackTrace();
}
}
}</programlisting>
<para>The "PGgeometry" object is a wrapper object which contains a
specific topological geometry object (subclasses of the abstract class
"Geometry") depending on the type: Point, LineString, Polygon, MultiPoint,
MultiLineString, MultiPolygon.</para><programlisting>PGgeometry geom = (PGgeometry)r.getObject(1);
if( geom.getType() = Geometry.POLYGON ) {
Polygon pl = (Polygon)geom.getGeometry();
for( int r = 0; r &lt; pl.numRings(); r++ ) {
LinearRing rng = pl.getRing(r);
System.out.println("Ring: " + r);
for( int p = 0; p &lt; rng.numPoints(); p++ ) {
Point pt = rng.getPoint(p);
System.out.println("Point: " + p);
System.out.println(pt.toString());
}
}
}</programlisting>
<para>The JavaDoc for the extension objects provides a reference for the
various data acessor functions in the geometric objects.</para>
</sect1>
<sect1>
<title> C Clients (libpq)</title>
<para>...</para>
<sect2>
<title>Text Cursors</title>
<para>...</para>
</sect2>
<sect2>
<title>Binary Cursors</title>
<para>...</para>
</sect2>
</sect1>
</chapter>
<chapter>
<title>PostGIS Reference</title>
<para>The functions given below are the ones which a user of PostGIS is likely to need. There are other functions which are required support functions to the PostGIS objects which are not of use to a general user.</para>
<sect1>
<title>OpenGIS Functions</title>
<variablelist>
<varlistentry>
<term>AsBinary(geometry)</term>
<listitem><para>Returns the geometry in the OGC "well-known-binary" format,
using the endian encoding of the server on which the database is running. This is useful in binary cursors to pull data out
of the database without converting it to a string representation.</para>
</listitem>
</varlistentry><varlistentry>
<term>Dimension(geometry)</term>
<listitem><para>Returns '2' if the geometry is two dimensional and '3' if the geometry is three dimensional.</para></listitem>
</varlistentry>
<varlistentry>
<term>Envelope(geometry)</term>
<listitem><para>Returns a POLYGON representing the bounding box of the geometry.</para></listitem>
</varlistentry>
<varlistentry>
<term>GeometryType(geometry)</term>
<listitem><para>Returns the type of the geometry. Eg: LINESTRING, POLYGON, MULTIPOINT, etc.</para></listitem>
</varlistentry><varlistentry>
<term>X(geometry)</term>
<listitem><para>Find and return the X coordinate of the first point in the geometry. Return NULL if there is no point in the geometry.</para></listitem>
</varlistentry>
<varlistentry>
<term>Y(geometry)</term>
<listitem><para>Find and return the Y coordinate of the first point in the geometry. Return NULL if there is no point in the geometry.</para></listitem>
</varlistentry>
<varlistentry>
<term>Z(geometry)</term>
<listitem><para>Find and return the Z coordinate of the first point in the geometry. Return NULL if there is no point in the geometry.</para></listitem>
</varlistentry>
<varlistentry>
<term>NumPoints(geometry)</term>
<listitem><para>Find and return the number of points in the first linestring in the geometry. Return NULL if there is no linestring in the geometry.</para></listitem>
</varlistentry>
<varlistentry>
<term>PointN(geometry,integer)</term>
<listitem><para>Return the N'th point in the first linestring in the geometry. Return NULL if there is no linestring in the geometry.</para></listitem>
</varlistentry>
<varlistentry>
<term>ExteriorRing(geometry)</term>
<listitem><para>Return the exterior ring of the first polygon in the geometry. Return NULL if there is no polygon in the geometry.</para></listitem>
</varlistentry>
<varlistentry>
<term>NumInteriorRings(geometry)</term>
<listitem><para>Return the number of interior rings of the first polygon in the geometry. Return NULL if there is no polygon in the geometry.</para></listitem>
</varlistentry>
<varlistentry>
<term>InteriorRingN(geometry,integer)</term>
<listitem><para>Return the N'th interior ring of the first polygon in the geometry. Return NULL if there is no polygon in the geometry.</para></listitem>
</varlistentry>
<varlistentry>
<term>NumGeometries(geometry)</term>
<listitem><para>If geometry is a GEOMETRYCOLLECTION return the number of geometries, otherwise return NULL.</para></listitem>
</varlistentry>
<varlistentry>
<term>GeometryN(geometry)</term>
<listitem><para>Return the N'th geometry if the geometry is a GEOMETRYCOLLECTION. Otherwise, return NULL.</para></listitem>
</varlistentry>
</variablelist>
</sect1>
<sect1><title>Other Functions</title><variablelist>
<varlistentry>
<term>A &lt;&amp; B</term>
<listitem><para>The "&lt;&amp;" operator returns true if A's bounding box
overlaps or is to the right of B's bounding box.</para>
</listitem>
</varlistentry>
<varlistentry>
<term>A &amp;&gt; B</term>
<listitem><para>The "&amp;&gt;" operator returns true if A's bounding box
overlaps or is to the left of B's bounding box.</para>
</listitem>
</varlistentry>
<varlistentry>
<term>A &lt;&lt; B</term>
<listitem><para>The "&lt;&lt;" operator returns true if A's bounding box
is strictly to the right of B's bounding box.</para>
</listitem>
</varlistentry>
<varlistentry>
<term>A &gt;&gt; B</term>
<listitem><para>The "&gt;&gt;" operator returns true if A's bounding box
is strictly to the left of B's bounding box.</para>
</listitem>
</varlistentry>
<varlistentry>
<term>A ~= B</term>
<listitem><para>The "~=" operator is the "same as" operator. It tests
actual geometric equality of two features. So if A and B are the same feature,
vertex-by-vertex, the operator returns true.</para>
</listitem>
</varlistentry>
<varlistentry>
<term>A ~ B</term>
<listitem><para>The "~" operator returns true of A's bounding box is
completely contained by B's bounding box.</para>
</listitem>
</varlistentry>
<varlistentry>
<term>A &amp;&amp; B</term>
<listitem><para>The "&amp;&amp;" operator is the "overlaps" operator. If
A's bounding boux overlaps B's bounding box the operator returns true.</para>
</listitem>
</varlistentry>
<varlistentry>
<term>area2d(geometry)</term>
<listitem><para>Returns the area of the geometry if it is a polygon or
multi-polygon.</para>
</listitem>
</varlistentry>
<varlistentry>
<term>asbinary(geometry,'NDR')</term>
<listitem><para>Returns the geometry in the OGC "well-known-binary" format,
using little-endian encoding. This is useful in binary cursors to pull data out
of the database without converting it to a string representation.</para>
</listitem>
</varlistentry><varlistentry>
<term>asbinary(geometry,'XDR')</term>
<listitem><para>Returns the geometry in the OGC "well-known-binary" format,
using big-endian encoding. This is useful in binary cursors to pull data out
of the database without converting it to a string representation.</para>
</listitem>
</varlistentry><varlistentry>
<term>box3d(geometry)</term>
<listitem><para>Returns a BOX3D representing the maximum extents of the geometry.</para>
</listitem>
</varlistentry><varlistentry>
<term>extent(geometry)</term>
<listitem><para>The extent() function is an "aggregate" function in the
terminology of PostgreSQL. That means that it operators on lists of data, in
the same way the sum() and mean() functions do. For example, "SELECT
EXTENT(GEOM) FROM GEOMTABLE" will return a BOX3D giving the maximum extend of
all features in the table. Similarly, "SELECT EXTENT(GEOM) FROM GEOMTABLE GROUP
BY CATEGORY" will return one extent result for each category.</para>
</listitem>
</varlistentry><varlistentry>
<term>force_collection(geometry)</term>
<listitem><para>Converts the geometry into a GEOMETRYCOLLECTION. This is useful for simplifying the WKB representation.</para>
</listitem>
</varlistentry><varlistentry>
<term>force_2d(geometry)</term>
<listitem><para>Forces the geometries into a "2-dimensional mode" so that
all output representations will only have the X and Y coordinates. This is
useful for force OGC-compliant output (since OGC only specifies 2-D
geometries).</para>
</listitem>
</varlistentry>
<varlistentry>
<term>force_3d(geometry)</term>
<listitem><para>Forces the geometries into a "3-dimensional mode" so that
all output representations will have the X, Y and Z coordinates.</para>
</listitem>
</varlistentry>
<varlistentry>
<term>length2d(geometry)</term>
<listitem><para>Returns the 23-dimensional length of the geometry if it is
a linestring or multi-linestring.</para>
</listitem>
</varlistentry><varlistentry>
<term>length3d(geometry)</term>
<listitem><para>Returns the 3-dimensional length of the geometry if it is a
linestring or multi-linestring.</para>
</listitem>
</varlistentry><varlistentry>
<term>length_spheroid(geometry,spheroid)</term>
<listitem><para>Calculates the length of of a geometry on an elipsoid. This is useful if the coordinates of the geometry are in latitude/longitude and a length is desired without reprojection. The elipsoid is a separate database type and can be constructed as follows:<literallayout>SPHEROID[&lt;NAME&gt;,&lt;SEMI-MAJOR AXIS&gt;,&lt;INVERSE FLATTENING&gt;]
Eg: SPHEROID["GRS_1980",6378137,298.257222101]</literallayout>An example calculation might look like this:<literallayout>SELECT length_spheroid(geometry_column,'SPHEROID["GRS_1980",6378137,298.257222101]') from geometry_table;</literallayout></para>
</listitem>
</varlistentry><varlistentry>
<term>length3d_spheroid(geometry,spheroid)</term>
<listitem><para>Calculates the length of of a geometry on an elipsoid, taking the elevation into account. This is just like length_spheroid except vertical coordinates (expressed in the same units as the spheroid axes) are used to calculate the extra distance vertical displacement adds.</para>
</listitem>
</varlistentry><varlistentry>
<term>mem_size(geometry)</term>
<listitem><para>Returns the amount of space (in bytes) the geometry takes.</para>
</listitem>
</varlistentry><varlistentry>
<term>npoints(geometry)</term>
<listitem><para>Returns the number of points in the geometry.</para>
</listitem>
</varlistentry>
<varlistentry>
<term>nrings(geometry)</term>
<listitem><para>If the geometry is a polygon or multi-polygon returns the
number of rings.</para>
</listitem>
</varlistentry><varlistentry>
<term>numb_sub_objects(geometry)</term>
<listitem><para>Returns the number of objects stored in the geometry. This
is useful for MULTI-geometries and GEOMETRYCOLLECTIONs.</para>
</listitem>
</varlistentry><varlistentry>
<term>perimeter2d(geometry)</term>
<listitem><para>Returns the 2-dimensional perimeter of the geometry, if it
is a polygon or multi-polygon.</para>
</listitem>
</varlistentry><varlistentry><term>perimeter3d(geometry)</term>
<listitem><para>Returns the 3-dimensional perimeter of the geometry, if it
is a polygon or multi-polygon.</para>
</listitem>
</varlistentry><varlistentry><term>point_inside_circle(geometry,float,float,float)</term>
<listitem><para>The syntax for this functions is point_inside_circle(&lt;geometry&gt;,&lt;circle_center_x&gt;,&lt;circle_center_y&gt;,&lt;radius&gt;). Returns the true if the geometry is a point and is inside the circle. Returns false otherwise.</para>
</listitem>
</varlistentry><varlistentry>
<term>summary(geometry)</term>
<listitem><para>Returns a text summary of the contents of the geometry.</para>
</listitem>
</varlistentry><varlistentry>
<term>translate(geometry,float8,float8,float8)</term>
<listitem><para>Translates the geometry to a new location using the numeric
parameters as offsets. Ie: translate(geom,X,Y,Z).</para>
</listitem>
</varlistentry><varlistentry>
<term>truly_inside(geometryA,geometryB)</term>
<listitem><para>Returns true if any part of A is within the bounding box
of B.</para>
</listitem>
</varlistentry></variablelist></sect1></chapter>
<chapter>
<title>Programming Information</title>
<para> The following sections provide information about the internal structure of the PostGIS spatial objects, and some information about certain design decisions.</para>
<sect1>
<title>Internal Object Representation</title>
<para>The internal representation information is useful to people who intend to write C functions to directly manipulate the backend objects at
the server. Reading <filename>postgis.h</filename> will give the exact type definitions used, and reading <filename>postgis.c</filename> will give some examples of how to
manipulate the objects.</para>
<sect2><title>Basic Structure</title><para>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: </para><programlisting>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;</programlisting><para>However, because of the way PostgreSQL handles memory allocation for objects, the type is actually organized as:</para><programlisting>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;</programlisting><para>This is because a Postgres type is a just a chuck of memory - it cannot contain any pointers. In a normal program <varname>objType</varname> and
objData would be pointers to separate chunks of memory for these two lists (in fact, <varname>objData</varname> would likely be a list of pointers to other memory locations). That would look something like:</para><para>Since we cannot have pointers, the geometry structure actually has pseudo-pointers to its data portion, and looks like:
</para><para>Since we cannot have pointers, the geometry structure actually has pseudo-pointers to its data portion, and looks like:
</para><itemizedlist><listitem><para>The structure is assumed to start off double-aligned (see the SQL define type).</para></listitem><listitem><para>Inside the structure, <varname>bvol</varname> and all the objects are double-aligned (there is unused space between <varname>is3d</varname> and <varname>bvol</varname>, there could be
unused space between <varname>objOffset[n]</varname> and object 0, and there could be unused space between object i and object i+1.</para></listitem><listitem><para>The actual location of <varname>objType[0]</varname> is always <varname>geometry-&gt;objType[0]</varname>.</para></listitem><listitem><para>The actual location of <varname>objOffset[0]</varname> is <varname>sizeof(int32)*geometry-&gt;nobjs</varname> after objType[0]. </para></listitem><listitem><para>The actual location of object 0 is <varname>objOffset[0]</varname> bytes into the structure . </para></listitem><listitem><para>Object 0 is also <varname>sizeof(int32)*geometry-&gt;nobjs</varname> bytes after <varname>objOffset[0]</varname> (could be 4 more bytes along if this isn't
double-aligned) . </para></listitem><listitem><para>The actual location of object 1 is <varname>objOffset[1]</varname> bytes into the structure.
</para></listitem></itemizedlist></sect2><sect2><title>Usage</title><para>Normally, one will want to find the location of <varname>objOffset[0]</varname> so it is accessible:</para><programlisting>Int32 *offsets;
offsets = (int32 *) ( ((char *) &amp;(geom1-&gt;objType[0] ))+ sizeof(int32) * geom1-&gt;nobjs );</programlisting><para>Next, one might want to get object i:</para><programlisting>Char *obji;
oi = (char *) geom1 +offsets[i] ;
typei= geom1-&gt;objType[i];</programlisting><para>Then one can re-cast the object:</para><programlisting>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;
...
}</programlisting><para>What does everything mean? </para><variablelist><varlistentry><term>Size </term><listitem><para>The total size of the structure.</para></listitem></varlistentry><varlistentry><term>Type</term><listitem><para>Generic type for this Structure.</para><para>This is mostly used so output knows how the object was originally entered into the system. </para></listitem></varlistentry><varlistentry><term>Is3d</term><listitem><para>TRUE if and only if, when the object was created, any of the points were given in 3D.</para><para>For example,
MULTIPOINT(1 1,2 2) -&gt; is3d = FALSE
MULTIPOINT(1 1, 2 2, 3 3 3) -&gt; is3d = TRUE</para><para>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.</para></listitem></varlistentry><varlistentry><term>Bvol</term><listitem><para>Bounding Volume for all the points in this geometry </para></listitem></varlistentry><varlistentry><term>nobjs</term><listitem><para>The number of point/line/polygons there are in this geometry.</para></listitem></varlistentry><varlistentry><term>objType[i]</term><listitem><para>The type of each of the sub-objects.</para><programlisting>#define POINTTYPE 1
#define LINETYPE 2
#define POLYGONTYPE 3</programlisting></listitem></varlistentry><varlistentry><term>objOffset[i]</term><listitem><para>Offset, in bytes, into the structure where the object i is actually stored.</para></listitem></varlistentry><varlistentry><term>objdata</term><listitem><para>Data block with all the objects stored in it.</para></listitem></varlistentry></variablelist></sect2><sect2><title>Input / Output</title><para>The geometry type tries really hard to consistently reproduce objects. For example, if you insert a MULTIPOINT(), you will get a
MULTIPOINT() when you select.
This is more difficult than you might expect because, internally, all the objects listed below are stored the same:
</para><itemizedlist><listitem><para>MULTIPOINT( 1 1, 2 2, 3 3)
</para></listitem><listitem><para>GEOMETRYCOLLECTION(POINT(1 1), POINT(2 2), POINT(3 3) )</para></listitem><listitem><para>GEOMETRYCOLLECTION(MULTIPOINT( 1 1, 2 2), POINT( 3 3) ) </para></listitem><listitem><para>GEOMETRYCOLLECTION(MULTIPOINT( 1 1, 2 2, 3 3))
</para></listitem></itemizedlist><para>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.
</para><para>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: </para><itemizedlist><listitem><para>GEOMETRYCOLLECTION(POINT(1 1),POINT(2 2)) becomes MULTIPOINT(1 1 , 2 2)</para></listitem><listitem><para>GEOMETRYCOLLECTION(LINESTRING(1 1,2 2),LINESTRING(10 10, 20 20)) becomes MULTILINESTRING((1 1,2 2), (10
10, 20 20))</para></listitem><listitem><para>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))) </para></listitem></itemizedlist><para>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: </para><itemizedlist><listitem><para>GEOMETRYCOLLECTION(POINT(1 1),POINT(2 2),POINT(3 3)) becomes GEOMETRYCOLLECTION(POINT(1 1),POINT(2
2),POINT(3 3)) </para></listitem><listitem><para>GEOMETRYCOLLECTION(MULTIPOINT(1 1,2 2,3 3)) becomes GEOMETRYCOLLECTION(POINT(1 1),POINT(2 2),POINT(3 3)) </para></listitem></itemizedlist></sect2><sect2><title>Fundamental Geometry Types</title><para>Inside each GEOMETRY object there are only four different possible geomtry types, and the <varname>Type</varname> variable of the GEOMETRY controls how the collection will be presented to the outside world (as a GEOMETRYCOLLECTION or a MULTIPOLYGON, for example).</para><sect3><title>POINT3D</title><para>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.</para><programlisting>typedef struct
{
double x,y,z;
} POINT3D;</programlisting></sect3><sect3><title>LINE3D</title><para>A lines is a set of connected points. The line moves from <varname>points[0]-&gt;points[1]-&gt;points[2]</varname>
</para><programlisting>typedef struct
{
int32 npoints; // how many points in the line
int32 junk; // double-word alignment
POINT3D points[1]; // array of actual points
} LINE3D;</programlisting></sect3><sect3><title>POLYGON3D</title><para>A polygon is a set of rings. Each ring is a closed line.
</para><itemizedlist><listitem><para>The first ring is the outer ring, the others are holes.</para></listitem><listitem><para><varname>Npoints[i]</varname> is the number of points in ring i.</para></listitem><listitem><para><varname>&amp;points[0]</varname> is at <varname>&amp;polygon-&gt;npoints[0] + sizeof(int32)*polygon-&gt;nrings</varname>.</para></listitem><listitem><para>Once you've calculated <varname>&amp;points[0]</varname>, ensure it is double-aligned.</para></listitem></itemizedlist><para>For example: </para><programlisting>POINT3D *pts;
pts = (POINT3D *) ( (char *)&amp;(poly-&gt;npoints[poly-&gt;nrings] ) );
if ((int32) pts1 != (((int32)pts1&gt;&gt;3)&lt;&lt;3) )
pts1 = (POINT3D *) ((char *) pts1 + 4);</programlisting><para>Therefore, </para><itemizedlist><listitem><para>Ring 0's points are <varname>points[0]</varname> to <varname>points[npoint[0]-1]</varname>.</para></listitem><listitem><para>Ring 1's points are from <varname>points[npoint[0]]</varname> to <varname>points[npoints[0] + npoints[1]-1]</varname>.</para></listitem></itemizedlist><programlisting>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;</programlisting></sect3><sect3><title>BOX3D</title><para>Representation of a long diagonal of a cube, from the lower-left bottom (LLB) to the upper-right top (URT). </para><itemizedlist><listitem><para>Minimum X is box3d-&gt;LLB.x</para></listitem><listitem><para>Minimum Y is box3d-&gt;LLB.y</para></listitem><listitem><para>Minimum Z is box3d-&gt;LLB.z</para></listitem><listitem><para>Maximum X is box3d-&gt;URT.x</para></listitem><listitem><para>Maximum Y is box3d-&gt;URT.y</para></listitem><listitem><para>Maximum Y is box3d-&gt;URT.z</para></listitem></itemizedlist><programlisting>typedef struct
{
POINT3D LLB,URT; /* corner POINT3Ds on long diagonal */
} BOX3D;</programlisting></sect3></sect2></sect1>
<sect1>
<title>Internal Functions</title>
<para>To be done.</para>
</sect1>
</chapter>
</book>