use of org.apache.sis.referencing.CommonCRS in project sis by apache.
the class MilitaryGridReferenceSystemTest method verifyDecoderTables.
/**
* Verifies the hard-coded tables used for decoding purpose.
* This method performs its computation using UTM zone 31, which is the zone from 0° to 6°E.
* The results should be the same for all other zones.
*
* @throws TransformException if an error occurred while projecting a geographic coordinate.
* @throws ReflectiveOperationException if this test method can not access the table to verify.
*/
@Test
@DependsOnMethod("verifyInvariants")
public void verifyDecoderTables() throws TransformException, ReflectiveOperationException {
final int numBands = 20;
final double zoneCentre = 3;
final double zoneBorder = 0;
final CommonCRS datum = CommonCRS.WGS84;
final DirectPosition2D geographic = new DirectPosition2D();
final DirectPosition2D projected = new DirectPosition2D();
final MathTransform northMT = datum.universal(+1, zoneCentre).getConversionFromBase().getMathTransform();
final MathTransform southMT = datum.universal(-1, zoneCentre).getConversionFromBase().getMathTransform();
final int[] table;
{
// Use reflection for keeping MilitaryGridReferenceSystem.Decoder.ROW_RESOLVER private.
final Field f = MilitaryGridReferenceSystem.Decoder.class.getDeclaredField("ROW_RESOLVER");
f.setAccessible(true);
table = (int[]) f.get(null);
assertEquals("ROW_RESOLVER.length", numBands, table.length);
}
for (int band = 0; band < numBands; band++) {
final double φ = band * MilitaryGridReferenceSystem.LATITUDE_BAND_HEIGHT + TransverseMercator.Zoner.SOUTH_BOUNDS;
final boolean isSouth = (φ < 0);
final MathTransform projection = (isSouth) ? southMT : northMT;
/*
* Computes the smallest possible northing value. In the North hemisphere, this is the value
* on the central meridian because northing values tends toward the poles as we increase the
* distance from that centre. In the South hemisphere, this is the value on the zone border
* where we have the maximal distance from the centre.
*/
geographic.x = φ;
geographic.y = isSouth ? zoneBorder : zoneCentre;
final double ymin = projection.transform(geographic, projected).getOrdinate(1);
/*
* Computes the largest possible northing value. This is not only the value of the next latitude band;
* we also need to interchange the "zone centre" and "zone border" logic described in previous comment.
* The result is that we will have some overlap in the northing value of consecutive latitude bands.
*/
geographic.y = isSouth ? zoneCentre : zoneBorder;
geographic.x = MilitaryGridReferenceSystem.Decoder.upperBound(φ);
final double ymax = projection.transform(geographic, projected).getOrdinate(1);
/*
* Computes the value that we will encode in the MilitaryGridReferenceSystem.Decoder.ROW_RESOLVER table.
* The lowest 4 bits are the number of the row cycle (a cycle of 2000 km). The remaining bits tell which
* rows are valid in that latitude band.
*/
final int rowCycle = (int) StrictMath.floor(ymin / (MilitaryGridReferenceSystem.GRID_SQUARE_SIZE * MilitaryGridReferenceSystem.GRID_ROW_COUNT));
// Inclusive
final int lowerRow = (int) StrictMath.floor(ymin / MilitaryGridReferenceSystem.GRID_SQUARE_SIZE);
// Exclusive
final int upperRow = (int) StrictMath.ceil(ymax / MilitaryGridReferenceSystem.GRID_SQUARE_SIZE);
assertTrue("rowCycle", rowCycle >= 0 && rowCycle <= MilitaryGridReferenceSystem.Decoder.NORTHING_BITS_MASK);
assertTrue("lowerRow", lowerRow >= 0);
assertTrue("upperRow", upperRow >= 0);
int validRows = 0;
for (int i = lowerRow; i < upperRow; i++) {
validRows |= 1 << (i % MilitaryGridReferenceSystem.GRID_ROW_COUNT);
}
final int bits = (validRows << MilitaryGridReferenceSystem.Decoder.NORTHING_BITS_COUNT) | rowCycle;
/*
* Verification. If it fails, format the line of code that should be inserted
* in the MilitaryGridReferenceSystem.Decoder.ROW_RESOLVER table definition.
*/
if (table[band] != bits) {
String bitMasks = Integer.toBinaryString(validRows);
bitMasks = "00000000000000000000".substring(bitMasks.length()).concat(bitMasks);
fail(String.format("ROW_RESOLVER[%d]: expected %d but got %d. Below is suggested line of code:%n" + "/* Latitude band %c (from %3.0f°) */ %d | 0b%s_0000%n", band, bits, table[band], MilitaryGridReferenceSystem.Encoder.latitudeBand(φ), φ, rowCycle, bitMasks));
}
}
}
use of org.apache.sis.referencing.CommonCRS 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.apache.sis.referencing.CommonCRS in project sis by apache.
the class CommonAuthorityFactory method createCoordinateReferenceSystem.
/**
* Creates a coordinate reference system from the specified code.
* This method performs the following steps:
*
* <ol>
* <li>Skip the {@code "OGC"}, {@code "CRS"}, {@code "AUTO"}, {@code "AUTO1"} or {@code "AUTO2"} namespace
* if present (ignoring case). All other namespaces will cause an exception to be thrown.</li>
* <li>Skip the {@code "CRS"} prefix if present. This additional check is for accepting codes like
* {@code "OGC:CRS84"} (not a valid CRS code, but seen in practice).</li>
* <li>In the remaining text, interpret the integer value as documented in this class javadoc.
* Note that some codes require coma-separated parameters after the integer value.</li>
* </ol>
*
* @param code value allocated by OGC.
* @return the coordinate reference system for the given code.
* @throws FactoryException if the object creation failed.
*/
@Override
public CoordinateReferenceSystem createCoordinateReferenceSystem(final String code) throws FactoryException {
ArgumentChecks.ensureNonNull("code", code);
final String localCode;
final boolean isLegacy;
String complement = null;
{
// Block for keeping 'start' and 'end' variables locale.
int start = skipNamespace(code);
isLegacy = (start & LEGACY_MASK) != 0;
start &= ~LEGACY_MASK;
final int startOfParameters = code.indexOf(SEPARATOR, start);
int end = CharSequences.skipTrailingWhitespaces(code, start, code.length());
if (startOfParameters >= 0) {
complement = code.substring(startOfParameters + 1);
end = CharSequences.skipTrailingWhitespaces(code, start, startOfParameters);
}
localCode = code.substring(start, end);
}
int codeValue = 0;
double[] parameters = ArraysExt.EMPTY_DOUBLE;
try {
codeValue = Integer.parseInt(localCode);
if (complement != null) {
parameters = CharSequences.parseDoubles(complement, SEPARATOR);
}
} catch (NumberFormatException exception) {
throw noSuchAuthorityCode(localCode, code, exception);
}
/*
* At this point we have isolated the code value from the parameters (if any). Verify the number of arguments.
* Then codes in the AUTO(2) namespace are delegated to a separated method while codes in the CRS namespaces
* are handled below.
*/
final int count = parameters.length;
if (codeValue >= FIRST_PROJECTION_CODE) {
int expected;
short errorKey = 0;
if (count < (expected = 2)) {
errorKey = Errors.Keys.TooFewArguments_2;
} else if (count > (expected = 3)) {
errorKey = Errors.Keys.TooManyArguments_2;
}
if (errorKey == 0) {
return createAuto(code, codeValue, isLegacy, (count > 2) ? parameters[0] : isLegacy ? Constants.EPSG_METRE : 1, parameters[count - 2], parameters[count - 1]);
}
throw new NoSuchAuthorityCodeException(Errors.format(errorKey, expected, count), AUTO2, localCode, code);
}
if (count != 0) {
throw new NoSuchAuthorityCodeException(Errors.format(Errors.Keys.UnexpectedCharactersAfter_2, localCode, complement), Constants.CRS, localCode, code);
}
final CommonCRS crs;
switch(codeValue) {
case Constants.CRS1:
return displayCRS();
case Constants.CRS84:
crs = CommonCRS.WGS84;
break;
case Constants.CRS83:
crs = CommonCRS.NAD83;
break;
case Constants.CRS27:
crs = CommonCRS.NAD27;
break;
case Constants.CRS88:
return CommonCRS.Vertical.NAVD88.crs();
default:
throw noSuchAuthorityCode(localCode, code, null);
}
return crs.normalizedGeographic();
}
Aggregations