Search in sources :

Example 1 with CommonCRS

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));
        }
    }
}
Also used : Field(java.lang.reflect.Field) MathTransform(org.opengis.referencing.operation.MathTransform) CommonCRS(org.apache.sis.referencing.CommonCRS) DirectPosition2D(org.apache.sis.geometry.DirectPosition2D) Test(org.junit.Test) DependsOnMethod(org.apache.sis.test.DependsOnMethod)

Example 2 with CommonCRS

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);
    }
}
Also used : CartesianCS(org.opengis.referencing.cs.CartesianCS) ProjectedCRS(org.opengis.referencing.crs.ProjectedCRS) Length(javax.measure.quantity.Length) GeodeticObjectBuilder(org.apache.sis.internal.referencing.GeodeticObjectBuilder) InternationalString(org.opengis.util.InternationalString) SimpleInternationalString(org.apache.sis.util.iso.SimpleInternationalString) CommonCRS(org.apache.sis.referencing.CommonCRS) GeographicCRS(org.opengis.referencing.crs.GeographicCRS)

Example 3 with CommonCRS

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();
}
Also used : NoSuchAuthorityCodeException(org.opengis.referencing.NoSuchAuthorityCodeException) InternationalString(org.opengis.util.InternationalString) SimpleInternationalString(org.apache.sis.util.iso.SimpleInternationalString) CommonCRS(org.apache.sis.referencing.CommonCRS)

Aggregations

CommonCRS (org.apache.sis.referencing.CommonCRS)3 SimpleInternationalString (org.apache.sis.util.iso.SimpleInternationalString)2 InternationalString (org.opengis.util.InternationalString)2 Field (java.lang.reflect.Field)1 Length (javax.measure.quantity.Length)1 DirectPosition2D (org.apache.sis.geometry.DirectPosition2D)1 GeodeticObjectBuilder (org.apache.sis.internal.referencing.GeodeticObjectBuilder)1 DependsOnMethod (org.apache.sis.test.DependsOnMethod)1 Test (org.junit.Test)1 NoSuchAuthorityCodeException (org.opengis.referencing.NoSuchAuthorityCodeException)1 GeographicCRS (org.opengis.referencing.crs.GeographicCRS)1 ProjectedCRS (org.opengis.referencing.crs.ProjectedCRS)1 CartesianCS (org.opengis.referencing.cs.CartesianCS)1 MathTransform (org.opengis.referencing.operation.MathTransform)1