In the last section, we worked with a table – usa.states – containing geometries of the type POLYGON. The other basic geometry types are POINT and LINESTRING. As we'll see momentarily there are numerous other geometry types available in PostGIS that allow for the storage of multipart shapes, 3-dimensional shapes and shapes that have a measure (or M value) associated with its vertices. If keeping all of the various types straight becomes difficult, it may help to remember that the simple geometries we deal with most often are POINT, LINESTRING and POLYGON.
To demonstrate some of the concepts in this section, we're going to create a new schema to store points of interest in New York City. Unlike the last schema where we used the Shapefile Import/Export Manager to both create and populate a table at the same time, here we'll carry out those steps separately.
A. Create a new empty spatial table
- In the Browser pane within pgAdmin, right-click on the Schemas node beneath the Lesson3db database and select Create > Schema.
- Set the Name of the schema to nyc_poi and its Owner to postgres then click Save to create the schema.
- Expand the object listing associated with the nyc_poi schema.
- Right-click on the Tables node and select Create > Table.
- Set the table's Name to pts and its Owner to postgres.
- Under the Columns tab click the + button.
- For the new column, set the Name to gid (geometry ID) and the Data type to serial. This data type is roughly equivalent to the AutoNumber type we saw in Access. Define this column as the table's Primary key.
- Repeat the previous step to create a column called name. Set its Data type to character varying and its Length to 50. This column should not be the Primary key.
The last column we want to add to the table is one that will hold the geometries. While it's possible to add a column of type 'point' through the GUI, there are a number of other important settings that should be made when adding a geometry column (such as its spatial reference ID, or SRID). These settings are all handled by a PostGIS maintenance function called AddGeometryColumn(), so that is the route we will take.
- Click Save to dismiss the dialog and create the table. Before adding the geometry column to the table let's recall the pgAdmin search path. It's not set to include the nyc_poi schema, so let's do that first.
- Reset the search path by executing the following statement in a Query window:
SET search_path TO nyc_poi, public;
- Now add a geometry column called geom to the table by executing this statement. (You can re-use the Query window you already have open for this and subsequent queries.)
SELECT AddGeometryColumn('nyc_poi','pts','geom',4269,'POINT',2);First let's address the unusual syntax of this statement. You've no doubt grown accustomed to listing column names (or *) in the SELECT clause, but here we're plugging in a function without any columns. We're forced to use this awkward syntax because SQL rules don't allow for invoking functions directly. Function calls must be made in one of the statement types we've encountered so far (SELECT, INSERT, UPDATE or DELETE). In this situation, a SELECT statement is the most appropriate.
The arguments to the function in this statement are, in order: the schema name, the table name, the name to be given to the geometry column, its spatial reference ID, the type of geometry it will store, and the dimension of the coordinates it will hold.
Before moving on, an explanation of this dimension parameter is in order. In most cases you're likely to be storing just X and Y coordinates. When that's the case you should assign a dimension value of 2, as we just did. However, it is also possible that you want to store the elevation (Z value) of the points. In that case you assign a dimension value of 3. As mentioned above it is also possible to store some type of measure (M value) with each point (e.g., the time the point was recorded with a GPS unit). In that scenario you would also assign a dimension value of 3. You would differentiate that XYM type of point from an XYZ point by setting the geometry type to POINTM instead of POINT. Finally, it is possible to store all four values (X, Y, Z and M). In that situation you would assign a dimension value of 4.
B. Add rows to the spatial table
We're about to add rows to our pts table through a series of INSERT statements. You'll find it much easier to copy and paste these statements rather than typing them manually, if not now, then certainly when we insert polygons later using long strings of coordinates.
- Execute the following statement to insert a row into the pts table.
INSERT INTO pts (name, geom) VALUES ('Empire State Building', ST_GeomFromText('POINT(-73.985744 40.748549)',4269));The key point to take away from this statement (no pun intended) is the call to the ST_GeomFromText() function. This function converts a geometry supplied in text format to the hexadecimal form that PostGIS geometries are stored in. The other argument is the spatial reference of the geometry. This argument is required in this case because when we created the geom column using AddGeometryColumn(), it added a constraint that values in that column must be in a particular spatial reference (which we specified as 4269).
- Execute the statements below to add a couple more rows to the table. Note that while we've executed single statements thus far in the lesson you are also allowed to execute multiple statements in succession.
INSERT INTO pts (name, geom) VALUES ('Statue of Liberty', ST_GeomFromText('POINT(-74.044508 40.689229)',4269)); INSERT INTO pts (name, geom) VALUES ('World Trade Center', ST_GeomFromText('POINT(-74.013371 40.711549)',4269));
- Finally add two more rows using the statement below. Note that in this step you're adding multiple rows using a single statement.
INSERT INTO pts (name, geom) VALUES ('Radio City Music Hall', ST_GeomFromText('POINT(-73.97988 40.760171)',4269)), ('Madison Square Garden', ST_GeomFromText('POINT(-73.993544 40.750541)',4269));
- In the pgAdmin window right-click on the pts table and select View/Edit Data > All Rows to confirm that the INSERT statements executed properly.
C. Create and populate a table of linestrings
- Repeat the steps (in Part A above) to create a new table within the nyc_poi schema, that will hold NYC line features. Pay particular attention to these differences:
- Give the table a name of lines.
- The table should have the same column definitions with the exception that the geometry type should be set to LINESTRING rather than POINT.
- No need to set the search path again as it will already include the nyc_poi schema.
- Execute the following statement to insert 3 new rows into the lines table:
INSERT INTO lines (name, geom) VALUES ('Holland Tunnel',ST_GeomFromText('LINESTRING( -74.036486 40.730121, -74.03125 40.72882, -74.011123 40.725958)',4269)), ('Lincoln Tunnel',ST_GeomFromText('LINESTRING( -74.019921 40.767119, -74.002841 40.759773)',4269)), ('Brooklyn Bridge',ST_GeomFromText('LINESTRING( -73.99945 40.708231, -73.9937 40.703676)',4269));Note that I've split this statement across several lines to improve its readability, not for any syntax reasons. You should feel welcome to format your statements however you see fit.
As you can see the syntax for constructing linestrings is similar to that of points. The difference is that instead of supplying just one X/Y (lon/lat in this case) pair you supply however many pairs are needed to delineate the feature, with the pairs being connected sequentially by straight line segments. The longitude (X) value comes first and is separated from the latitude (Y) by a space. The lon/lat pairs are in turn separated by commas. For simplicity's sake I gave you the coordinates of some very simple straight lines. In a real-world situation you would likely need many lon/lat pairs for each line feature, the number depending on the curviness of the feature.
D. Create and populate a table of polygons
- Repeat the steps (in Part A) to create a new table within the nyc_poi schema, with the following exceptions:
- Give the table a name of polys.
- Set the geometry type of the geom column to POLYGON rather than LINESTRING.
- Execute the following statement to add a row to your polys table:
INSERT INTO polys (name, geom) VALUES ('Central Park',ST_GeomFromText('POLYGON(( -73.973057 40.764356, -73.981898 40.768094, -73.958209 40.800621, -73.949282 40.796853, -73.973057 40.764356))',4269));While the syntax for constructing a polygon looks very similar to that of a linestring there are two important differences:
- The first X/Y (lon/lat) pair should be the same as the last (to close the polygon).
- Note that the coordinate list is enclosed in an additional set of parentheses. This set of parentheses is required because polygons are actually composed of potentially multiple rings. Every polygon has a ring that defines its exterior. Some polygons also have additional rings that define holes in the interior. When constructing a polygon with holes the exterior ring is supplied first followed by the interior rings. Each ring is enclosed in a set of parentheses and the rings are separated by commas.
To see an example let's add Central Park again, this time cutting out the large reservoir near its center.
- First let's remove the original Central Park row. In pgAdmin, right-click on the polys table and select Truncate > Truncate. Note that this deletes all rows from the table.
- Add Central Park (minus the reservoir) back into the table using this statement:
INSERT INTO polys (name, geom) VALUES ('Central Park',ST_GeomFromText('POLYGON(( -73.973057 40.764356, -73.981898 40.768094, -73.958209 40.800621, -73.949282 40.796853, -73.973057 40.764356), (-73.966681 40.785221, -73.966058 40.787674, -73.965586 40.788064, -73.9649 40.788291, -73.963913 40.788194, -73.963333 40.788291, -73.962539 40.788259, -73.962153 40.788389, -73.96181 40.788714, -73.961359 40.788909, -73.960887 40.788925, -73.959986 40.788649, -73.959492 40.788649, -73.958913 40.78873, -73.958269 40.788974, -73.957797 40.788844, -73.957497 40.788568, -73.957497 40.788259, -73.957776 40.787739, -73.95784 40.787057, -73.957819 40.786569, -73.960801 40.782394, -73.961145 40.78215, -73.961638 40.782036, -73.962518 40.782199, -73.963076 40.78267, -73.963677 40.783661, -73.965694 40.784457, -73.966681 40.785221) )',4269));
E. 3- and 4-dimensional geometries
Earlier in this section we discussed 3-dimensional (XYZ and XYM) and 4-dimensional (XYZM) geometries in the context of properly specifying the dimension argument to the AddGeometryColumn() function. We won't be doing so in this course but let's look for a moment at the syntax used for creating these geometries.
To define a column that can store M values as part of the geometry, use the POINTM, LINESTRINGM and POLYGONM data types. When specifying objects of these types, the M value should appear last. For example an M value of 9999 is attached to each coordinate in these features from our nyc_poi schema:
POINTM(-73.985744 40.748549 9999) LINESTRINGM(-74.019921 40.767119 9999, -74.002841 40.759773 9999) POLYGONM((-73.973057 40.764356 9999, -73.981898 40.768094 9999, -73.958209 40.800621 9999, -73.949282 40.796853 9999, -73.973057 40.764356 9999)
Perhaps the most common usage of M coordinates is in linear referencing (e.g., to store the distance from the start of a road, power line, pipeline, etc.). This Wikipedia article on Linear Referencing provides a good starting point if you're interested in learning more.
To define a column capable of storing Z values along with X and Y, use the "plain" POINT, LINESTRING and POLYGON data types rather than their "M" counterparts. The syntax for specifying an XYZ coordinate is the same as that for an XYM coordinate. The "plain" data type name tells PostGIS that the third coordinate is a Z value rather than an M value. For example we could include sea level elevation in the coordinates for the Empire State Building (in feet):
POINT(-73.985744 40.748549 190).
Finally, in the event you want to store both Z and M values, again use the "plain" POINT, LINESTRING and POLYGON data types. The Z value should be listed third and the M value last. For example:
POINT(-73.985744 40.748549 190 9999)
F. Multipart geometries
PostGIS provides support for features with multiple parts through the MULTIPOINT, MULTILINESTRING and MULTIPOLYGON data types. A classic example of multipart geometry is the state of Hawaii which is composed of multiple disconnected islands. The syntax for specifying a MULTIPOLYGON builds upon the rules for a regular POLYGON; the parts are separated by commas and an additional set of parentheses is used to enclose the full coordinate list. The footprints of the World Trade Center Towers 1 and 2 (now fountains in the 9/11 Memorial) can be represented as a single multipart polygon as follows:
MULTIPOLYGON(((-74.013751 40.711976, -74.01344 40.712439, -74.012834 40.712191, -74.013145 40.711732, -74.013751 40.711976)), ((-74.013622 40.710772, -74.013311 40.711236, -74.012699 40.710992, -74.013021 40.710532, -74.013622 40.710772)))
This basic example shows the syntax for storing just X and Y coordinates. Keep in mind that Z values and M values are also supported for multipart geometries. As you might guess, the "MULTI" data types have "M" counterparts too: MULTIPOINTM, MULTILINESTRINGM and MULTIPOLYGONM.
G. Mixing geometries
The tables we've created so far reflect a bias toward Esri-centric design with each table storing a single column of homogeneous geometries (i.e., all points, or all lines, or all polygons, but not a mix). However, PostGIS supports two design approaches that are good to keep in mind when putting together a database:
- It is possible to store multiple geometry columns in a table. This capability could be used to store data in two or more different spatial reference systems. Though, one should do so under limited circumstances given the existence of the ST_Transform() function and the additional maintenance such a design would require.
- It is possible to store multiple geometry types in a single column. This capability can simplify schema design and certain types of queries, though there are drawbacks to this approach, such as the fact that some third-party tools can't deal with mixed-geometry tables.
Let's see how this heterogeneous column approach can be used to store all of our nyc_poi data in the same table.
- Repeat the steps (in Part A) to create a new table within the nyc_poi schema. Pay particular attention to these differences:
- Give the table a name of mixed.
- The table should have the same column definitions with the exception that the geometry type should be set to GEOMETRY.
- Add the same features to this new table by executing the following statement:
INSERT INTO mixed (name, geom) VALUES ('Empire State Building', ST_GeomFromText('POINT(-73.985744 40.748549)',4269)), ('Statue of Liberty', ST_GeomFromText('POINT(-74.044508 40.689229)',4269)), ('World Trade Center', ST_GeomFromText('POINT(-74.013371 40.711549)',4269)), ('Radio City Music Hall', ST_GeomFromText('POINT(-73.97988 40.760171)',4269)), ('Madison Square Garden', ST_GeomFromText('POINT(-73.993544 40.750541)',4269)), ('Holland Tunnel',ST_GeomFromText('LINESTRING( -74.036486 40.730121, -74.03125 40.72882, -74.011123 40.725958)',4269)), ('Lincoln Tunnel',ST_GeomFromText('LINESTRING( -74.019921 40.767119, -74.002841 40.759773)',4269)), ('Brooklyn Bridge',ST_GeomFromText('LINESTRING( -73.99945 40.708231, -73.9937 40.703676)',4269)), ('Central Park',ST_GeomFromText('POLYGON(( -73.973057 40.764356, -73.981898 40.768094, -73.958209 40.800621, -73.949282 40.796853, -73.973057 40.764356))',4269));
- In the pgAdmin window right-click on the mixed table and select View/Edit Data > All Rows to confirm that the INSERT statement executed properly.
At some point in this lesson you probably thought to yourself, "This is fine, but what if I want to see the geometries?" That is the focus of the next part of the lesson where we will use the third-party application Quantum GIS (QGIS) to view our PostGIS data.