use of org.opengis.referencing.crs.CoordinateReferenceSystem in project sis by apache.
the class AbstractEnvelope method toSimpleEnvelopes.
/**
* Returns this envelope as an array of simple (without wraparound) envelopes.
* The length of the returned array depends on the number of dimensions where a
* {@linkplain org.opengis.referencing.cs.RangeMeaning#WRAPAROUND wraparound} range is found.
* Typically, wraparound occurs only in the range of longitude values, when the range crosses
* the anti-meridian (a.k.a. date line). However this implementation will take in account any
* axis having wraparound {@linkplain CoordinateSystemAxis#getRangeMeaning() range meaning}.
*
* <p>Special cases:</p>
*
* <ul>
* <li>If this envelope {@linkplain #isEmpty() is empty}, then this method returns an empty array.</li>
* <li>If this envelope does not have any wraparound behavior, then this method returns {@code this}
* in an array of length 1. This envelope is <strong>not</strong> cloned.</li>
* <li>If this envelope crosses the <cite>anti-meridian</cite> (a.k.a. <cite>date line</cite>)
* then this method represents this envelope as two separated simple envelopes.
* <li>While uncommon, the envelope could theoretically crosses the limit of other axis having
* wraparound range meaning. If wraparound occur along <var>n</var> axes, then this method
* represents this envelope as 2ⁿ separated simple envelopes.
* </ul>
*
* @return a representation of this envelope as an array of non-empty envelope.
*
* @see Envelope2D#toRectangles()
* @see GeneralEnvelope#simplify()
*
* @since 0.4
*/
@SuppressWarnings("ReturnOfCollectionOrArrayField")
public Envelope[] toSimpleEnvelopes() {
// A bitmask of the dimensions having a "wrap around" behavior.
long isWrapAround = 0;
CoordinateReferenceSystem crs = null;
final int dimension = getDimension();
for (int i = 0; i != dimension; i++) {
// Do not use getSpan(i).
final double span = getUpper(i) - getLower(i);
if (!(span > 0)) {
// Use '!' for catching NaN.
if (!isNegative(span)) {
// Span is positive zero.
return EMPTY;
}
if (crs == null) {
crs = getCoordinateReferenceSystem();
}
if (!isWrapAround(crs, i)) {
return EMPTY;
}
if (i >= Long.SIZE) {
// a CRS is unusual enough for not being worth to make the distinction in the error message.
throw new IllegalStateException(Errors.format(Errors.Keys.ExcessiveListSize_2, "axis", dimension));
}
isWrapAround |= (1L << i);
}
}
/*
* The number of simple envelopes is 2ⁿ where n is the number of wraparound found. In most
* cases, isWrapAround == 0 so we have an array of length 1 containing only this envelope.
*/
final int bitCount = Long.bitCount(isWrapAround);
if (bitCount >= Integer.SIZE - 1) {
// Should be very unusual, but let be paranoiac.
throw new IllegalStateException(Errors.format(Errors.Keys.ExcessiveListSize_2, "wraparound", bitCount));
}
final Envelope[] envelopes = new Envelope[1 << bitCount];
if (envelopes.length == 1) {
envelopes[0] = this;
} else {
/*
* Need to create at least 2 envelopes. Instantiate now all envelopes with ordinate values
* initialized to a copy of this envelope. We will write directly in their internal arrays later.
*/
double[] c = new double[dimension * 2];
for (int i = 0; i < dimension; i++) {
c[i] = getLower(i);
c[i + dimension] = getUpper(i);
}
final double[][] ordinates = new double[envelopes.length][];
for (int i = 0; i < envelopes.length; i++) {
final GeneralEnvelope envelope = new GeneralEnvelope(i == 0 ? c : c.clone());
envelope.crs = crs;
envelopes[i] = envelope;
ordinates[i] = envelope.ordinates;
}
/*
* Assign the minimum and maximum ordinate values in the dimension where a wraparound has been found.
* The 'for' loop below iterates only over the 'i' values for which the 'isWrapAround' bit is set to 1.
*/
// For identifying whether we need to set the lower or the upper ordinate.
int mask = 1;
@SuppressWarnings("null") final CoordinateSystem // Should not be null at this point.
cs = crs.getCoordinateSystem();
for (int i; (i = Long.numberOfTrailingZeros(isWrapAround)) != Long.SIZE; isWrapAround &= ~(1L << i)) {
final CoordinateSystemAxis axis = cs.getAxis(i);
final double min = axis.getMinimumValue();
final double max = axis.getMaximumValue();
for (int j = 0; j < ordinates.length; j++) {
c = ordinates[j];
if ((j & mask) == 0) {
c[i + dimension] = max;
} else {
c[i] = min;
}
}
mask <<= 1;
}
}
return envelopes;
}
use of org.opengis.referencing.crs.CoordinateReferenceSystem in project sis by apache.
the class CoordinateFormat method format.
/**
* Formats the given coordinate and appends the resulting text to the given stream or buffer.
*
* @param position the coordinate to format.
* @param toAppendTo where the text is to be appended.
* @throws IOException if an error occurred while writing to the given appendable.
*/
@Override
@SuppressWarnings("UnnecessaryBoxing")
public void format(final DirectPosition position, final Appendable toAppendTo) throws IOException {
ArgumentChecks.ensureNonNull("position", position);
ArgumentChecks.ensureNonNull("toAppendTo", toAppendTo);
CoordinateReferenceSystem crs = position.getCoordinateReferenceSystem();
if (crs == null) {
// May still be null.
crs = defaultCRS;
}
if (crs != lastCRS) {
initialize(crs);
}
/*
* Standard java.text.Format API can only write into a StringBuffer. If the given Appendable is not a
* StringBuffer, then we will need to format in a temporary buffer before to copy to the Appendable.
*/
final StringBuffer destination;
if (toAppendTo instanceof StringBuffer) {
destination = (StringBuffer) toAppendTo;
} else {
if (buffer == null) {
buffer = new StringBuffer();
}
destination = buffer;
destination.setLength(0);
}
if (dummy == null) {
dummy = new FieldPosition(0);
}
/*
* The format to use for each ordinate has been computed by 'initialize'. The format array length
* should match the number of dimensions in the given position if the DirectPosition is consistent
* with its CRS, but we will nevertheless verify has a paranoiac check. If there is no CRS, or if
* the DirectPosition dimension is (illegally) greater than the CRS dimension, then we will format
* the ordinate as a number.
*/
final int dimension = position.getDimension();
for (int i = 0; i < dimension; i++) {
double value = position.getOrdinate(i);
final Object object;
final Format f;
if (formats != null && i < formats.length) {
f = formats[i];
if (isNegative(i)) {
value = -value;
}
if (toFormatUnit != null) {
final UnitConverter c = toFormatUnit[i];
if (c != null) {
value = c.convert(value);
}
}
switch(types[i]) {
default:
object = Double.valueOf(value);
break;
case LONGITUDE:
object = new Longitude(value);
break;
case LATITUDE:
object = new Latitude(value);
break;
case ANGLE:
object = new Angle(value);
break;
case DATE:
object = new Date(Math.round(value) + epochs[i]);
break;
}
} else {
object = value;
f = getFormat(Number.class);
}
/*
* At this point we got the value to format together with the Format instance to use.
*/
if (i != 0) {
toAppendTo.append(separator);
}
if (f.format(object, destination, dummy) != toAppendTo) {
toAppendTo.append(destination);
destination.setLength(0);
}
if (unitSymbols != null && i < unitSymbols.length) {
final String symbol = unitSymbols[i];
if (symbol != null) {
toAppendTo.append(Characters.NO_BREAK_SPACE).append(symbol);
}
}
}
}
use of org.opengis.referencing.crs.CoordinateReferenceSystem in project sis by apache.
the class Shapes2D method transform.
/**
* Transforms a rectangular envelope using the given coordinate operation.
* The transformation is only approximative: the returned envelope may be bigger
* than the smallest possible bounding box, but should not be smaller in most cases.
*
* <p>This method can handle the case where the rectangle contains the North or South pole,
* or when it cross the ±180° longitude.</p>
*
* @param operation the operation to use. Source and target dimension must be 2.
* @param envelope the rectangle to transform (may be {@code null}).
* @param destination the destination rectangle (may be {@code envelope}).
* If {@code null}, a new rectangle will be created and returned.
* @return {@code destination}, or a new rectangle if {@code destination} was non-null and {@code envelope} was null.
* @throws TransformException if a transform failed.
*
* @see #transform(MathTransform2D, Rectangle2D, Rectangle2D)
* @see Envelopes#transform(CoordinateOperation, Envelope)
*/
@SuppressWarnings("null")
public static Rectangle2D transform(final CoordinateOperation operation, final Rectangle2D envelope, Rectangle2D destination) throws TransformException {
ArgumentChecks.ensureNonNull("operation", operation);
if (envelope == null) {
return null;
}
final MathTransform transform = operation.getMathTransform();
if (!(transform instanceof MathTransform2D)) {
throw new MismatchedDimensionException(Errors.format(Errors.Keys.IllegalPropertyValueClass_3, "transform", MathTransform2D.class, MathTransform.class));
}
MathTransform2D mt = (MathTransform2D) transform;
final double[] center = new double[2];
destination = transform(mt, envelope, destination, center);
/*
* If the source envelope crosses the expected range of valid coordinates, also projects
* the range bounds as a safety. See the comments in transform(Envelope, ...).
*/
final CoordinateReferenceSystem sourceCRS = operation.getSourceCRS();
if (sourceCRS != null) {
final CoordinateSystem cs = sourceCRS.getCoordinateSystem();
if (cs != null && cs.getDimension() == 2) {
// Paranoiac check.
CoordinateSystemAxis axis = cs.getAxis(0);
double min = envelope.getMinX();
double max = envelope.getMaxX();
Point2D.Double pt = null;
for (int i = 0; i < 4; i++) {
if (i == 2) {
axis = cs.getAxis(1);
min = envelope.getMinY();
max = envelope.getMaxY();
}
final double v = (i & 1) == 0 ? axis.getMinimumValue() : axis.getMaximumValue();
if (!(v > min && v < max)) {
continue;
}
if (pt == null) {
pt = new Point2D.Double();
}
if ((i & 2) == 0) {
pt.x = v;
pt.y = envelope.getCenterY();
} else {
pt.x = envelope.getCenterX();
pt.y = v;
}
destination.add(mt.transform(pt, pt));
}
}
}
/*
* Now take the target CRS in account.
*/
final CoordinateReferenceSystem targetCRS = operation.getTargetCRS();
if (targetCRS == null) {
return destination;
}
final CoordinateSystem targetCS = targetCRS.getCoordinateSystem();
if (targetCS == null || targetCS.getDimension() != 2) {
// It should be an error, but we keep this method tolerant.
return destination;
}
/*
* Checks for singularity points. See the Envelopes.transform(CoordinateOperation, Envelope)
* method for comments about the algorithm. The code below is the same algorithm adapted for
* the 2D case and the related objects (Point2D, Rectangle2D, etc.).
*
* The 'border' variable in the loop below is used in order to compress 2 dimensions
* and 2 extremums in a single loop, in this order: (xmin, xmax, ymin, ymax).
*/
TransformException warning = null;
Point2D sourcePt = null;
Point2D targetPt = null;
// A bitmask for each (dimension, extremum) pairs.
int includedBoundsValue = 0;
for (int border = 0; border < 4; border++) {
// 2 dimensions and 2 extremums compacted in a flag.
// The dimension index being examined.
final int dimension = border >>> 1;
final CoordinateSystemAxis axis = targetCS.getAxis(dimension);
if (axis == null) {
// Should never be null, but check as a paranoiac safety.
continue;
}
final double extremum = (border & 1) == 0 ? axis.getMinimumValue() : axis.getMaximumValue();
if (Double.isInfinite(extremum) || Double.isNaN(extremum)) {
continue;
}
if (targetPt == null) {
try {
mt = mt.inverse();
} catch (NoninvertibleTransformException exception) {
Envelopes.recoverableException(Shapes2D.class, exception);
return destination;
}
targetPt = new Point2D.Double();
}
switch(dimension) {
case 0:
targetPt.setLocation(extremum, center[1]);
break;
case 1:
targetPt.setLocation(center[0], extremum);
break;
default:
throw new AssertionError(border);
}
try {
sourcePt = mt.transform(targetPt, sourcePt);
} catch (TransformException exception) {
if (warning == null) {
warning = exception;
} else {
warning.addSuppressed(exception);
}
continue;
}
if (envelope.contains(sourcePt)) {
destination.add(targetPt);
includedBoundsValue |= (1 << border);
}
}
/*
* Iterate over all dimensions of type "WRAPAROUND" for which minimal or maximal axis
* values have not yet been included in the envelope. We could inline this check inside
* the above loop, but we don't in order to have a chance to exclude the dimensions for
* which the point have already been added.
*
* See transform(CoordinateOperation, Envelope) for more comments about the algorithm.
*/
if (includedBoundsValue != 0) {
/*
* Bits mask transformation:
* 1) Swaps the two dimensions (YyXx → XxYy)
* 2) Insert a space between each bits (XxYy → X.x.Y.y.)
* 3) Fill the space with duplicated values (X.x.Y.y. → XXxxYYyy)
*
* In terms of bit positions 1,2,4,8 (not bit values), we have:
*
* 8421 → 22881144
* i.e. (ymax, ymin, xmax, xmin) → (xmax², ymax², xmin², ymin²)
*
* Now look at the last part: (xmin², ymin²). The next step is to perform a bitwise
* AND operation in order to have only both of the following conditions:
*
* Borders not yet added to the envelope: ~(ymax, ymin, xmax, xmin)
* Borders in which a singularity exists: (xmin, xmin, ymin, ymin)
*
* The same operation is repeated on the next 4 bits for (xmax, xmax, ymax, ymax).
*/
int toTest = ((includedBoundsValue & 1) << 3) | ((includedBoundsValue & 4) >>> 1) | ((includedBoundsValue & 2) << 6) | ((includedBoundsValue & 8) << 2);
// Duplicate the bit values.
toTest |= (toTest >>> 1);
toTest &= ~(includedBoundsValue | (includedBoundsValue << 4));
/*
* Forget any axes that are not of kind "WRAPAROUND". Then get the final
* bit pattern indicating which points to test. Iterate over that bits.
*/
if ((toTest & 0x33333333) != 0 && !CoordinateOperations.isWrapAround(targetCS.getAxis(0)))
toTest &= 0xCCCCCCCC;
if ((toTest & 0xCCCCCCCC) != 0 && !CoordinateOperations.isWrapAround(targetCS.getAxis(1)))
toTest &= 0x33333333;
while (toTest != 0) {
final int border = Integer.numberOfTrailingZeros(toTest);
final int bitMask = 1 << border;
// Clear now the bit, for the next iteration.
toTest &= ~bitMask;
final int dimensionToAdd = (border >>> 1) & 1;
final CoordinateSystemAxis toAdd = targetCS.getAxis(dimensionToAdd);
final CoordinateSystemAxis added = targetCS.getAxis(dimensionToAdd ^ 1);
double x = (border & 1) == 0 ? toAdd.getMinimumValue() : toAdd.getMaximumValue();
double y = (border & 4) == 0 ? added.getMinimumValue() : added.getMaximumValue();
if (dimensionToAdd != 0) {
final double t = x;
x = y;
y = t;
}
targetPt.setLocation(x, y);
try {
sourcePt = mt.transform(targetPt, sourcePt);
} catch (TransformException exception) {
if (warning == null) {
warning = exception;
} else {
warning.addSuppressed(exception);
}
continue;
}
if (envelope.contains(sourcePt)) {
destination.add(targetPt);
}
}
}
/*
* At this point we finished envelope transformation. Verify if some ordinates need to be "wrapped around"
* as a result of the coordinate operation. This is usually the longitude axis where the source CRS uses
* the [-180 … +180]° range and the target CRS uses the [0 … 360]° range, or the converse. In such case we
* set the rectangle to the full range (we do not use the mechanism documented in Envelope2D) because most
* Rectangle2D implementations do not support spanning the anti-meridian. This results in larger rectangle
* than what would be possible with GeneralEnvelope or Envelope2D, but we try to limit the situation where
* this expansion is applied.
*/
final Set<Integer> wrapAroundChanges;
if (operation instanceof AbstractCoordinateOperation) {
wrapAroundChanges = ((AbstractCoordinateOperation) operation).getWrapAroundChanges();
} else {
wrapAroundChanges = CoordinateOperations.wrapAroundChanges(sourceCRS, targetCS);
}
for (int dim : wrapAroundChanges) {
// Empty in the vast majority of cases.
final CoordinateSystemAxis axis = targetCS.getAxis(dim);
final double minimum = axis.getMinimumValue();
final double maximum = axis.getMaximumValue();
final double o1, o2;
if (dim == 0) {
o1 = destination.getMinX();
o2 = destination.getMaxX();
} else {
o1 = destination.getMinY();
o2 = destination.getMaxY();
}
if (o1 < minimum || o2 > maximum) {
final double span = maximum - minimum;
if (dim == 0) {
destination.setRect(minimum, destination.getY(), span, destination.getHeight());
} else {
destination.setRect(destination.getX(), minimum, destination.getWidth(), span);
}
}
}
if (warning != null) {
Envelopes.recoverableException(Shapes2D.class, warning);
}
return destination;
}
use of org.opengis.referencing.crs.CoordinateReferenceSystem in project sis by apache.
the class CoordinateOperationMethods method computeUnionOfAllDomainOfValidity.
/**
* For each {@link OperationMethod} (identified by their name), computes the union of the domain of validity
* of all CRS using that operation method. The result is a map where keys are {@link OperationMethod} names,
* and values are the union of the domain of validity of all CRS using that {@code OperationMethod}.
*
* <p>This is a costly operation.</p>
*
* @todo This method is not yet used. This is pending the implementation of {@code CRSAuthorityFactory} is SIS.
*
* @param factory the factory to use for getting CRS.
* @return the union of domain of validity of all map projections using a method of the given name.
* @throws FactoryException if an error occurred while fetching the list of CRS.
*/
public static Map<String, DefaultGeographicBoundingBox> computeUnionOfAllDomainOfValidity(final CRSAuthorityFactory factory) throws FactoryException {
final Map<String, DefaultGeographicBoundingBox> domainOfValidity = new HashMap<>();
for (final String code : factory.getAuthorityCodes(GeneralDerivedCRS.class)) {
final CoordinateReferenceSystem crs;
try {
crs = factory.createCoordinateReferenceSystem(code);
} catch (FactoryException e) {
// Ignore and inspect the next element.
continue;
}
if (crs instanceof GeneralDerivedCRS) {
final GeographicBoundingBox candidate = CRS.getGeographicBoundingBox(crs);
if (candidate != null) {
final String name = ((GeneralDerivedCRS) crs).getConversionFromBase().getMethod().getName().getCode();
DefaultGeographicBoundingBox validity = domainOfValidity.get(name);
if (validity == null) {
validity = new DefaultGeographicBoundingBox(candidate);
domainOfValidity.put(name, validity);
} else {
validity.add(candidate);
}
}
}
}
return domainOfValidity;
}
use of org.opengis.referencing.crs.CoordinateReferenceSystem in project sis by apache.
the class ConsistencyTest method testCoordinateReferenceSystems.
/**
* Verifies the WKT consistency of all CRS instances.
*
* @throws FactoryException if an error other than "unsupported operation method" occurred.
*/
@Test
public void testCoordinateReferenceSystems() throws FactoryException {
assumeTrue(RUN_EXTENSIVE_TESTS);
final WKTFormat v1 = new WKTFormat(null, null);
final WKTFormat v1c = new WKTFormat(null, null);
final WKTFormat v2 = new WKTFormat(null, null);
final WKTFormat v2s = new WKTFormat(null, null);
v1.setConvention(Convention.WKT1);
v1c.setConvention(Convention.WKT1_COMMON_UNITS);
v2.setConvention(Convention.WKT2);
v2s.setConvention(Convention.WKT2_SIMPLIFIED);
for (final String code : CRS.getAuthorityFactory(null).getAuthorityCodes(CoordinateReferenceSystem.class)) {
if (!EXCLUDES.contains(code)) {
final CoordinateReferenceSystem crs;
try {
crs = CRS.forCode(code);
} catch (UnavailableFactoryException | NoSuchIdentifierException | FactoryDataException e) {
print(code, "WARNING", e.getLocalizedMessage());
continue;
}
lookup(parseAndFormat(v2, code, crs), crs);
lookup(parseAndFormat(v2s, code, crs), crs);
/*
* There is more information lost in WKT 1 than in WKT 2, so we can not test everything.
* For example we can not format fully three-dimensional geographic CRS because the unit
* is not the same for all axes. We can not format neither some axis directions.
*/
try {
parseAndFormat(v1, code, crs);
} catch (UnformattableObjectException e) {
print(code, "WARNING", e.getLocalizedMessage());
continue;
}
parseAndFormat(v1c, code, crs);
}
}
}
Aggregations