use of org.apache.sis.referencing.cs.AbstractCS in project sis by apache.
the class GeodeticObjectParser method parseCoordinateSystem.
/**
* Parses a {@code "CS"} element followed by all {@code "AXIS"} elements.
* This element has the following pattern (simplified):
*
* {@preformat wkt
* CS["<type>", dimension],
* AXIS["<name>", NORTH | SOUTH | EAST | WEST | UP | DOWN | OTHER],
* UNIT["<name>", <conversion factor>],
* etc.
* }
*
* This element is different from all other elements parsed by {@code GeodeticObjectParser}
* in that its components are sibling elements rather than child elements of the CS element.
*
* <p>The optional {@code "UNIT[…]"} element shall be parsed by the caller. That element may appear after the
* {@code "CS[…]"} element (not inside). The unit may be forced to some dimension (e.g. {@code "LengthUnit"})
* or be any kind of unit, depending on the context in which this {@code parseCoordinateSystem(…)} method is
* invoked.</p>
*
* <h4>Variants of Cartesian type</h4>
* The {@link WKTKeywords#Cartesian} type may be used for projected, geocentric or other kinds of CRS.
* However while all those variants are of the same CS type, their axis names and directions differ.
* Current implementation uses the following rules:
*
* <ul>
* <li>If the datum is not geodetic, then the axes of the Cartesian CS are unknown.</li>
* <li>Otherwise if {@code dimension is 2}, then the CS is assumed to be for a projected CRS.</li>
* <li>Otherwise if {@code dimension is 3}, then the CS is assumed to be for a geocentric CRS.</li>
* </ul>
*
* @param parent the parent element.
* @param type the expected type (Cartesian | ellipsoidal | vertical | etc…), or null if unknown.
* @param dimension the minimal number of dimensions. Can be 1 if unknown.
* @param isWKT1 {@code true} if the parent element is an element from the WKT 1 standard.
* @param defaultUnit the contextual unit (usually {@code Units.METRE} or {@code Units.RADIAN}), or {@code null} if unknown.
* @param datum the datum of the enclosing CRS, or {@code null} if unknown.
* @return the {@code "CS"}, {@code "UNIT"} and/or {@code "AXIS"} elements as a Coordinate System, or {@code null}.
* @throws ParseException if an element can not be parsed.
* @throws FactoryException if the factory can not create the coordinate system.
*/
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 List<CoordinateSystemAxis> list = new ArrayList<>(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(new CoordinateSystemAxis[list.size()]);
// Take ORDER[n] elements in account.
Arrays.sort(axes, this);
}
}
/*
* If there is 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);
}
// Easting or Longitude axis name and abbreviation.
String nx = null, x = null;
// Northing or latitude axis name and abbreviation.
String ny = null, y = null;
// Depth, height or time axis name and abbreviation.
String nz = null, z = null;
AxisDirection dx = AxisDirection.EAST;
AxisDirection dy = AxisDirection.NORTH;
// Depth, height or time axis direction.
AxisDirection direction = null;
// Depth, height or time axis unit.
Unit<?> unit = defaultUnit;
switch(type) {
/*
* Cartesian — we can create axes only for geodetic datum, in which case the axes are for
* two-dimensional Projected or three-dimensional Geocentric CRS.
*/
case WKTKeywords.Cartesian:
{
if (!(datum instanceof GeodeticDatum)) {
throw parent.missingComponent(WKTKeywords.Axis);
}
if (defaultUnit == null) {
throw parent.missingComponent(WKTKeywords.LengthUnit);
}
if (is3D) {
// If dimension can not be 2, then CRS can not be Projected.
return Legacy.standard(defaultUnit.asType(Length.class));
}
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 (vt == VerticalDatumTypes.ELLIPSOIDAL) {
// Not allowed by ISO 19111 as a standalone axis, but SIS is
// tolerant to this case since it is sometime 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 = AxisDirection.OTHER;
nz = "Parametric";
z = "p";
break;
}
/*
* Unknown CS type — we can not 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 StringBuilder 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]);
}
// For error message.
dimension = (axes.length < 2) ? 2 : 3;
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]);
}
// For error message.
dimension = (axes.length < 2) ? 2 : 3;
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]);
}
// For error message.
dimension = (axes.length < 2) ? 2 : 3;
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.spherical:
{
if (axes.length != (dimension = 3))
break;
return csFactory.createSphericalCS(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);
}
use of org.apache.sis.referencing.cs.AbstractCS in project sis by apache.
the class GridExtentCRS method build.
/**
* Builds a coordinate reference system for the given axis types. The CRS type is always engineering.
* We can not create temporal CRS because we do not know the temporal datum origin.
*
* @param gridToCRS matrix of the transform used for converting grid cell indices to envelope coordinates.
* It does not matter whether it maps pixel center or corner (translation coefficients are ignored).
* @param types the value of {@link GridExtent#types} or a default value (shall not be {@code null}).
* @param locale locale to use for axis names, or {@code null} for default.
* @return CRS for the grid, or {@code null}.
*
* @see GridExtent#typeFromAxes(CoordinateReferenceSystem, int)
*/
static EngineeringCRS build(final Matrix gridToCRS, final DimensionNameType[] types, final Locale locale) throws FactoryException {
final int tgtDim = gridToCRS.getNumRow() - 1;
final int srcDim = Math.min(gridToCRS.getNumCol() - 1, types.length);
final CoordinateSystemAxis[] axes = new CoordinateSystemAxis[tgtDim];
final CSFactory csFactory = DefaultFactories.forBuildin(CSFactory.class);
boolean hasVertical = false;
boolean hasTime = false;
boolean hasOther = false;
for (int i = 0; i < srcDim; i++) {
final DimensionNameType type = types[i];
if (type != null) {
/*
* Try to locate the CRS dimension corresponding to grid dimension j.
* We expect a one-to-one matching; if it is not the case, return null.
* Current version does not accept scale factors, but we could revisit
* in a future version if there is a need for it.
*/
int target = -1;
double scale = 0;
for (int j = 0; j < tgtDim; j++) {
final double m = gridToCRS.getElement(j, i);
if (m != 0) {
if (target >= 0 || axes[j] != null || Math.abs(m) != 1) {
return null;
}
target = j;
scale = m;
}
}
if (target < 0) {
return null;
}
/*
* This hard-coded set of axis directions is the converse of
* GridExtent.AXIS_DIRECTIONS map.
*/
String abbreviation;
AxisDirection direction;
if (type == DimensionNameType.COLUMN || type == DimensionNameType.SAMPLE) {
abbreviation = "x";
direction = AxisDirection.COLUMN_POSITIVE;
} else if (type == DimensionNameType.ROW || type == DimensionNameType.LINE) {
abbreviation = "y";
direction = AxisDirection.ROW_POSITIVE;
} else if (type == DimensionNameType.VERTICAL) {
abbreviation = "z";
direction = AxisDirection.UP;
hasVertical = true;
} else if (type == DimensionNameType.TIME) {
abbreviation = "t";
direction = AxisDirection.FUTURE;
hasTime = true;
} else {
abbreviation = abbreviation(target);
direction = AxisDirection.OTHER;
hasOther = true;
}
/*
* Verify that no other axis has the same direction and abbreviation. If duplicated
* values are found, keep only the first occurrence in grid axis order (may not be
* the CRS axis order).
*/
for (int k = tgtDim; --k >= 0; ) {
final CoordinateSystemAxis previous = axes[k];
if (previous != null) {
if (direction.equals(AxisDirections.absolute(previous.getDirection()))) {
direction = AxisDirection.OTHER;
hasOther = true;
}
if (abbreviation.equals(previous.getAbbreviation())) {
abbreviation = abbreviation(target);
}
}
}
if (scale < 0) {
direction = AxisDirections.opposite(direction);
}
final String name = Types.toString(Types.getCodeTitle(type), locale);
axes[target] = axis(csFactory, name, abbreviation, direction);
}
}
/*
* Search for axes that have not been created in above loop.
* It happens when some axes have no associated `DimensionNameType` code.
*/
for (int j = 0; j < tgtDim; j++) {
if (axes[j] == null) {
final String name = Vocabulary.getResources(locale).getString(Vocabulary.Keys.Dimension_1, j);
final String abbreviation = abbreviation(j);
axes[j] = axis(csFactory, name, abbreviation, AxisDirection.OTHER);
}
}
/*
* Create a coordinate system of affine type if all axes seem spatial.
* If no specialized type seems to fit, use an unspecified ("abstract")
* coordinate system type in last resort.
*/
final Map<String, ?> properties = properties("Grid extent");
final CoordinateSystem cs;
if (hasOther || (tgtDim > (hasTime ? 1 : 3))) {
cs = new AbstractCS(properties, axes);
} else
switch(tgtDim) {
case 1:
{
final CoordinateSystemAxis axis = axes[0];
if (hasVertical) {
cs = csFactory.createVerticalCS(properties, axis);
} else if (hasTime) {
cs = csFactory.createTimeCS(properties, axis);
} else {
cs = csFactory.createLinearCS(properties, axis);
}
break;
}
case 2:
cs = csFactory.createAffineCS(properties, axes[0], axes[1]);
break;
case 3:
cs = csFactory.createAffineCS(properties, axes[0], axes[1], axes[2]);
break;
default:
return null;
}
return DefaultFactories.forBuildin(CRSFactory.class).createEngineeringCRS(properties(cs.getName()), CommonCRS.Engineering.GRID.datum(), cs);
}
use of org.apache.sis.referencing.cs.AbstractCS in project sis by apache.
the class AbstractCRS method forConvention.
/**
* Returns a coordinate reference system equivalent to this one but with axes rearranged according the given
* convention. If this CRS is already compatible with the given convention, then this method returns {@code this}.
*
* @param convention the axes convention for which a coordinate reference system is desired.
* @return a coordinate reference system compatible with the given convention (may be {@code this}).
*
* @see AbstractCS#forConvention(AxesConvention)
*/
public synchronized AbstractCRS forConvention(final AxesConvention convention) {
ensureNonNull("convention", convention);
AbstractCRS crs = getCached(convention);
if (crs == null) {
final AbstractCS cs = AbstractCS.castOrCopy(coordinateSystem);
final AbstractCS candidate = cs.forConvention(convention);
if (candidate == cs) {
crs = this;
} else {
/*
* Copy properties (scope, domain of validity) except the identifier (e.g. "EPSG:4326")
* because the modified CRS is no longer conform to the authoritative definition.
* If name contains a namespace (e.g. "EPSG"), remove that namespace for the same reason.
* For example "EPSG:WGS 84" will become simply "WGS 84".
*/
Map<String, ?> properties = IdentifiedObjects.getProperties(this, IDENTIFIERS_KEY);
ReferenceIdentifier name = getName();
if (name.getCodeSpace() != null || name.getAuthority() != null) {
name = new NamedIdentifier(null, name.getCode());
final Map<String, Object> copy = new HashMap<>(properties);
copy.put(NAME_KEY, name);
properties = copy;
}
crs = createSameType(properties, candidate);
}
crs = setCached(convention, crs);
}
return crs;
}
Aggregations