in endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/GeodeticObjectParser.java [700:994]
private CoordinateSystem parseCoordinateSystem(final Element parent, String type, int dimension,
final boolean isWKT1, final Unit<?> defaultUnit, final Datum datum) throws ParseException, FactoryException
{
axisOrder.clear();
final boolean is3D = (dimension >= 3);
Map<String,Object> csProperties = null;
/*
* Parse the CS[<type>, <dimension>] element. This is specific to the WKT 2 format.
* In principle the CS element is mandatory, but the Apache SIS parser is lenient on
* this aspect: if the CS element is not present, we will compute the same defaults
* than what we do for WKT 1.
*/
if (!isWKT1) {
final Element element = parent.pullElement(OPTIONAL, WKTKeywords.CS);
if (element != null) {
final String expected = type;
type = element.pullVoidElement("type").keyword;
dimension = element.pullInteger("dimension");
csProperties = new HashMap<>(parseMetadataAndClose(element, "CS", null));
if (expected != null) {
if (!expected.equalsIgnoreCase(type)) {
throw new UnparsableObjectException(errorLocale, Errors.Keys.UnexpectedValueInElement_2,
new String[] {WKTKeywords.CS, type}, element.offset);
}
}
if (dimension <= 0 || dimension >= Numerics.MAXIMUM_MATRIX_SIZE) {
final short key;
final Object[] args;
if (dimension <= 0) {
key = Errors.Keys.ValueNotGreaterThanZero_2;
args = new Object[] {"dimension", dimension};
} else {
key = Errors.Keys.ExcessiveNumberOfDimensions_1;
args = new Object[] {dimension};
}
throw new UnparsableObjectException(errorLocale, key, args, element.offset);
}
type = type.equalsIgnoreCase(WKTKeywords.Cartesian) ?
WKTKeywords.Cartesian : type.toLowerCase(symbols.getLocale());
}
}
/*
* AXIS[…] elements are optional, but if we find one we will request that there is as many axes
* as the number of dimensions. If there is more axes than expected, we may emit an error later
* depending on the CS type.
*
* AXIS[…] elements will be parsed for verifying the syntax, but otherwise ignored if the parsing
* convention is WKT1_IGNORE_AXES. This is for compatibility with the way some other libraries
* parse WKT 1.
*/
CoordinateSystemAxis[] axes = null;
CoordinateSystemAxis axis = parseAxis(type == null ? MANDATORY : OPTIONAL, parent, type, defaultUnit);
if (axis != null) {
final var list = new ArrayList<CoordinateSystemAxis>(dimension + 2);
do {
list.add(axis);
axis = parseAxis(list.size() < dimension ? MANDATORY : OPTIONAL, parent, type, defaultUnit);
} while (axis != null);
if (!isWKT1 || !ignoreAxes) {
axes = list.toArray(CoordinateSystemAxis[]::new);
Arrays.sort(axes, this); // Take ORDER[n] elements in account.
}
}
/*
* If there are no explicit AXIS[…] elements, or if the user asked to ignore them,
* then we need to create default axes. This is possible only if we know the type
* of the CS to create, and only for some of those CS types.
*/
final CSFactory csFactory = factories.getCSFactory();
if (axes == null) {
if (type == null) {
throw parent.missingComponent(WKTKeywords.Axis);
}
String nx = null, x = null; // Easting or Longitude axis name and abbreviation.
String ny = null, y = null; // Northing or latitude axis name and abbreviation.
String nz = null, z = null; // Depth, height or time axis name and abbreviation.
AxisDirection dx = AxisDirection.EAST;
AxisDirection dy = AxisDirection.NORTH;
AxisDirection direction = null; // Depth, height or time axis direction.
Unit<?> unit = defaultUnit; // Depth, height or time axis unit.
switch (type) {
/*
* Cartesian — we can create axes only if the datum is geodetic, in which case the axes
* are for two- or three-dimensional Projected or three-dimensional Geocentric CRS.
*/
case WKTKeywords.Cartesian: {
if (datum != null && !(datum instanceof GeodeticDatum)) {
throw parent.missingComponent(WKTKeywords.Axis);
}
if (defaultUnit == null) {
throw parent.missingComponent(WKTKeywords.LengthUnit);
}
if (is3D) { // If dimension cannot be 2, then CRS cannot be Projected.
return Legacy.standard(defaultUnit);
}
nx = AxisNames.EASTING; x = "E";
ny = AxisNames.NORTHING; y = "N";
if (dimension >= 3) { // Non-standard but SIS is tolerant to this case.
z = "h";
nz = AxisNames.ELLIPSOIDAL_HEIGHT;
unit = Units.METRE;
}
break;
}
/*
* Ellipsoidal — can be two- or three- dimensional, in which case the height can
* only be ellipsoidal height. The default axis order depends on the WKT version:
*
* - WKT 1 said explicitly that the default order is (longitude, latitude).
* - WKT 2 has no default, and allows only (latitude, longitude) order.
*/
case WKTKeywords.ellipsoidal: {
if (defaultUnit == null) {
throw parent.missingComponent(WKTKeywords.AngleUnit);
}
if (isWKT1) {
nx = AxisNames.GEODETIC_LONGITUDE; x = "λ";
ny = AxisNames.GEODETIC_LATITUDE; y = "φ";
} else {
nx = AxisNames.GEODETIC_LATITUDE; x = "φ"; dx = AxisDirection.NORTH;
ny = AxisNames.GEODETIC_LONGITUDE; y = "λ"; dy = AxisDirection.EAST;
}
if (dimension >= 3) {
direction = AxisDirection.UP;
z = "h";
nz = AxisNames.ELLIPSOIDAL_HEIGHT;
unit = Units.METRE;
}
break;
}
/*
* Vertical — the default name and symbol depends on whether this is depth,
* geoidal height, ellipsoidal height (non-standard) or other kind of heights.
*/
case WKTKeywords.vertical: {
if (defaultUnit == null) {
throw parent.missingComponent(WKTKeywords.Unit);
}
z = "h";
nz = "Height";
direction = AxisDirection.UP;
if (datum instanceof VerticalDatum) {
final VerticalDatumType vt = ((VerticalDatum) datum).getVerticalDatumType();
if (vt == VerticalDatumType.GEOIDAL) {
nz = AxisNames.GRAVITY_RELATED_HEIGHT;
z = "H";
} else if (vt == VerticalDatumType.DEPTH) {
direction = AxisDirection.DOWN;
nz = AxisNames.DEPTH;
z = "D";
} else if (VerticalDatumTypes.ellipsoidal(vt)) {
// Not allowed by ISO 19111 as a standalone axis, but SIS is
// tolerant to this case since it is sometimes hard to avoid.
nz = AxisNames.ELLIPSOIDAL_HEIGHT;
}
}
break;
}
/*
* Temporal — axis name and abbreviation not yet specified by ISO 19111.
*/
case WKTKeywords.temporal: {
if (defaultUnit == null) {
throw parent.missingComponent(WKTKeywords.TimeUnit);
}
direction = AxisDirection.FUTURE;
nz = "Time";
z = "t";
break;
}
/*
* Parametric — axis name and abbreviation not yet specified by ISO 19111_2.
*/
case WKTKeywords.parametric: {
if (defaultUnit == null) {
throw parent.missingComponent(WKTKeywords.ParametricUnit);
}
direction = AxisDirections.UNSPECIFIED;
nz = "Parametric";
z = "p";
break;
}
/*
* Unknown CS type — we cannot guess which axes to create.
*/
default: {
throw parent.missingComponent(WKTKeywords.Axis);
}
}
int i = 0;
axes = new CoordinateSystemAxis[dimension];
if (x != null && i < dimension) axes[i++] = csFactory.createCoordinateSystemAxis(singletonMap(CoordinateSystemAxis.NAME_KEY, nx), x, dx, defaultUnit);
if (y != null && i < dimension) axes[i++] = csFactory.createCoordinateSystemAxis(singletonMap(CoordinateSystemAxis.NAME_KEY, ny), y, dy, defaultUnit);
if (z != null && i < dimension) axes[i++] = csFactory.createCoordinateSystemAxis(singletonMap(CoordinateSystemAxis.NAME_KEY, nz), z, direction, unit);
// Not a problem if the array does not have the expected length for the CS type. This will be verified below in this method.
}
/*
* Infer a CS name will be inferred from the axes if possible.
* Example: "Compound CS: East (km), North (km), Up (m)."
*/
final String name;
{ // For keeping the `buffer` variable local to this block.
final var buffer = new StringBuilder();
if (type != null && !type.isEmpty()) {
final int c = type.codePointAt(0);
buffer.appendCodePoint(Character.toUpperCase(c))
.append(type, Character.charCount(c), type.length()).append(' ');
}
name = AxisDirections.appendTo(buffer.append("CS"), axes);
}
if (csProperties == null) {
csProperties = singletonMap(CoordinateSystem.NAME_KEY, name);
} else {
csProperties.put(CoordinateSystem.NAME_KEY, name);
}
if (type == null) {
/*
* Creates a coordinate system of unknown type. This block is executed during parsing of WKT version 1,
* since that legacy format did not specified any information about the coordinate system in use.
* This block should not be executed during parsing of WKT version 2.
*/
return new AbstractCS(csProperties, axes);
}
/*
* Finally, delegate to the factory method corresponding to the CS type and the number of axes.
*/
switch (type) {
case WKTKeywords.ellipsoidal: {
switch (axes.length) {
case 2: return csFactory.createEllipsoidalCS(csProperties, axes[0], axes[1]);
case 3: return csFactory.createEllipsoidalCS(csProperties, axes[0], axes[1], axes[2]);
}
dimension = (axes.length < 2) ? 2 : 3; // For error message.
break;
}
case WKTKeywords.spherical: {
switch (axes.length) {
case 2: if (csFactory instanceof GeodeticObjectFactory) {
return ((GeodeticObjectFactory) csFactory).createSphericalCS(csProperties, axes[0], axes[1]);
}
break;
case 3: return csFactory.createSphericalCS(csProperties, axes[0], axes[1], axes[2]);
}
dimension = (axes.length < 2) ? 2 : 3; // For error message.
break;
}
case WKTKeywords.Cartesian: {
switch (axes.length) {
case 2: return csFactory.createCartesianCS(csProperties, axes[0], axes[1]);
case 3: return csFactory.createCartesianCS(csProperties, axes[0], axes[1], axes[2]);
}
dimension = (axes.length < 2) ? 2 : 3; // For error message.
break;
}
case WKTKeywords.affine: {
switch (axes.length) {
case 2: return csFactory.createAffineCS(csProperties, axes[0], axes[1]);
case 3: return csFactory.createAffineCS(csProperties, axes[0], axes[1], axes[2]);
}
dimension = (axes.length < 2) ? 2 : 3; // For error message.
break;
}
case WKTKeywords.vertical: {
if (axes.length != (dimension = 1)) break;
return csFactory.createVerticalCS(csProperties, axes[0]);
}
case WKTKeywords.temporal: {
if (axes.length != (dimension = 1)) break;
return csFactory.createTimeCS(csProperties, axes[0]);
}
case WKTKeywords.linear: {
if (axes.length != (dimension = 1)) break;
return csFactory.createLinearCS(csProperties, axes[0]);
}
case WKTKeywords.polar: {
if (axes.length != (dimension = 2)) break;
return csFactory.createPolarCS(csProperties, axes[0], axes[1]);
}
case WKTKeywords.cylindrical: {
if (axes.length != (dimension = 3)) break;
return csFactory.createCylindricalCS(csProperties, axes[0], axes[1], axes[2]);
}
case WKTKeywords.parametric: {
if (axes.length != (dimension = 1)) break;
return ServicesForMetadata.createParametricCS(csProperties, axes[0], csFactory);
}
default: {
warning(parent, WKTKeywords.CS, Errors.formatInternational(Errors.Keys.UnknownType_1, type), null);
return new AbstractCS(csProperties, axes);
}
}
throw new UnparsableObjectException(errorLocale, (axes.length > dimension)
? Errors.Keys.TooManyOccurrences_2 : Errors.Keys.TooFewOccurrences_2,
new Object[] {dimension, WKTKeywords.Axis}, parent.offset);
}