org.geolatte.geom.codec.CrsWktDecoder Maven / Gradle / Ivy
Show all versions of geolatte-geom Show documentation
/*
* This file is part of the GeoLatte project.
*
* GeoLatte is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* GeoLatte is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with GeoLatte. If not, see .
*
* Copyright (C) 2010 - 2011 and Ownership of code is shared by:
* Qmino bvba - Romeinsestraat 18 - 3001 Heverlee (http://www.qmino.com)
* Geovise bvba - Generaal Eisenhowerlei 9 - 2140 Antwerpen (http://www.geovise.com)
*/
package org.geolatte.geom.codec;
import org.geolatte.geom.Position;
import org.geolatte.geom.crs.*;
import java.util.ArrayList;
import java.util.List;
/**
* A decoder for CoordinateReferenceSystem
definitions in WKT.
*
* The current implementation ensures that the postgis CRS WKT's are correctly interpreted. There are
* some minor differences with the OGC specification: "Coordinate Transformation Services (rev. 1.00)".
*
* The implementation uses a recursive-decent parsing approach.
*
* This class is not thread-safe.
*
* @author Karel Maesen, Geovise BVBA
* creation-date: 8/2/11
*/
public class CrsWktDecoder {
private final static CrsWktVariant CRS_TOKENS = new CrsWktVariant();
private int srid = 0;
private final WktVariant wktVariant;
protected WktToken currentToken;
private CrsWktTokenizer tokenizer;
/**
* Initiates a new CrsWktDecoder
that uses the CrsWktVariant
.
*/
public CrsWktDecoder() {
wktVariant = CRS_TOKENS;
}
/**
* Decodes a WKT representation of a CoordinateReferenceSystem
.
*
* @param wkt the WKT string to decode
* @return The CoordinateReferenceSystem
that is encoded in the input WKT.
*/
public CoordinateReferenceSystem extends Position> decode(String wkt, int srid) {
this.srid = srid;
setTokenizer(new CrsWktTokenizer(wkt, getWktVariant()));
nextToken();
return decode();
}
/**
* Determines the WKT variant and calls the according method to decode the WKT.
*
* @return The decoded WKT as a CoordinateReferenceSystem
object.
*/
private CoordinateReferenceSystem extends Position> decode() {
if (currentToken == CrsWktVariant.PROJCS) {
return decodeProjectedCrs();
} else if (currentToken == CrsWktVariant.GEOGCS) {
return decodeGeographicCrs();
} else if (currentToken == CrsWktVariant.GEOCCS) {
return decodeGeocentricCrs();
} else if (currentToken == CrsWktVariant.COMPD_CS) {
return decodeCompoundCrs();
} else if (currentToken == CrsWktVariant.VERT_CS) {
return decodeVertCS();
}
throw new WktDecodeException("Expected Wkt Token PROJCS, GEOGCS, GEOCCS or COMPD_CS. Received " + currentToken);
}
/**
* e * Currently not used in Postgis and also not implemented here!
*
* @throws UnsupportedConversionException Geocentric CRS is currently not implemented
*/
private GeocentricCartesianCoordinateReferenceSystem decodeGeocentricCrs() {
String crsName = decodeName();
matchesElementSeparator();
Datum datum = decodeDatum();
matchesElementSeparator();
PrimeMeridian primem = decodePrimem();
matchesElementSeparator();
Unit unit = decodeUnit(true);
CoordinateSystemAxis[] axes = decodeOptionalAxes(3, unit, GeocentricCartesianCoordinateReferenceSystem.class);
CrsId cr = decodeOptionalAuthority(srid);
matchesCloseList();
GeocentricCartesianCoordinateReferenceSystem system = new GeocentricCartesianCoordinateReferenceSystem(cr, crsName,
datum, primem,
new CartesianCoordinateSystem3D((StraightLineAxis) axes[0], (StraightLineAxis) axes[1], (VerticalStraightLineAxis) axes[2])
);
return system;
}
/**
* Implementation to decode a Geographic CRS.
*
* @return The GeographicCoordinateReferenceSystem
that is decoded from the WKT.
*/
private Geographic2DCoordinateReferenceSystem decodeGeographicCrs() {
String crsName = decodeName();
matchesElementSeparator();
Datum datum = decodeDatum();
matchesElementSeparator();
PrimeMeridian primem = decodePrimem();
matchesElementSeparator();
Unit unit = decodeUnit(false);
CoordinateSystemAxis[] twinAxes = decodeOptionalAxes(2, unit, Geographic2DCoordinateReferenceSystem.class);
CrsId cr = decodeOptionalAuthority(srid);
matchesCloseList();
Geographic2DCoordinateReferenceSystem system = new Geographic2DCoordinateReferenceSystem(cr, crsName, new
EllipsoidalCoordinateSystem2D((EllipsoidalAxis) twinAxes[0], (EllipsoidalAxis) twinAxes[1]));
system.setDatum(datum);
system.setPrimeMeridian(primem);
return system;
}
/**
* Implementation to decode a Projected CRS.
*
* @return The ProjectedCoordinateReferenceSystem
that is decoded from the WKT.
*/
private ProjectedCoordinateReferenceSystem decodeProjectedCrs() {
String crsName = decodeName();
matchesElementSeparator();
Geographic2DCoordinateReferenceSystem geogcs = decodeGeographicCrs();
matchesElementSeparator();
Unit unit;
Projection projection;
List parameters;
// spatial_reference.sql contains both variants of ProjCRS Wkt
if (currentToken == CrsWktVariant.UNIT) {
unit = decodeUnit(true);
projection = decodeProjection();
parameters = decodeOptionalParameters();
} else {
projection = decodeProjection();
parameters = decodeOptionalParameters();
unit = decodeUnit(true);
}
CoordinateSystemAxis[] twinAxes = decodeOptionalAxes(2, unit, ProjectedCoordinateReferenceSystem.class);
Extension extension = decodeOptionalExtension();
CrsId crsId = decodeOptionalAuthority(srid);
matchesCloseList();
return new ProjectedCoordinateReferenceSystem(crsId, crsName, geogcs, projection, parameters,
new CartesianCoordinateSystem2D((StraightLineAxis) twinAxes[0], (StraightLineAxis) twinAxes[1]), extension);
}
private CompoundCoordinateReferenceSystem
decodeCompoundCrs() {
String crsName = decodeName();
matchesElementSeparator();
SingleCoordinateReferenceSystem> head = (SingleCoordinateReferenceSystem>) decode();
matchesElementSeparator();
SingleCoordinateReferenceSystem> tail = (SingleCoordinateReferenceSystem>) decode();
CrsId cr = decodeOptionalAuthority(srid);
return new CompoundCoordinateReferenceSystem
(cr, crsName, head, tail);
}
private VerticalCoordinateReferenceSystem decodeVertCS() {
String crsName = decodeName();
matchesElementSeparator();
VerticalDatum vdatum = decodeVertDatum();
matchesElementSeparator();
LinearUnit unit = (LinearUnit) decodeUnit(true);
matchesElementSeparator();
VerticalStraightLineAxis axis = (VerticalStraightLineAxis) decodeAxis(unit, VerticalCoordinateReferenceSystem.class);
CrsId id = decodeOptionalAuthority();
return new VerticalCoordinateReferenceSystem(id, crsName, vdatum, axis);
}
private VerticalDatum decodeVertDatum() {
if (currentToken != CrsWktVariant.VERT_DATUM) {
throw new WktDecodeException("Expected VERT_DATUM keyword, found " + currentToken.toString());
}
String name = decodeName();
matchesElementSeparator();
int type = decodeInt();
Extension extension = decodeOptionalExtension();
CrsId authority = decodeOptionalAuthority(srid);
matchesCloseList();
return new VerticalDatum(authority, name, type, extension);
}
private List decodeOptionalParameters() {
List parameters = new ArrayList();
CrsParameter parameter = decodeOptionalParameter();
while (parameter != null) {
parameters.add(parameter);
parameter = decodeOptionalParameter();
}
return parameters;
}
private CrsParameter decodeOptionalParameter() {
matchesElementSeparator();
if (currentToken != CrsWktVariant.PARAMETER) {
return null;
}
nextToken();
String name = decodeName();
matchesElementSeparator();
double value = decodeNumber();
matchesCloseList();
return new CrsParameter(name, value);
}
private Projection decodeProjection() {
matchesElementSeparator();
if (currentToken != CrsWktVariant.PROJECTION) {
throw new WktDecodeException("Expected PROJECTION keyword, found " + currentToken.toString());
}
String name = decodeName();
CrsId crsId = decodeOptionalAuthority(CrsId.UNDEFINED.getCode());
matchesCloseList();
return new Projection(crsId, name);
}
private CoordinateSystemAxis[] decodeOptionalAxes(int num, Unit unit, Class crsClass) {
matchesElementSeparator();
if (currentToken != CrsWktVariant.AXIS) {
return defaultCRS(unit, crsClass);
}
CoordinateSystemAxis[] axes = new CoordinateSystemAxis[num];
for (int i = 0; i < num; i++) {
if (i > 0) matchesElementSeparator();
axes[i] = decodeAxis(unit, crsClass);
}
return axes;
}
private CoordinateSystemAxis[] defaultCRS(Unit unit, Class crsClass) {
if (Geographic2DCoordinateReferenceSystem.class.isAssignableFrom(crsClass)) {
return new CoordinateSystemAxis[]{
new GeodeticLongitudeCSAxis("Lon", (AngularUnit) unit),
new GeodeticLatitudeCSAxis("Lat", (AngularUnit) unit)
};
}
if (ProjectedCoordinateReferenceSystem.class.isAssignableFrom(crsClass)) {
return new CoordinateSystemAxis[]{
new StraightLineAxis("X", CoordinateSystemAxisDirection.EAST, (LinearUnit) unit),
new StraightLineAxis("Y", CoordinateSystemAxisDirection.NORTH, (LinearUnit) unit)
};
}
if (GeocentricCartesianCoordinateReferenceSystem.class.isAssignableFrom(crsClass)) {
return new CoordinateSystemAxis[]{
new StraightLineAxis("X", CoordinateSystemAxisDirection.GeocentricX, (LinearUnit) unit),
new StraightLineAxis("Y", CoordinateSystemAxisDirection.GeocentricY, (LinearUnit) unit),
new StraightLineAxis("Z", CoordinateSystemAxisDirection.GeocentricZ, (LinearUnit) unit)
};
}
throw new IllegalStateException("Can't create default for CrsRegistry of type " + crsClass.getCanonicalName());
}
private CoordinateSystemAxis decodeAxis(Unit unit, Class crsClass) {
if (currentToken != CrsWktVariant.AXIS) {
throw new WktDecodeException("Expected AXIS keyword, found " + currentToken.toString());
}
String name = decodeName();
matchesElementSeparator();
CoordinateSystemAxisDirection direction = CoordinateSystemAxisDirection.valueOf(currentToken.toString());
nextToken();
matchesCloseList();
if (Geographic2DCoordinateReferenceSystem.class.isAssignableFrom(crsClass)) {
if (direction.equals(CoordinateSystemAxisDirection.NORTH)) {
return new GeodeticLatitudeCSAxis(name, (AngularUnit) unit);
} else if (direction.equals(CoordinateSystemAxisDirection.EAST)) {
return new GeodeticLongitudeCSAxis(name, (AngularUnit) unit);
} else {
throw new IllegalStateException("Axis in horizontal Geographic coordinate system is neither latitude," +
" nor longitude");
}
}
if (ProjectedCoordinateReferenceSystem.class.isAssignableFrom(crsClass)) {
//this fixes problems with some polar projection systems.
if (direction == CoordinateSystemAxisDirection.UNKNOWN) {
if (name.equalsIgnoreCase("X") || name.equalsIgnoreCase("Easting")) {
return new StraightLineAxis(name, direction, 0, unit);
} else {
return new StraightLineAxis(name, direction, 1, unit);
}
}
return new StraightLineAxis(name, direction, (LinearUnit) unit);
}
//here we normalize based on the name, because usage of direction in Postgis can't be relied on
if (GeocentricCartesianCoordinateReferenceSystem.class.isAssignableFrom(crsClass)) {
String normalizedName = name.toUpperCase();
if (normalizedName.equalsIgnoreCase("GEOCENTRIC X")) {
return new StraightLineAxis(name, CoordinateSystemAxisDirection.GeocentricX, (LinearUnit) unit);
} else if (normalizedName.equalsIgnoreCase("GEOCENTRIC Y")) {
return new StraightLineAxis(name, CoordinateSystemAxisDirection.GeocentricY, (LinearUnit) unit);
} else {
return new VerticalStraightLineAxis(name, CoordinateSystemAxisDirection.GeocentricZ, (LinearUnit) unit);
}
}
if (VerticalCoordinateReferenceSystem.class.isAssignableFrom(crsClass)) {
return new VerticalStraightLineAxis(name, direction, (LinearUnit) unit);
}
throw new IllegalStateException("Can't create default for CrsRegistry of type " + crsClass.getCanonicalName());
}
private Unit decodeUnit(boolean isLinear) {
if (currentToken != CrsWktVariant.UNIT) {
throw new WktDecodeException("Expected UNIT keyword, found " + currentToken.toString());
}
String name = decodeName();
matchesElementSeparator();
double cf = decodeNumber();
matchesElementSeparator();
CrsId crsId = decodeOptionalAuthority(CrsId.UNDEFINED.getCode());
matchesCloseList();
return isLinear ? new LinearUnit(crsId, name, cf) : new AngularUnit(crsId, name, cf);
}
private PrimeMeridian decodePrimem() {
if (currentToken != CrsWktVariant.PRIMEM) {
throw new WktDecodeException("Expected PRIMEM keyword, received " + currentToken.toString());
}
String name = decodeName();
matchesElementSeparator();
double longitude = decodeNumber();
CrsId crsId = decodeOptionalAuthority(CrsId.UNDEFINED.getCode());
matchesCloseList();
return new PrimeMeridian(crsId, name, longitude);
}
private Datum decodeDatum() {
if (currentToken != CrsWktVariant.DATUM) {
throw new WktDecodeException("Expected DATUM token.");
}
String datumName = decodeName();
matchesElementSeparator();
Ellipsoid ellipsoid = decodeSpheroid();
double[] toWGS84 = decodeOptionalToWGS84();
CrsId crsId = decodeOptionalAuthority(CrsId.UNDEFINED.getCode());
matchesCloseList();
return new Datum(crsId, ellipsoid, datumName, toWGS84);
}
private double[] decodeOptionalToWGS84() {
matchesElementSeparator();
if (currentToken != CrsWktVariant.TOWGS84) {
return new double[0];
}
nextToken();
double[] toWGS = new double[7];
matchesOpenList();
for (int i = 0; i < 7; i++) { //TODO -- what with 3-parameter variants?
toWGS[i] = decodeNumber();
matchesElementSeparator();
}
matchesCloseList();
return toWGS;
}
private Ellipsoid decodeSpheroid() {
if (currentToken != CrsWktVariant.SPHEROID) {
throw new WktDecodeException("Expected SPHEROID keyword, but received " + currentToken.toString());
}
String ellipsoidName = decodeName();
matchesElementSeparator();
double semiMajorAxis = decodeNumber();
matchesElementSeparator();
double inverseFlattening = decodeNumber();
CrsId crsId = decodeOptionalAuthority(CrsId.UNDEFINED.getCode());
matchesCloseList();
return new Ellipsoid(crsId, ellipsoidName, semiMajorAxis, inverseFlattening);
}
private CrsId decodeOptionalAuthority() {
return decodeOptionalAuthority(CrsId.UNDEFINED.getCode());
}
private Extension decodeOptionalExtension() {
matchesElementSeparator();
if (currentToken != CrsWktVariant.EXTENSION) {
return null;
}
nextToken();
matchesOpenList();
String prop = decodeText();
matchesElementSeparator();
String val = decodeText();
matchesCloseList();
return new Extension(prop, val);
}
private CrsId decodeOptionalAuthority(int srid) {
matchesElementSeparator();
if (currentToken != CrsWktVariant.AUTHORITY) {
return new CrsId("EPSG", srid);
}
nextToken();
matchesOpenList();
String authority = decodeText();
matchesElementSeparator();
int value = decodeInt();
matchesCloseList();
return new CrsId(authority, value);
}
private String decodeName() {
nextToken();
matchesOpenList();
return decodeText();
}
protected void setTokenizer(CrsWktTokenizer tokenizer) {
this.tokenizer = tokenizer;
this.currentToken = null;
}
/**
* Returns the text and moves to the next token if the current token matches text, otherwise throws an exception.
*
* @return the matched text
* @throws WktDecodeException when the current token does not match text.
*/
protected String decodeText() {
if (currentToken instanceof WktTextToken) {
String text = ((WktTextToken) currentToken).getText();
nextToken();
return text;
}
throw new WktDecodeException("Expected text token, received " + currentToken.toString());
}
protected int decodeInt() {
if (currentToken instanceof WktNumberToken) {
double num = ((WktNumberToken) currentToken).getNumber();
nextToken();
try {
return (int) num;
} catch (Exception e) {
throw new WktDecodeException("Expected Integer, received " + currentToken.toString());
}
} else if (currentToken instanceof WktTextToken) {
String text = ((WktTextToken) currentToken).getText();
nextToken();
try {
return Integer.parseInt(text);
} catch (NumberFormatException e) {
throw new WktDecodeException("Expected Integer, received " + currentToken.toString());
}
}
throw new WktDecodeException("Expected text token, received " + currentToken.toString());
}
/**
* Advances the decoding to the next token.
*/
protected void nextToken() {
currentToken = tokenizer.nextToken();
}
/**
* Returns true and moves to the next token if the current token matches the open list token.
*
* @return True if the current token matches the open list token, false otherwise.
*/
protected boolean matchesOpenList() {
if (currentToken == getWktVariant().getOpenList()) {
nextToken();
return true;
}
return false;
}
/**
* Returns true and moves to the next token if the current token matches the close list token.
*
* @return True if the current token matches the close list token, false otherwise.
*/
protected boolean matchesCloseList() {
if (currentToken == getWktVariant().getCloseList()) {
nextToken();
return true;
}
return false;
}
/**
* Returns true and moves to the next token if the current token matches the element separator token.
*
* @return True if the current token matches the element separator token, false otherwise.
*/
protected boolean matchesElementSeparator() {
if (currentToken == getWktVariant().getElementSeparator()) {
nextToken();
return true;
}
return false;
}
/**
* Returns the value of the current token and moves to the next token if the current token matches a number.
*
* @return The value of the current token if the current token matches a number.
* @throws WktDecodeException if the current token does not match a number.
*/
protected double decodeNumber() {
if (currentToken instanceof WktNumberToken) {
double value = ((WktNumberToken) currentToken).getNumber();
nextToken();
return value;
}
throw new WktDecodeException("Expected a number ; received " + currentToken.toString());
}
/**
* Returns the WktVariant
for this decoder.
*
* @return the WktVariant
for this decoder.
*/
protected WktVariant getWktVariant() {
return this.wktVariant;
}
/**
* Reports the current position of the tokenizer.
*
* @return the current position of the tokenizer.
*/
protected int getTokenizerPosition() {
return this.tokenizer.position();
}
}