Introduction
This is a tutorial for new users of the Geotools2 cts-coordtrans module. This tutorial is split into four sections:
- GEOTOOLS:MathTransforms - used to transform points
- GEOTOOLS:CoordinateSystem Objects - describe a coordinate system
- GEOTOOLS:Interfaces - current OGC interfaces and future changes
- GEOTOOLS:Miscellaneous - some extras
The cts-coordtrans (CTS) module is an implementation of a subset of the Open GIS Consortium's (OGC) Coordinate Transformation Services specification. It provides an implementation for general positioning, coordinate systems, and coordinate transformations. This module has 3 Java packages:
- org.geotools.pt - points and positioning
- org.geotools.cs - coordinates systems
- org.geotools.ct - coordinate transformations
- org.geotools.ct.proj - projection transformations
Section 1 - MathTransforms
The MathTransform interface is the main interface used to apply transformations. There are two APIs in Geotools2 for creating these:
CoordinateTransformationFactory
The CoordinateTransformationFactory is a high level API that can be used to create a CoordinateTransformation from source and target CoordinateSystem objects. The factory automatically finds a transformation path between the source and target coordinate systems if one is available. A MathTransform can then be obtained from the CoordinateTransformation object and used to transform points. This is shown in the following incomplete example.
Example 1. CoordinateTransformationFactory
CoordinateTransformationFactory trFactory = CoordinateTransformationFactory.getDefault();
CoordinateSystem sourceCS = ...
CoordinateSystem targetCS = ...
CoordinateTransformation tr = trFactory.createFromCoordinateSystems(sourceCS, targetCS);
MathTransform transform = tr.getMathTransform();
CoordinatePoint point = new CoordinatePoint(-123.0, 49.0);
point = transform.transform(point, point);
The inverse transformation, to transform points from the target to the source coordinate system, can be obtained from MathTransform::inverse().
MathTransformFactory
The MathTransformFactory is a lower level API that gives the user more control when creating transformations. This factory creates MathTransforms based on available keywords and a list of parameters for the transformation you want to create.
Some available transformation keywords are shown in the following table. See the Coordinate Transformation Parameters page for imore information about the parameters used with these transformations.
Table 1. Transformation Keywords
| Keyword | EPSG code |
|---|---|
| Affine | |
| Geocentric_To_Ellipsoid | 9602 |
| Ellipsoid_To_Geocentric | 9602 |
| Molodenski | 9604 |
| Abridged_Molodenski | 9605 |
| Mercator_1SP | 9804 |
| Mercator_2SP | 9805 |
| Transverse_Mercator | 9807 |
| Lambert_Conformal_Conic_1SP | 9801 |
| Lambert_Conformal_Conic_2SP | 9802 |
| Lambert_Conformal_Conic_2SP_Belgium | 9803 |
| Albers_Conic_Equal_Area | 9822 |
| Oblique_Stereographic | 9809 |
| Polar_Stereographic | 9810 |
| Stereographic | |
| Orthographic |
The following example creates a projection transformation that can be used to transform geographic coordinates (longitude and latitude) to projected coordinates (easting and northing).
Example 2. MathTransformFactory
MathTransformFactory mtFactory = MathTransformFactory.getDefault();
javax.media.jai.ParameterList params =
mtFactory.getMathTransformProvider("Lambert_Conformal_Conic_1SP").getParameterList();
params.setParameter("semi_major", 6378206.4);
params.setParameter("semi_minor", 6356583.8);
params.setParameter("central_meridian", -77.0);
params.setParameter("latitude_of_origin", 18.0);
params.setParameter("scale_factor", 1.0);
params.setParameter("false_easting", 250000.0);
params.setParameter("false_northing", 150000.0);
MathTransform transform =
mtFactory.createParameterizedTransform("Lambert_Conformal_Conic_1SP", params);
The following is another example of using the MathTransformFactory to create a MathTransform. Here an Abridged Molodenski transformation is created to approximately shift geographic coordinates from one datum to another.
Example 3. MathTransformFactory
MathTransformFactory mtFactory = MathTransformFactory.getDefault();
javax.media.jai.ParameterList params =
mtFactory.getMathTransformProvider("Abridged_Molodenski").getParameterList();
params.setParameter("dim", 2);
params.setParameter("dx", -3.0);
params.setParameter("dy", 142.0);
params.setParameter("dz", 183.0);
params.setParameter("src_semi_major", 6378206.4);
params.setParameter("src_semi_minor", 6356583.80000000);
params.setParameter("tgt_semi_major", 6378137.0);
params.setParameter("tgt_semi_minor", 6356752.31424518);
MathTransform transform =
mtFactory.createParameterizedTransform("Abridged_Molodenski", params);
Section 2 - CoordinateSystem Objects
The CoordinateTransformationFactory is nice for automatically creating transformation objects, but you need to make some CoordinateSystem objects to use it. This section gives an overview of different types of coordinate systems, parts of coordinate systems and how to create them.
Types of Coordinate Systems
The following are the different types of coordinate systems described by the OGC. The HorizontalCoordinateSystem's are described in the following section in more detail.
- HorizontalCoordinateSystem
- GeographicCoordinateSystem - A coordinate system based on latitude and longitude.
- ProjectedCoordinateSystem - A 2D cartographic coordinate system.
- GeocentricCoordinateSystem - A 3D coordinate system, with its origin at the center of the Earth.
- VerticalCoordinateSystem - A one-dimensional coordinate system suitable for vertical measurements.
- CompoundCoordinateSystem - An aggregate of two coordinate systems (ie. a ProjectedCoordinateSystem with a VerticalCoordinateSystem).
- FittedCoordinateSystem - A coordinate system which sits inside another coordinate system.
- LocalCoordinateSystem - A local coordinate system, with uncertain relationship to the world (ie. a CAD blueprint).
Parts of Horizontal Coordinate Systems
Horizontal coordinate systems are commonly encountered when working with spatial data. They are composed of many objects and a knowledge of some of these are needed when creating them. Some of these objects are:
- HorizontalDatum - ties the coordinate system to the Earth. It is composed of two parts:
- Ellipsoid / Spheroid - An approximation of the Earth's surface as a squashed sphere. This is described by a semi-major radius and a semi-minor radius (or flattening) parameter.
- WGS84ConversionInfo - Up to 7 Bursa Wolf parameters that provide an approximate transformation from this datum to the WGS84 datum.
- Prime Meridian - the meridian from which longitude values are measured. This is usually Greenwich.
- Angular Unit - The units geographic coordinates are measured in. This is commonly degrees.
- Projection - a named map projection and its parameters.
- Linear Units - Units of a projected coordinate system. Commonly metres.
- AxisInfo - Information about the orientation and order of axes in the coordinate system.
More information about AxisInfo
Geotools2 CTS reads and writes coordinates in the following order: dimension 0, dimension 1 (if any) , dimension 2 (if any), dimension 3 (if any), etc. The meaning of dimension 0, 1, 2, 3 is coordinate system dependent.
This allows coordinate systems to be defined in any arbitrary order and orientations. For instance, a coordinate system may have an order of (x, y) or (y, x) or (z, x, y) or etc. The orientation may be positive NORTH, SOUTH, EAST, WEST, UP, DOWN, etc.
To determine the orientation of a particular dimension, you need to call CoordinateSystem::getAxis(theDimension) and inspect the orientation attribute of the returned AxisInfo object.
Creating Coordinate Systems
There are 5 ways to create CoordinateSystem objects in Geotools2.
Pre-made static objects
Many classes in the org.geotools.cs package have convenient, pre-made static objects, which make it easy to reference commonly used instances. Some of these are shown in the following example.
Example 4. Pre-made static objects
CoordinateSystem aGeogCS = GeographicCoordinateSystem.WGS84; HorizontalDatum datum = HorizontalDatum.WGS84; Ellipsoid ellipsoid = Ellipsoid.WGS84; PrimeMeridian meridian = PrimeMeridian.GREENWICH; Unit angularUnit = Unit.DEGREE;
Note that aGeogCS in the above example will have an axis order and orientation of (Longitude EAST, Latitude NORTH).
Creating by hand
Unfortunately, not all coordinate systems use WGS84. However, many arbitrary coordinate system can be created by hand in Geotools2. These use a CoordinateSystemFactory or constructors to create components of a coordinate system and the coordinate system itself.
The following example creates a geographic coordinate system with a NAD 27 datum.
Example 5. Creating a Geographic Coordinate System (NAD 27)
CoordinateSystemFactory csFactory = CoordinateSystemFactory.getDefault();
Unit angularUnit = Unit.DEGREE;
Ellipsoid ellipsoid =
csFactory.createFlattenedSphere("Clarke1866",6378206.4, 294.978698213901,Unit.METRE);
WGS84ConversionInfo convert = new WGS84ConversionInfo();
convert.dx = -3.0;
convert.dy = 142;
convert.dz = 183;
HorizontalDatum datum =
csFactory.createHorizontalDatum("Nad27",DatumType.CLASSIC, ellipsoid, convert);
PrimeMeridian meridian = PrimeMeridian.GREENWICH;
GeographicCoordinateSystem geogCS = csFactory.createGeographicCoordinateSystem(
"NAD_27", angularUnit, datum, meridian, AxisInfo.LATITUDE, AxisInfo.LONGITUDE);
The next example creates a projected coordinate system using an UTM Zone 10N map projection and a WGS84 datum.
Example 6. Creating a Projected Coordinate System (UTM Zone 10N, WGS84)
CoordinateSystemFactory csFactory = CoordinateSystemFactory.getDefault();
GeographicCoordinateSystem geogCS2 = GeographicCoordinateSystem.WGS84;
Ellipsoid ellipsoid = geogCS2.getHorizontalDatum().getEllipsoid();
Unit linearUnit = Unit.METRE;
javax.media.jai.ParameterList params =
csFactory.createProjectionParameterList("Transverse_Mercator");
params.setParameter("semi_major", ellipsoid.getSemiMajorAxis());
params.setParameter("semi_minor", ellipsoid.getSemiMinorAxis());
params.setParameter("central_meridian", -123.0);
params.setParameter("latitude_of_origin", 0.0);
params.setParameter("scale_factor", 0.9996);
params.setParameter("false_easting", 500000.0);
params.setParameter("false_northing", 0.0);
Projection projection =
csFactory.createProjection("UTM_10N", "Transverse_Mercator", params);
ProjectedCoordinateSystem projCS = csFactory.createProjectedCoordinateSystem(
"UTM_10N_WGS84", geogCS2, projection, linearUnit, AxisInfo.X, AxisInfo.Y);
A transformation between the coordinate systems above, created by a CoordinateTransformationFactory, would consist of three transformations concatenated together:
- The first transformation would swap the axis order from (lat., long.) to (long., lat.).
- The second transformation would be a Bursa Wolf transform, created from the WGS84ConversionInfo, to convert between the NAD27 and WGS84 datums.
- The third would be a projection transform to convert from geographic to projected UTM coordinates.
For another example of creating CoordinateSystem objects by hand see: gt/demo/referenceing/src/org/geotools/demo/referencing/TransformationConsole.java
Creating from WKT
As seen in the previous section, creating coordinate systems by hand can be time consuming and messy. An easier way to create an arbitrary coordinate system is to use a Well Known Text (WKT) description. This is shown in the following example.
Example 7. WKT Example
String wkt = "PROJCS[\"UTM_Zone_10N\", " + "GEOGCS[\"WGS84\", " + "DATUM[\"WGS84\", " + "SPHEROID[\"WGS84\", 6378137.0, 298.257223563]], " + "PRIMEM[\"Greenwich\", 0.0], " + "UNIT[\"degree\",0.017453292519943295], " + "AXIS[\"Longitude\",EAST], " + "AXIS[\"Latitude\",NORTH]], " + "PROJECTION[\"Transverse_Mercator\"], " + "PARAMETER[\"semi_major\", 6378137.0], " + "PARAMETER[\"semi_minor\", 6356752.314245179], " + "PARAMETER[\"central_meridian\", -123.0], " + "PARAMETER[\"latitude_of_origin\", 0.0], " + "PARAMETER[\"scale_factor\", 0.9996], " + "PARAMETER[\"false_easting\", 500000.0], " + "PARAMETER[\"false_northing\", 0.0], " + "UNIT[\"metre\",1.0], " + "AXIS[\"x\",EAST], " + "AXIS[\"y\",NORTH]]"; CoordinateSystemFactory csFactory = CoordinateSystemFactory.getDefault(); CoordinateSystem sourceCS = csFactory.createFromWKT(wkt);
For more WKT examples, see the test scripts in geotools2/geotools-src/cts-coordtrans/tests/unit/test-data. New WKT strings can usually be made by copying these and changing a few parameters.
Creating from XML
Not yet implemented.
Creating from an EPSG database
If you have access to an EPSG Geodesy Parameters database (http://www.epsg.org) you can use a CoordinateSystemEPSGFactory to create coordinate systems and other objects from EPSG authority codes. The creation of a WGS84 coordinate system is shown in the following example.
Example 8. CoordinateSystemEPSGFactory
CoordinateSystemAuthorityFactory epsgFactory = CoordinateSystemEPSGFactory.getDefault();
CoordinateSystem cs = epsgFactory.createCoordinateSystem("4326");
By default, the CoordinateSystemEPSGFactory uses an ODBC data source called "EPSG" that is backed by the MS Access format of the database.
Since version 6.4, the EPSG database is also published as DDL and DML SQL statements that can be imported into other databases. The table and field names in this format are slightly different from the MS Access format. Because of these differences, the CoordinateSystemModifiedEPSGFactory modifies the SQL statements used to query these databases.
The default preferences for the driver, connection and implementation to use can be set by running the CoordinateSystemEPSGFactory main method and giving values for these parameters. These preferences are stored in the system preferences (/etc/.java/.systemPrefs/ on *nix systems) and may require administrative privileges to set. Once set, CoordinateSystemEPSGFactory.getDefault() will use these preferences when it is invoked.
Example 9. Setting CoordinateSystemEPSGFactory preferences
[root@localhost gt2cts2]# java org.geotools.cs.CoordinateSystemEPSGFactory \
-driver org.postgresql.Driver \
-implementation org.geotools.cs.CoordinateSystemModifiedEPSGFactory \
-connection 'jdbc:postgresql://localhost:5432/epsg64;user=rschulz;pass='
Loaded "org.postgresql.Driver" JDBC driver version 7.3.
Connection: "jdbc:postgresql://localhost:5432/epsg64;user=rschulz;pass="
If you cannot set these preferences, a CoordinateSystemFactory can be created by giving your connection and driver to the CoordinateSystemModifiedEPSGFactory constructor.
The CoordinateSystemEPSGFactory main method is also useful to test the database and get WKT representations of objects, as shown in the following example.
Example 10. CoordinateSystemEPSGFactory command line usage
[rschulz@localhost gt2cts2]$ java org.geotools.cs.CoordinateSystemEPSGFactory 2984 <=== EPSG 2984 ===> PROJCS["RGNC 1991 / Lambert New Caledonia", GEOGCS["RGNC 1991", DATUM["Reseau Geodesique Nouvelle Caledonie 1991", SPHEROID["International 1924", 6378388.0, 297.0, AUTHORITY["EPSG","7022"]], TOWGS84[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], AUTHORITY["EPSG","6645"]], PRIMEM["Greenwich", 0.0, AUTHORITY["EPSG","8901"]], UNIT["degree of angle",0.017453292519943295], AXIS["Geodetic latitude",NORTH], AXIS["Geodetic longitude",EAST], AUTHORITY["EPSG","4645"]], PROJECTION["Lambert_Conformal_Conic_2SP"], PARAMETER["semi_major", 6378388.0], PARAMETER["semi_minor", 6356911.9461279465], PARAMETER["central_meridian", 166.0], PARAMETER["latitude_of_origin", -21.5], PARAMETER["false_easting", 400000.0], PARAMETER["false_northing", 300000.0], PARAMETER["standard_parallel_1", -20.666666666666668], PARAMETER["standard_parallel_2", -22.333333333333332], UNIT["metre",1.0], AXIS["Easting",EAST], AXIS["Northing",NORTH], AUTHORITY["EPSG","2984"]]
For information about setting up the EPSG database in PostgreSQL please see the EPSG Postgres Plugin.
Section 3 - Interfaces
One of Geotools2's goals is to ensure interoperability with standard Java packages like Java2D and Java Advanced Imaging. However, it is not always possible to leverage Java classes and be OpenGIS compliant at the same time. The Geotools2 CTS module works around this problem by being split into two sets of packages:
- org.opengis
- org.geotools
In the previous sections of this tutorial, the code examples use the Java classes in org.geotools.
org.opengis
The org.opengis package is the standard OpenGIS specification. Classes in this package are mostly interfaces, with no implementation. Interfaces defined here are suitable for RMI use, which implies that every method is declared to throw a java.rmi.RemoteException. Developers looking for strict OpenGIS compliance and/or RMI use may use this package.
Adapters for OGC interfaces
Factory classes allow the creation of OpenGIS objects (RMI enabled) backed by Geotools classes, and the converse (Geotools classes used as a wrapper around OpenGIS's interfaces). Internally, interoperability between OpenGIS interfaces and Geotools classes is assured through a set of RMI-aware adapters. Adapters are designed in such a way that the API never hides a java.rmi.RemoteException into an unchecked exception. RemoteExceptions are either declared to be thrown when applicable, or are caught and re-thrown as another checked exception (e.g. org.geotools.ct.TransformException) with the remote exception as its cause.
The following example shows the use of these adapters.
Example 11. Adapter for OGC Interfaces
Ellipsoid e = CoordinateSystemFactory.createEllipsoid(...);
CS_Ellipsoid oe = Adapters.export(e);
//to unwrap the java object
e = Adapters.wrap(oe);
org.geotools
The org.geotools.pt, ct and cs packages are org.opengis mirrors tuned for Java and local use. They contain concrete classes (not interfaces). There is generally a 1 to 1 correspondence between OpenGIS interfaces and Geotools CTS classes, so developers familiar with one package should be familiar with the other. Geotools CTS methods do not throw RemoteExceptions, which makes them easier for local use but not suitable for RMI use. Developers looking for Java2D interoperability and/or local use may use these packages.
New java interfaces
New java interfaces are being created to follow the new OpenGIS Spatial Referencing by Coordinates (Topic 2) abstract specification (ISO-19111). Current work on these interfaces is available at http://geoapi.sourceforge.net/snapshot/javadoc/index.html under "Spatial Referencing by Coordinates".
Once these interfaces are finished, they will be implemented in the Geotools2 CTS module: replacing the current org.opengis interfaces described above.
The major concepts discussed in this tutorial will not change when the new interfaces are adopted. However, there are some minor changes between the old and new specifications which are described in the Legacy OpenGIS API page. The main difference is that what is described above as a CoordinateSystem will be divided into two classes:
- CoordinateReferenceSystem - consists of a single CoordinateSystem and a single Datum that ties the coordinate system to the real world (usually Earth).
- CoordinateSystem - a set of mathematical rules for specifying how coordinates are to be assigned to points (ie. a Cartesian or Polar coordinate system).
Also, the new abstract specification does not describe any interfaces for Factories or MathTransforms. These will be added to the interfaces in the GeoAPI project.
Section 4 - Miscellaneous
This is a section for stuff that does not fit in with the above sections.
Calculations on an Ellipsoid
Orthodromic distance (shortest distance on a spheroid's surface) between two geographic coordinates can be calculated using the Ellipsoid::orthodromicDistance() method.
Thank you for the feedback. I will be updating this tutorial sometime in the fall (I hope) and will include a section discussing the uses of these CS's and MathTransforms.
Right now I can think of three uses (I have only tried #2):
1) the j2d renderer allows the CS's of datasets and the renderer to be specified and changed. This allows you to project different datasets to the same CS. The transformation is done on the rendered geometries, so this does not affect the datasets.
2) As you mention, another common usage is to transform dataset geometries from one CS to another. Since we are using JTS for geometries right now, this can be done with a com.vividsolutions.jts.geom.CoordinateFilter that applies a MathTransform to each coordinate. Unfortunately this is a naive way to do this and does not work on some geometries that cross map edges (like Antarctica). Doing this properly, in all cases, requires more logic than just calculating the new coordinate (splitting and merging polygon geometries) and may belong in something like an Operations API that acts on entire datasets (this API has not yet been created).
3) the Grid Coverage API uses CS's. There was also talk of having DataStores storing the CS of their data.
Do I understand correctly that a DataStore doesn't read in its own CS, even if the underlying mechanism for data storage contains the CS information? For example, ArcSDE has a table that stores a CS for every registered layer. At the moment, when I call
[code]
CoordinateReferenceSystem cs = myDataStore.getSchema("SCHEMA.LAYERNAME").
getDefaultGeometry().getCoordinateSystem();
[/code]
the result is always null. Is this the design of the API, or is the API feature incomplete, or am I just making the wrong call?
The DataStores have not implemented this yet and only return null for a CS. See todays IRC log (topic 4) for a discussion of this.
Thanks for the info. Any estimates as to when a candidate release will be available that implements getCoordinateSystem()?
As mentioned in the IRC log, Jody needs this functionality within the next few months. Of course the standare open source answer of "faster if you help" also applies here. However for shapefile, we need to parse the wkt in the .prj file. This will require updating the existing wkt parser to handle esri wkt or waiting for the more flexible wkt parser in the new referencing implementation.
I am familiar with geodetic distance and formulation (Sodano,
Vincenty, etc.) but I am wondering if there is such a thing as
"graticular/differential geodetic ("parageodetic"?) distance"--that
is, geodetic distance calculated along a graticular transverse
longitude ("Arc Path" [AP], spherically a "great circle"), rather
than on an auxiliary sphere? For instance, in the case of an antipodal
entry, the geodetic path will shift to a north-south meridian,
whereas the graticular distance would continue along the
graticular AP, all the way to the other side.
Where "M" is the meridional arcradius/radius of curvature and "N" is the "normal", what I've come up with is an omniversal arcradius/radius of curvature, that finds the equivalent M arcradius for any Lat at any azimuth, not just north-south, using just the graticular (i.e., geographic, not "reduced" or otherwise "conformal") Lat and Long:
O = [M^2 + sin(Az_g)^2 * (N^2 - M^2)]^.5
(Az_g is likewise the graticular/non-elliptic
azimuth at Lat)
sin(Az_e) = sin(Az_g) * [N/O]
tan(Az_e) = tan(Az_g) * [N/M]
(Az_e is the elliptically correct
azimuth, at that point)
Hence, this is the elliptically correct "great-circle"
("great-ellipse"?) radius at a given point (which, at that point,
equals the graticularly equivalent geodetic arcradius).
Is this equation known--perhaps under another name/identity? Looking
at it, it seems so simple and obvious, yet I can't find it discussed
or discredited-or even acknowledged-anywheres!
Since it isn't as complex as standard geodetic formulation and follows
(spherical) orthodromic map/globe grid arcs, it would seem to be the
ideal, stsndard "great-ellipse" distance formula for navigation and
other applications requiring more than simple "great-circle" values!
Kaimbridge ( kaimbridge@programmer.net )
[BTW, unless you are using that model (Hayford-1909) for a specific
reason (i.e., a volumous (sp?) data base requiring redoing), you may
want to update your reference spheroid for Earth: 6378.135, 6356.75
("Global 2000 Spheroid"-"G2KS") is a good general purpose model, well
within tight range of the more "official", regional models (e.g.,
GRS-80, NAD-83, WGS-84)! P=)
This article is very clear so far as it goes if you've programmed with GIS objects before. However one thing that would be very welcome is relating this information to data sources. One of the most common tasks in GIS applications is handling data in different coordinate systems. For example, if a user opens two shape files with different coordinate systems, how do I tell geotools what those coordinate systems are? Using the directions here, I could easily parse the ".prj" file to produe a coordinate system, but it's not clear what I should do with that object once I've created it.
This is one of the most common programming tasks I've come across i the years I've been developing GIS enabled applications, since users often handle data from diverse sources with different preferred coordinate systems.