use of org.opengis.referencing.crs.ProjectedCRS in project sis by apache.
the class TransformTestCase method testTransformOverPole.
/**
* Tests conversions of an envelope or rectangle over a pole using a coordinate operation.
*
* @throws FactoryException if an error occurred while creating the operation.
* @throws TransformException if an error occurred while transforming the envelope.
*/
@Test
@DependsOnMethod("testTransform")
public final void testTransformOverPole() throws FactoryException, TransformException {
final ProjectedCRS sourceCRS = (ProjectedCRS) CRS.fromWKT("PROJCS[“WGS 84 / Antarctic Polar Stereographic”,\n" + " GEOGCS[“WGS 84”,\n" + " DATUM[“World Geodetic System 1984”,\n" + " SPHEROID[“WGS 84”, 6378137.0, 298.257223563]],\n" + " PRIMEM[“Greenwich”, 0.0],\n" + " UNIT[“degree”, 0.017453292519943295]],\n" + " PROJECTION[“Polar Stereographic (variant B)”],\n" + " PARAMETER[“standard_parallel_1”, -71.0],\n" + " UNIT[“m”, 1.0]]");
final GeographicCRS targetCRS = sourceCRS.getBaseCRS();
final Conversion conversion = inverse(sourceCRS.getConversionFromBase());
final MathTransform2D transform = (MathTransform2D) conversion.getMathTransform();
/*
* The rectangle to test, which contains the South pole.
*/
G rectangle = createFromExtremums(sourceCRS, -3943612.4042124213, -4078471.954436003, 3729092.5890516187, 4033483.085688618);
/*
* This is what we get without special handling of singularity point.
* Note that is does not include the South pole as we would expect.
* The commented out values are what we get by projecting an arbitrary
* larger amount of points.
*/
G expected = createFromExtremums(targetCRS, // anti-regression values
-179.8650137390031, // anti-regression values
-88.99136583196396, // 178.8122742080059 -40.90577500420587] // empirical values
137.9769431693009, // anti-regression values
-40.90577500420587);
/*
* Tests what we actually get. First, test using the method working on MathTransform.
* Next, test again the same transform, but using the API on Envelope objects.
*/
G actual = transform(targetCRS, transform, rectangle);
assertGeometryEquals(expected, actual, ANGULAR_TOLERANCE, ANGULAR_TOLERANCE);
/*
* Using the transform(CoordinateOperation, …) method,
* the singularity at South pole is taken in account.
*/
expected = createFromExtremums(targetCRS, -180, -90, 180, -40.905775004205864);
actual = transform(conversion, rectangle);
assertGeometryEquals(expected, actual, ANGULAR_TOLERANCE, ANGULAR_TOLERANCE);
/*
* Another rectangle containing the South pole, but this time the south
* pole is almost in a corner of the rectangle
*/
rectangle = createFromExtremums(sourceCRS, -4000000, -4000000, 300000, 30000);
expected = createFromExtremums(targetCRS, -180, -90, 180, -41.03163170198091);
actual = transform(conversion, rectangle);
assertGeometryEquals(expected, actual, ANGULAR_TOLERANCE, ANGULAR_TOLERANCE);
/*
* Another rectangle with the South pole close to the border.
* This test should execute the step #3 in the transform method code.
*/
rectangle = createFromExtremums(sourceCRS, -2000000, -1000000, 200000, 2000000);
expected = createFromExtremums(targetCRS, -180, -90, 180, -64.3861643256928);
actual = transform(conversion, rectangle);
assertGeometryEquals(expected, actual, ANGULAR_TOLERANCE, ANGULAR_TOLERANCE);
}
use of org.opengis.referencing.crs.ProjectedCRS in project sis by apache.
the class TransformTestCase method testTransformNotOverPole.
/**
* Tests conversions of an envelope or rectangle which is <strong>not</strong> over a pole,
* but was wrongly considered as over a pole before SIS-329 fix.
*
* @throws FactoryException if an error occurred while creating the operation.
* @throws TransformException if an error occurred while transforming the envelope.
*
* @see <a href="https://issues.apache.org/jira/browse/SIS-329">SIS-329</a>
*/
@Test
@DependsOnMethod("testTransform")
public final void testTransformNotOverPole() throws FactoryException, TransformException {
final ProjectedCRS sourceCRS = CommonCRS.WGS84.universal(10, -3.5);
final GeographicCRS targetCRS = sourceCRS.getBaseCRS();
final Conversion conversion = inverse(sourceCRS.getConversionFromBase());
final G rectangle = createFromExtremums(sourceCRS, 199980, 4490220, 309780, 4600020);
final G expected = createFromExtremums(targetCRS, // Computed by SIS (not validated by external authority).
40.50846282536367, // Computed by SIS (not validated by external authority).
-6.594124551832373, 41.52923550023067, -5.246186118392847);
final G actual = transform(conversion, rectangle);
assertGeometryEquals(expected, actual, ANGULAR_TOLERANCE, ANGULAR_TOLERANCE);
}
use of org.opengis.referencing.crs.ProjectedCRS in project sis by apache.
the class CommonAuthorityFactory method createAuto.
/**
* Creates a projected CRS from parameters in the {@code AUTO(2)} namespace.
*
* @param code the user-specified code, used only for error reporting.
* @param projection the projection code (e.g. 42001).
* @param isLegacy {@code true} if the code was found in {@code "AUTO"} or {@code "AUTO1"} namespace.
* @param factor the multiplication factor for the unit of measurement.
* @param longitude a longitude in the desired projection zone.
* @param latitude a latitude in the desired projection zone.
* @return the projected CRS for the given projection and parameters.
*/
@SuppressWarnings("null")
private ProjectedCRS createAuto(final String code, final int projection, final boolean isLegacy, final double factor, final double longitude, final double latitude) throws FactoryException {
Boolean isUTM = null;
String method = null;
String param = null;
switch(projection) {
/*
* 42001: Universal Transverse Mercator — central meridian must be in the center of a UTM zone.
* 42002: Transverse Mercator — like 42001 except that central meridian can be anywhere.
* 42003: WGS 84 / Auto Orthographic — defined by "Central_Meridian" and "Latitude_of_Origin".
* 42004: WGS 84 / Auto Equirectangular — defined by "Central_Meridian" and "Standard_Parallel_1".
* 42005: WGS 84 / Auto Mollweide — defined by "Central_Meridian" only.
*/
case 42001:
isUTM = true;
break;
case 42002:
isUTM = (latitude == 0) && (Zoner.UTM.centralMeridian(Zoner.UTM.zone(0, longitude)) == longitude);
break;
case 42003:
method = "Orthographic";
param = Constants.LATITUDE_OF_ORIGIN;
break;
case 42004:
method = "Equirectangular";
param = Constants.STANDARD_PARALLEL_1;
break;
case 42005:
method = "Mollweide";
break;
default:
throw noSuchAuthorityCode(String.valueOf(projection), code, null);
}
/*
* For the (Universal) Transverse Mercator case (AUTO:42001 and 42002), we delegate to the CommonCRS
* enumeration if possible because CommonCRS will itself delegate to the EPSG factory if possible.
* The Math.signum(latitude) instruction is for preventing "AUTO:42001" to handle the UTM special cases
* (Norway and Svalbard) or to switch on the Universal Polar Stereographic projection for high latitudes,
* because the WMS specification does not said that we should.
*/
final CommonCRS datum = CommonCRS.WGS84;
// To be set, directly or indirectly, to WGS84.geographic().
final GeographicCRS baseCRS;
// Temporary UTM projection, for extracting other properties.
final ProjectedCRS crs;
// Coordinate system with (E,N) axes in metres.
CartesianCS cs;
try {
if (isUTM != null && isUTM) {
crs = datum.universal(Math.signum(latitude), longitude);
if (factor == (isLegacy ? Constants.EPSG_METRE : 1)) {
return crs;
}
baseCRS = crs.getBaseCRS();
cs = crs.getCoordinateSystem();
} else {
cs = projectedCS;
if (cs == null) {
crs = datum.universal(Math.signum(latitude), longitude);
projectedCS = cs = crs.getCoordinateSystem();
baseCRS = crs.getBaseCRS();
} else {
crs = null;
baseCRS = datum.geographic();
}
}
/*
* At this point we got a coordinate system with axes in metres.
* If the user asked for another unit of measurement, change the axes now.
*/
Unit<Length> unit;
if (isLegacy) {
unit = createUnitFromEPSG(factor).asType(Length.class);
} else {
unit = Units.METRE;
if (factor != 1)
unit = unit.multiply(factor);
}
if (!Units.METRE.equals(unit)) {
cs = (CartesianCS) CoordinateSystems.replaceLinearUnit(cs, unit);
}
/*
* Set the projection name, operation method and parameters. The parameters for the Transverse Mercator
* projection are a little bit more tedious to set, so we use a convenience method for that.
*/
final GeodeticObjectBuilder builder = new GeodeticObjectBuilder();
if (isUTM != null) {
if (isUTM && crs != null) {
builder.addName(crs.getName());
}
// else default to the conversion name, which is "Transverse Mercator".
builder.setTransverseMercator(isUTM ? Zoner.UTM : Zoner.ANY, latitude, longitude);
} else {
builder.setConversionMethod(method).addName(PROJECTION_NAMES[projection - FIRST_PROJECTION_CODE]).setParameter(Constants.CENTRAL_MERIDIAN, longitude, Units.DEGREE);
if (param != null) {
builder.setParameter(param, latitude, Units.DEGREE);
}
}
return builder.createProjectedCRS(baseCRS, cs);
} catch (IllegalArgumentException e) {
throw noSuchAuthorityCode(String.valueOf(projection), code, e);
}
}
use of org.opengis.referencing.crs.ProjectedCRS in project sis by apache.
the class DefaultCoordinateOperationFactory method createSingleOperation.
/**
* Creates a transformation or conversion from the given properties.
* This method infers by itself if the operation to create is a
* {@link Transformation}, a {@link Conversion} or a {@link Projection} sub-type
* ({@link CylindricalProjection}, {@link ConicProjection} or {@link PlanarProjection})
* using the {@linkplain DefaultOperationMethod#getOperationType() information provided by the given method}.
*
* <p>The properties given in argument follow the same rules than for the
* {@linkplain AbstractCoordinateOperation#AbstractCoordinateOperation(Map, CoordinateReferenceSystem,
* CoordinateReferenceSystem, CoordinateReferenceSystem, MathTransform) coordinate operation} constructor.
* The following table is a reminder of main (not all) properties:</p>
*
* <table class="sis">
* <caption>Recognized properties (non exhaustive list)</caption>
* <tr>
* <th>Property name</th>
* <th>Value type</th>
* <th>Returned by</th>
* </tr>
* <tr>
* <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
* <td>{@link org.opengis.metadata.Identifier} or {@link String}</td>
* <td>{@link DefaultConversion#getName()}</td>
* </tr>
* <tr>
* <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
* <td>{@link org.opengis.metadata.Identifier} (optionally as array)</td>
* <td>{@link DefaultConversion#getIdentifiers()}</td>
* </tr>
* <tr>
* <td>{@value org.opengis.referencing.operation.CoordinateOperation#DOMAIN_OF_VALIDITY_KEY}</td>
* <td>{@link org.opengis.metadata.extent.Extent}</td>
* <td>{@link DefaultConversion#getDomainOfValidity()}</td>
* </tr>
* </table>
*
* @param properties the properties to be given to the identified object.
* @param sourceCRS the source CRS.
* @param targetCRS the target CRS.
* @param interpolationCRS the CRS of additional coordinates needed for the operation, or {@code null} if none.
* @param method the coordinate operation method (mandatory in all cases).
* @param transform transform from positions in the source CRS to positions in the target CRS.
* @return the coordinate operation created from the given arguments.
* @throws FactoryException if the object creation failed.
*
* @see DefaultOperationMethod#getOperationType()
* @see DefaultTransformation
* @see DefaultConversion
*/
public SingleOperation createSingleOperation(final Map<String, ?> properties, final CoordinateReferenceSystem sourceCRS, final CoordinateReferenceSystem targetCRS, final CoordinateReferenceSystem interpolationCRS, final OperationMethod method, MathTransform transform) throws FactoryException {
ArgumentChecks.ensureNonNull("sourceCRS", sourceCRS);
ArgumentChecks.ensureNonNull("targetCRS", targetCRS);
ArgumentChecks.ensureNonNull("method", method);
/*
* Undocumented (for now) feature: if the 'transform' argument is null but parameters are
* found in the given properties, create the MathTransform instance from those parameters.
* This is needed for WKT parsing of CoordinateOperation[…] among others.
*/
if (transform == null) {
final ParameterValueGroup parameters = Containers.property(properties, ReferencingServices.PARAMETERS_KEY, ParameterValueGroup.class);
if (parameters == null) {
throw new NullArgumentException(Errors.format(Errors.Keys.NullArgument_1, "transform"));
}
transform = getMathTransformFactory().createBaseToDerived(sourceCRS, parameters, targetCRS.getCoordinateSystem());
}
/*
* The "operationType" property is currently undocumented. The intent is to help this factory method in
* situations where the given operation method is not an Apache SIS implementation or does not override
* getOperationType(), or the method is ambiguous (e.g. "Affine" can be used for both a transformation
* or a conversion).
*
* If we have both a 'baseType' and a Method.getOperationType(), take the most specific type.
* An exception will be thrown if the two types are incompatible.
*/
Class<?> baseType = Containers.property(properties, ReferencingServices.OPERATION_TYPE_KEY, Class.class);
if (baseType == null) {
baseType = SingleOperation.class;
}
if (method instanceof DefaultOperationMethod) {
final Class<? extends SingleOperation> c = ((DefaultOperationMethod) method).getOperationType();
if (c != null) {
// Paranoiac check (above method should not return null).
if (baseType.isAssignableFrom(c)) {
baseType = c;
} else if (!c.isAssignableFrom(baseType)) {
throw new IllegalArgumentException(Errors.format(Errors.Keys.IncompatiblePropertyValue_1, ReferencingServices.OPERATION_TYPE_KEY));
}
}
}
/*
* If the base type is still abstract (probably because it was not specified neither in the given OperationMethod
* or in the properties), then try to find a concrete type using the following rules derived from the definitions
* given in ISO 19111:
*
* - If the two CRS uses the same datum (ignoring metadata), assume that we have a Conversion.
* - Otherwise we have a datum change, which implies that we have a Transformation.
*
* In the case of Conversion, we can specialize one step more if the conversion is going from a geographic CRS
* to a projected CRS. It may seems that we should check if ProjectedCRS.getBaseCRS() is equals (ignoring meta
* data) to source CRS. But we already checked the datum, which is the important part. The axis order and unit
* could be different, which we want to allow.
*/
if (baseType == SingleOperation.class) {
if (isConversion(sourceCRS, targetCRS)) {
if (interpolationCRS == null && sourceCRS instanceof GeographicCRS && targetCRS instanceof ProjectedCRS) {
baseType = Projection.class;
} else {
baseType = Conversion.class;
}
} else {
baseType = Transformation.class;
}
}
/*
* Now create the coordinate operation of the requested type. If we can not find a concrete class for the
* requested type, we will instantiate a SingleOperation in last resort. The later action is a departure
* from ISO 19111 since 'SingleOperation' is conceptually abstract. But we do that as a way to said that
* we are missing this important piece of information but still go ahead.
*
* It is inconvenient to guarantee that the created operation is an instance of 'baseType' since the user
* could have specified an implementation class or a custom sub-interface. We will perform the type check
* only after object creation.
*/
final AbstractSingleOperation op;
if (Transformation.class.isAssignableFrom(baseType)) {
op = new DefaultTransformation(properties, sourceCRS, targetCRS, interpolationCRS, method, transform);
} else if (Projection.class.isAssignableFrom(baseType)) {
ArgumentChecks.ensureCanCast("sourceCRS", GeographicCRS.class, sourceCRS);
ArgumentChecks.ensureCanCast("targetCRS", ProjectedCRS.class, targetCRS);
if (interpolationCRS != null) {
throw new IllegalArgumentException(Errors.format(Errors.Keys.ForbiddenAttribute_2, "interpolationCRS", baseType));
}
final GeographicCRS baseCRS = (GeographicCRS) sourceCRS;
final ProjectedCRS crs = (ProjectedCRS) targetCRS;
if (CylindricalProjection.class.isAssignableFrom(baseType)) {
op = new DefaultCylindricalProjection(properties, baseCRS, crs, method, transform);
} else if (ConicProjection.class.isAssignableFrom(baseType)) {
op = new DefaultConicProjection(properties, baseCRS, crs, method, transform);
} else if (PlanarProjection.class.isAssignableFrom(baseType)) {
op = new DefaultPlanarProjection(properties, baseCRS, crs, method, transform);
} else {
op = new DefaultProjection(properties, baseCRS, crs, method, transform);
}
} else if (Conversion.class.isAssignableFrom(baseType)) {
op = new DefaultConversion(properties, sourceCRS, targetCRS, interpolationCRS, method, transform);
} else {
// See above comment about this last-resort fallback.
op = new AbstractSingleOperation(properties, sourceCRS, targetCRS, interpolationCRS, method, transform);
}
if (!baseType.isInstance(op)) {
throw new FactoryException(Resources.format(Resources.Keys.CanNotCreateObjectAsInstanceOf_2, baseType, op.getName()));
}
return pool.unique(op);
}
use of org.opengis.referencing.crs.ProjectedCRS in project sis by apache.
the class MilitaryGridReferenceSystemTest method verifyConsistency.
/**
* Encodes random coordinates, decodes them and verifies that the results are approximatively equal
* to the original coordinates.
*
* @throws TransformException if an error occurred while computing the coordinate.
*/
@Test
@DependsOnMethod({ "testEncodeUTM", "testDecodeUTM", "testEncodeUPS", "testDecodeUPS" })
public void verifyConsistency() throws TransformException {
final Random random = TestUtilities.createRandomNumberGenerator();
final MilitaryGridReferenceSystem.Coder coder = coder();
final DirectPosition2D expected = new DirectPosition2D();
final DirectPosition2D position = new DirectPosition2D(CommonCRS.WGS84.geographic());
for (int i = 0; i < 100; i++) {
// Latitude (despite the 'x' field name)
position.x = random.nextDouble() * 180 - 90;
// Longitude (despite the 'y' field name)
position.y = random.nextDouble() * 358 - 179;
final String reference = coder.encode(position);
final DirectPosition r = decode(coder, reference);
final ProjectedCRS crs = (ProjectedCRS) r.getCoordinateReferenceSystem();
assertSame(expected, crs.getConversionFromBase().getMathTransform().transform(position, expected));
final double distance = expected.distance(r.getOrdinate(0), r.getOrdinate(1));
if (!(distance < 1.5)) {
// Use '!' for catching NaN.
final String lineSeparator = System.lineSeparator();
fail("Consistency check failed for φ = " + position.x + " and λ = " + position.y + lineSeparator + "MGRS reference = " + reference + lineSeparator + "Parsing result = " + r + lineSeparator + "Projected φ, λ = " + expected + lineSeparator + "Distance (m) = " + distance + lineSeparator);
}
}
}
Aggregations