All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.geotoolkit.referencing.operation.projection.ObliqueMercator Maven / Gradle / Ivy

/*
 *    Geotoolkit.org - An Open Source Java GIS Toolkit
 *    http://www.geotoolkit.org
 *
 *    (C) 2005-2012, Open Source Geospatial Foundation (OSGeo)
 *    (C) 2009-2012, Geomatys
 *
 *    This library is free software; you can redistribute it and/or
 *    modify it under the terms of the GNU Lesser General Public
 *    License as published by the Free Software Foundation;
 *    version 2.1 of the License.
 *
 *    This library is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *    Lesser General Public License for more details.
 *
 *    This package contains formulas from the PROJ package of USGS.
 *    USGS's work is fully acknowledged here. This derived work has
 *    been relicensed under LGPL with Frank Warmerdam's permission.
 */
package org.geotoolkit.referencing.operation.projection;

import java.util.Collection;
import java.awt.geom.AffineTransform;
import net.jcip.annotations.Immutable;

import org.opengis.referencing.operation.Matrix;
import org.opengis.parameter.ParameterValueGroup;
import org.opengis.parameter.ParameterDescriptor;
import org.opengis.parameter.ParameterDescriptorGroup;
import org.opengis.parameter.GeneralParameterDescriptor;
import org.opengis.parameter.ParameterNotFoundException;
import org.opengis.parameter.InvalidParameterNameException;
import org.opengis.parameter.InvalidParameterValueException;
import org.opengis.referencing.operation.MathTransform2D;

import org.geotoolkit.measure.Angle;
import org.geotoolkit.measure.Latitude;
import org.geotoolkit.resources.Errors;
import org.geotoolkit.util.ComparisonMode;
import org.geotoolkit.referencing.operation.matrix.Matrix2;
import org.geotoolkit.referencing.operation.provider.HotineObliqueMercator;

import static java.lang.Math.*;
import static java.lang.Double.*;
import static org.geotoolkit.math.XMath.atanh;
import static org.geotoolkit.internal.InternalUtilities.epsilonEqual;
import static org.geotoolkit.referencing.operation.provider.UniversalParameters.*;
import static org.geotoolkit.referencing.operation.provider.ObliqueMercator.AZIMUTH;
import static org.geotoolkit.referencing.operation.provider.ObliqueMercator.LATITUDE_OF_CENTRE;
import static org.geotoolkit.referencing.operation.provider.ObliqueMercator.LONGITUDE_OF_CENTRE;


/**
 * Oblique Mercator projection (EPSG codes 9812, 9815). See the
 * Mercator projection on MathWorld
 * for an overview. See any of the following providers for a list of programmatic parameters:
 * 

*

    *
  • {@link org.geotoolkit.referencing.operation.provider.ObliqueMercator}
  • *
  • {@link org.geotoolkit.referencing.operation.provider.ObliqueMercator.TwoPoint}
  • *
  • {@link org.geotoolkit.referencing.operation.provider.HotineObliqueMercator}
  • *
  • {@link org.geotoolkit.referencing.operation.provider.HotineObliqueMercator.TwoPoint}
  • *
* * {@section Description} * * The Oblique Mercator projection is a conformal, oblique, cylindrical projection with * the cylinder touching the ellipsoid (or sphere) along a great circle path (the central line). * The {@linkplain Mercator} and {@linkplain TransverseMercator Transverse Mercator} projections * can be thought of as special cases of the oblique Mercator, where the central line is along the * equator or a meridian, respectively. The Oblique Mercator projection has been used in * Switzerland, Hungary, Madagascar, Malaysia, Borneo and the panhandle of Alaska. *

* The Oblique Mercator projection uses a (U,V) coordinate system, with the * U axis along the central line. During the forward projection, coordinates from the * ellipsoid are projected conformally to a sphere of constant total curvature, called the * "aposphere", before being projected onto the plane. The projection coordinates are further * converted to a (X,Y) coordinate system by rotating the calculated * (u,v) coordinates to give output (x,y) coordinates. * The rotation value is usually the same as the projection azimuth (the angle, east of north, of * the central line), but some cases allow a separate rotation parameter. *

* There are two forms of the oblique Mercator, differing in the origin of their grid coordinates. * The Hotine Oblique Mercator (EPSG code 9812) has grid coordinates start at the * intersection of the central line and the equator of the aposphere. The Oblique Mercator * (EPSG code 9815) is the same, except the grid coordinates begin at the central point (where the * latitude of center and central line intersect). ESRI separates these two case by appending * {@code "Natural_Origin"} (for the {@code "Hotine_Oblique_Mercator"}) and {@code "Center"} * (for the {@code "Oblique_Mercator"}) to the projection names. *

* Two different methods are used to specify the central line for the oblique Mercator: * 1) a central point and an azimuth, east of north, describing the central line and * 2) two points on the central line. The EPSG does not use the two point method, while ESRI * separates the two cases by putting {@code "Azimuth"} and {@code "Two_Point"} in their projection * names. Both cases use the point where the {@code "latitude_of_center"} parameter crosses the * central line as the projection's central point. The {@code "central_meridian"} is not a * projection parameter, and is instead calculated as the intersection between the central * line and the equator of the aposphere. *

* For the azimuth method, the central latitude cannot be ±90 degrees and the central line * cannot be at a maximum or minimum latitude at the central point. In the two point method, the * latitude of the first and second points cannot be equal. Also, the latitude of the first point * and central point cannot be ±90 degrees. Furthermore, the latitude of the first point * cannot be 0 and the latitude of the second point cannot be -90 degrees. A change of 10-7 * radians can allow calculation at these special cases. Snyder's restriction of the central latitude * being 0 has been removed, since the equations appear to work correctly in this case. *

* Azimuth values of 0 and ±90 degrees are allowed (and used in Hungary and Switzerland), * though these cases would usually use a Mercator or Transverse Mercator projection instead. * Azimuth values > 90 degrees cause errors in the equations. *

* The oblique Mercator is also called the "Rectified Skew Orthomorphic" (RSO). It * appears that the only difference from the oblique Mercator is that the RSO allows the rotation * from the (U,V) to (X,Y) coordinate system to be * different from the azimuth. This separate parameter is called {@code "rectified_grid_angle"} * (or {@code "XY_Plane_Rotation"} by ESRI) and is also included in the EPSG's parameters for the * Oblique Mercator and Hotine Oblique Mercator. The rotation parameter is optional in all the * non-two point projections and will be set to the azimuth if not specified. *

* Projection cases and aliases implemented by the {@link ObliqueMercator} are: *

* * * * * * * * * * * * * * * * *
NameAuthorityRemarks
{@code Oblique_Mercator}EPSG:9815grid coordinates begin at the central point, * has {@code "rectified_grid_angle"} parameter.
{@code Hotine_Oblique_Mercator_Azimuth_Center}ESRIgrid coordinates begin at the central point.
{@code Rectified_Skew_Orthomorphic_Center}ESRIgrid coordinates begin at the central point, * has {@code "rectified_grid_angle"} parameter.
{@code Hotine_Oblique_Mercator}EPSG:9812grid coordinates begin at the intersection of the central line and aposphere equator, * has {@code "rectified_grid_angle"} parameter.
{@code Hotine_Oblique_Mercator_Azimuth_Natural_Origin}ESRIgrid coordinates begin at the intersection of the central line and aposphere equator. *
{@code Rectified_Skew_Orthomorphic_Natural_Origin}ESRIgrid coordinates begin at the intersection of the central line and aposphere equator, * has {@code "rectified_grid_angle"} parameter.
{@code Hotine_Oblique_Mercator_Two_Point_Center}ESRIgrid coordinates begin at the central point.
{@code Hotine_Oblique_Mercator_Two_Point_Natural_Origin}ESRIgrid coordinates begin at the intersection of the central line and aposphere equator.
* * {@section References} *
    *
  • Proj-4 available at www.remotesensing.org/proj
    * Relevant files are: {@code PJ_omerc.c}, {@code pj_tsfn.c}, * {@code pj_fwd.c}, {@code pj_inv.c} and {@code lib_proj.h}
  • *
  • John P. Snyder (Map Projections - A Working Manual,
    * U.S. Geological Survey Professional Paper 1395, 1987)
  • *
  • "Coordinate Conversions and Transformations including Formulas",
    * EPSG Guidance Note Number 7 part 2, Version 24.
  • *
  • Gerald Evenden, 2004, * Documentation of revised Oblique Mercator
  • *
* * @author Gerald Evenden (USGS) * @author Rueben Schulz (UBC) * @author Martin Desruisseaux (Geomatys) * @author Rémi Maréchal (Geomatys) * @version 3.20 * * @see Mercator * @see TransverseMercator * * @since 2.1 * @module */ @Immutable public class ObliqueMercator extends UnitaryProjection { /** * For compatibility with different versions during deserialization. */ private static final long serialVersionUID = 5382294977124711214L; /** * Maximum difference allowed when comparing real numbers. */ private static final double EPSILON = 1E-6, FINER_EPSILON = 1E-10; /** * Parameters used in Oblique Mercator projections. Those parameters determine the rotation * in addition to the scale and translation determined by the parameters declared in the * super-class. * * @author Rueben Schulz (UBC) * @author Martin Desruisseaux (Geomatys) * @version 3.00 * * @since 3.00 * @module */ protected static class Parameters extends UnitaryProjection.Parameters { /** * For cross-version compatibility. */ private static final long serialVersionUID = -5356116159749775517L; /** * Latitude of the projection centre. This is similar to the {@link #latitudeOfOrigin * latitudeOfOrigin}, but the latitude of origin is the Earth equator on aposphere for * the oblique Mercator. */ public double latitudeOfCentre; /** * Longitude of the projection centre. This is NOT equal to the * {@link #centralMeridian centralMeridian}, which is the meridian where the central * line intersects the Earth equator on aposphere. *

* This parameter applies to the "azimuth" case only and shall be set to * {@linkplain Double#NaN NaN} for the "two points" case. */ public double longitudeOfCentre; /** * The rectified bearing of the central line, in degrees. This is set to {@linkplain Double#NaN NaN} * if the {@link org.geotoolkit.referencing.operation.provider.ObliqueMercator#RECTIFIED_GRID_ANGLE * RECTIFIED_GRID_ANGLE} parameter value is not provided. */ public double rectifiedGridAngle; /** * The latitude of the 1st point used to specify the central line, in degrees. * This parameter applies to the "two points" case only and shell be set to * {@linkplain Double#NaN NaN} for the "azimuth" case. */ public double latitudeOf1stPoint; /** * The longitude of the 1st point used to specify the central line, in degrees. * This parameter applies to the "two points" case only and shall be set to * {@linkplain Double#NaN NaN} for the "azimuth" case. */ public double longitudeOf1stPoint; /** * The latitude of the 2nd point used to specify the central line, in degrees. * This parameter applies to the "two points" case only and shall be set to * {@linkplain Double#NaN NaN} for the "azimuth" case. */ public double latitudeOf2ndPoint; /** * The longitude of the 2nd point used to specify the central line, in radians. * This parameter applies to the "two points" case only and shall be set to * {@linkplain Double#NaN NaN} for the "azimuth" case. */ public double longitudeOf2ndPoint; /** * Creates parameters initialized to values extracted from the given parameter group. * * @param descriptor The descriptor of parameters that are legal for the projection being * constructed. * @param values The parameter values in standard units. * @throws ParameterNotFoundException if a mandatory parameter is missing. */ public Parameters(final ParameterDescriptorGroup descriptor, final ParameterValueGroup values) throws ParameterNotFoundException { super(descriptor, values); final Collection expected = descriptor.descriptors(); latitudeOfCentre = latitudeOfOrigin; longitudeOfCentre = centralMeridian; rectifiedGridAngle = doubleValue(expected, RECTIFIED_GRID_ANGLE, values); latitudeOf1stPoint = doubleValue(expected, LAT_OF_1ST_POINT, values); longitudeOf1stPoint = doubleValue(expected, LONG_OF_1ST_POINT, values); latitudeOf2ndPoint = doubleValue(expected, LAT_OF_2ND_POINT, values); longitudeOf2ndPoint = doubleValue(expected, LONG_OF_2ND_POINT, values); } /** * {@inheritDoc} */ @Override public ParameterValueGroup getParameterValues() { final ParameterValueGroup values = super.getParameterValues(); final ParameterDescriptorGroup descriptor = getParameterDescriptors(); final Collection expected = descriptor.descriptors(); // Note: we don't need a "if (twoPoint) ... else" statement since // the "set" method will actually set the value only if applicable. set(expected, RECTIFIED_GRID_ANGLE, values, rectifiedGridAngle); set(expected, LAT_OF_1ST_POINT, values, latitudeOf1stPoint); set(expected, LONG_OF_1ST_POINT, values, longitudeOf1stPoint); set(expected, LAT_OF_2ND_POINT, values, latitudeOf2ndPoint); set(expected, LONG_OF_2ND_POINT, values, longitudeOf2ndPoint); return values; } /** * Infers from the parameter values if the azimuth is defined by two points on the central * line. * * @return {@code true} if using two points on the central line to specify the azimuth. * @throws IllegalArgumentException if the parameter values appear to be a mix of the * "azimuth" and the "two points" cases. */ public boolean usesTwoPoints() throws IllegalArgumentException { final boolean isTwoPoints = isNaN(azimuth) && isNaN(rectifiedGridAngle); final String problem; if (isTwoPoints == isNaN(latitudeOf1stPoint)) problem = "latitudeOf1stPoint"; else if (isTwoPoints == isNaN(longitudeOf1stPoint)) problem = "longitudeOf1stPoint"; else if (isTwoPoints == isNaN(latitudeOf2ndPoint)) problem = "latitudeOf2ndPoint"; else if (isTwoPoints == isNaN(longitudeOf2ndPoint)) problem = "longitudeOf2ndPoint"; else { return isTwoPoints; } if (isTwoPoints) { throw new ParameterNotFoundException(Errors.format( Errors.Keys.NO_PARAMETER_$1, problem), problem); } else { throw new InvalidParameterNameException(Errors.format( Errors.Keys.UNEXPECTED_PARAMETER_$1, problem), problem); } } /** * Ensures that the arguments are valid. This method it invoked before {@link #validate()}; * it does not yet initialize the normalize/denormalize affine transforms. * * @param isTwoPoints {@code true} for the "two points" case, * or {@code false} for the "azimuth" case. * @throws IllegalArgumentException if a field has an illegal value. */ final void validate(final boolean isTwoPoints) throws IllegalArgumentException { /* * Checks that 'latitudeOfCentre' is not +- 90 degrees. * Not checking if 'latitudeOfCentere' is 0, since equations behave correctly. */ ensureLatitudeInRange (LATITUDE_OF_CENTRE, latitudeOfCentre, false); ensureLongitudeInRange(LONGITUDE_OF_CENTRE, longitudeOfCentre, true); if (!isTwoPoints) { return; } ensureLatitudeInRange (LAT_OF_1ST_POINT, latitudeOf1stPoint, false); ensureLongitudeInRange(LONG_OF_1ST_POINT, longitudeOf1stPoint, true); ensureLatitudeInRange (LAT_OF_2ND_POINT, latitudeOf2ndPoint, true); ensureLongitudeInRange(LONG_OF_2ND_POINT, longitudeOf2ndPoint, true); /* * Ensures that (phi1 != phi2), (phi1 != 0°) and (phi2 != -90°), * as specified in class javadoc. */ final ParameterDescriptor desc; final Object value; if (abs(latitudeOf1stPoint - latitudeOf2ndPoint) < FINER_EPSILON) { desc = LAT_OF_1ST_POINT; value = LAT_OF_2ND_POINT.getName().getCode(); } else if (abs(latitudeOf1stPoint) < FINER_EPSILON) { desc = LAT_OF_1ST_POINT; value = new Latitude(latitudeOf1stPoint); } else if (abs(latitudeOf2ndPoint + 90) < FINER_EPSILON) { desc = LAT_OF_2ND_POINT; value = new Latitude(latitudeOf2ndPoint); } else { return; } final String name = desc.getName().getCode(); throw new InvalidParameterValueException(Errors.format( Errors.Keys.ILLEGAL_ARGUMENT_$2, name, value), name, value); } } /** * Constants used in the transformation. Those coefficients * depend only on {@link Parameters#latitudeOfCentre}. */ private final double B, E; /** * v values when the input latitude is a pole. Those values are derived * from {@code gamma0} only, so they don't need to be compared in the {@code equals} * method if {@code singamma0} and {@code cosgamma0} are compared. */ private final double v_pole_n, v_pole_s; /** * Sine and Cosine values for gamma0 (the angle between the meridian * and central line at the intersection between the central line and * the Earth equator on aposphere). */ private final double singamma0, cosgamma0; /** * Creates an Oblique Mercator projection from the given parameters. * The descriptor argument is usually the {@code PARAMETERS} constant defined in * {@link org.geotoolkit.referencing.operation.provider.ObliqueMercator} or a subclass, * but is not restricted to. If a different descriptor is supplied, it is user's * responsibility to ensure that it is suitable to an Oblique Mercator projection. * * @param descriptor Typically * {@link org.geotoolkit.referencing.operation.provider.ObliqueMercator#PARAMETERS}, * or the parameters from one of its subclasses. * @param values The parameter values of the projection to create. * @return The map projection. * * @since 3.00 */ public static MathTransform2D create(final ParameterDescriptorGroup descriptor, final ParameterValueGroup values) { final Parameters parameters = new Parameters(descriptor, values); final ObliqueMercator projection = new ObliqueMercator(parameters); return projection.createConcatenatedTransform(); } /** * Constructs a new map projection from the supplied parameters. * * @param parameters The parameters of the projection to be created. */ protected ObliqueMercator(final Parameters parameters) { super(parameters); final boolean isTwoPoints = parameters.usesTwoPoints(); parameters.validate(isTwoPoints); /* * Computes some common constants. All those constants * depend only on the latitude of centre, in radians. */ final double latitudeOfCentre = toRadians(parameters.latitudeOfCentre); final double com = sqrt(1 - excentricitySquared); final double sinφ0 = sin(latitudeOfCentre); final double cosφ0 = cos(latitudeOfCentre); double t = cosφ0 * cosφ0; // t is used as a temporary variable. B = sqrt(1 + excentricitySquared * (t*t) / (1 - excentricitySquared)); final double con = 1 - excentricitySquared * (sinφ0 * sinφ0); final double A = B * com / con; final double D = B * com / (cosφ0 * sqrt(con)); double F = D*D - 1; if (F < 0) { F = 0; } else { F = copySign(sqrt(F), latitudeOfCentre); } F += D; E = F * pow(tsfn(latitudeOfCentre, sinφ0), B); /* * Computes the constants that depend on the "twoPoint" vs "azimuth" case. In the * two points case, we compute them from (LAT_OF_1ST_POINT, LONG_OF_1ST_POINT) and * (LAT_OF_2ND_POINT, LONG_OF_2ND_POINT). For the "azimuth" case, we compute them * from LONGITUDE_OF_CENTRE and AZIMUTH. * * All angles that are stored in local variables are in radians. Some of them will * be copied back to the parameters object, where angles are in decimal degrees. */ final double longitudeOfCentre = toRadians(parameters.longitudeOfCentre); final double centralMeridian, azimuth, rectifiedGridAngle, gamma0; if (isTwoPoints) { final double latitudeOf1stPoint = toRadians(parameters.latitudeOf1stPoint); final double latitudeOf2ndPoint = toRadians(parameters.latitudeOf2ndPoint); final double longitudeOf1stPoint = toRadians(parameters.longitudeOf1stPoint); double longitudeOf2ndPoint = toRadians(parameters.longitudeOf2ndPoint); final double H = pow(tsfn(latitudeOf1stPoint, sin(latitudeOf1stPoint)), B); final double L = pow(tsfn(latitudeOf2ndPoint, sin(latitudeOf2ndPoint)), B); final double Fp = E / H; final double P = (L - H) / (L + H); double J = E * E; J = (J - L*H) / (J + L*H); /* * Computes the new value for the central meridian (in radians). Result * will be copied in the parameters object after this "if/else" block. */ double diff = longitudeOf1stPoint - longitudeOf2ndPoint; if (abs(diff) > PI) { longitudeOf2ndPoint = toRadians(parameters.longitudeOf2ndPoint += copySign(360, diff)); diff = longitudeOf1stPoint - longitudeOf2ndPoint; } t = 0.5*(longitudeOf1stPoint + longitudeOf2ndPoint); t -= atan(J * tan(0.5*B*diff)/P) / B; centralMeridian = rollLongitude(t, PI); /* * Computes the rectified grid angle (in radians). * Copies the value to the parameters object (in degrees). */ diff = rollLongitude(longitudeOf1stPoint - centralMeridian, PI); gamma0 = atan(2 * sin(B * diff) / (Fp - 1/Fp)); azimuth = rectifiedGridAngle = asin(D * sin(gamma0)); parameters.azimuth = parameters.rectifiedGridAngle = toDegrees(rectifiedGridAngle); } else { /* * Computes coefficients for the "azimuth" case. The 1st and 2nd points are * expected to be (NaN,NaN) since they are specific to the "two points" case. * This was verified by the call to "parameters.usesTwoPoints()" above. */ azimuth = toRadians(parameters.azimuth); if ((azimuth > -1.5*PI && azimuth < -0.5*PI) || (azimuth > 0.5*PI && azimuth < 1.5*PI)) { final String name = AZIMUTH.getName().getCode(); final Angle value = new Angle(parameters.azimuth); throw new InvalidParameterValueException(Errors.format( Errors.Keys.ILLEGAL_ARGUMENT_$2, name, value), name, value); } if (isNaN(parameters.rectifiedGridAngle)) { parameters.rectifiedGridAngle = parameters.azimuth; } rectifiedGridAngle = toRadians(parameters.rectifiedGridAngle); gamma0 = asin(sin(azimuth) / D); t = 0.5 * (F - 1/F) * tan(gamma0); if (abs(t) > 1) { // Check for asin(±1.00000001) if (abs(abs(t) - 1) > EPSILON) { throw new IllegalArgumentException(Errors.format(Errors.Keys.TOLERANCE_ERROR)); } t = copySign(1, t); } centralMeridian = longitudeOfCentre - asin(t) / B; } parameters.centralMeridian = toDegrees(centralMeridian); singamma0 = sin(gamma0); cosgamma0 = cos(gamma0); v_pole_n = log(tan(0.5 * (PI/2 - gamma0))); v_pole_s = log(tan(0.5 * (PI/2 + gamma0))); final double ArB = A / B; /* * At this point, all parameters have been processed. Now process to their * validation and the initialization of (de)normalize affine transforms. */ parameters.validate(); final AffineTransform denormalize = parameters.normalize(false); denormalize.rotate(-rectifiedGridAngle); if (!parameters.nameMatches(HotineObliqueMercator.PARAMETERS) && !parameters.nameMatches(HotineObliqueMercator.TwoPoint.PARAMETERS)) { final double u_c; if (abs(abs(azimuth) - PI/2) < FINER_EPSILON) { // LongitudeOfCentre = NaN in twoPoint, but azimuth cannot be 90 here (lat1 != lat2) u_c = A * (longitudeOfCentre - centralMeridian); } else { u_c = copySign(ArB * atan2(sqrt(D*D - 1), cos(azimuth)), latitudeOfCentre); } denormalize.translate(0, -u_c); } denormalize.scale(ArB, ArB); finish(); } /** * Converts the specified (λ,φ) coordinate (units in radians) * and stores the result in {@code dstPts} (linear distance on a unit sphere). In addition, * opportunistically computes the projection derivative if {@code derivate} is {@code true}. * * @since 3.20 (derived from 3.00) */ @Override public Matrix transform(final double[] srcPts, final int srcOff, final double[] dstPts, final int dstOff, final boolean derivate) throws ProjectionException { final double λ = rollLongitude(srcPts[srcOff]); final double φ = srcPts[srcOff + 1]; final double x, y; Matrix derivative = null; if (abs(abs(φ) - PI/2) > ANGLE_TOLERANCE) { final double sinφ = sin(φ); final double Q = E / pow(tsfn(φ, sinφ), B); final double iQ = 1 / Q; final double S = 0.5 * (Q - iQ); final double Sp = 0.5 * (Q + iQ); final double V = sin(B * λ); final double U = (S*singamma0 - V*cosgamma0) / Sp; if (abs(abs(U) - 1) < EPSILON) { throw new ProjectionException(Errors.Keys.INFINITE_VALUE_$1, "v"); } final double dV_dλ = cos(B * λ); if (abs(dV_dλ) < FINER_EPSILON) { y = λ * (B*B); } else { y = atan2((S * cosgamma0 + V * singamma0), dV_dλ); } x = atanh(-U); // = 0.5 * log((1-U) / (1+U)); if (derivate) { final double m10, m11; final double dQ_dφ = -B * Q * dtsfn_dφ(φ, sinφ, cos(φ)); final double dU_dλ = -B * (cosgamma0 / Sp) * dV_dλ; final double dU_dφ = dQ_dφ * (singamma0 + (singamma0 + U)/(Q*Q) - U) / (2*Sp); if (abs(dV_dλ) < FINER_EPSILON) { m10 = B*B; // y = λ * (B*B); m11 = 0; } else { final double dS_dφ = 0.5*dQ_dφ * (1 + 1/(Q*Q)); final double M = (S*cosgamma0 + V*singamma0); final double L = hypot(dV_dλ, M); final double P = L + dV_dλ; final double D = (P*P + M*M); m10 = 2 * B * (dV_dλ * (singamma0*P + (V - singamma0*M) * M/L) + V*M) / D; // Y = atan2(M, T) m11 = 2 * cosgamma0 * dS_dφ * (P - M*M/L) / D; } final double R = U*U - 1; derivative = new Matrix2( dU_dλ / R, // x = 0.5 * log((1-U) / (1+U)) dU_dφ / R, m10, m11); } } else { x = (φ > 0 ? v_pole_n : v_pole_s); y = φ; if (derivate) { derivative = new Matrix2(0, 0, 0, 1); } } if (dstPts != null) { dstPts[dstOff] = x; dstPts[dstOff+1] = y; } return derivative; } /** * Transforms the specified (x,y) coordinates * and stores the result in {@code dstPts} (angles in radians). */ @Override protected void inverseTransform(final double[] srcPts, final int srcOff, final double[] dstPts, final int dstOff) throws ProjectionException { final double x = srcPts[srcOff ]; final double y = srcPts[srcOff+1]; final double Qp = exp(-x); final double t = 1 / Qp; final double Sp = 0.5 * (Qp - t); final double Vp = sin(y); final double Up = (Vp * cosgamma0 + Sp * singamma0) / (0.5 * (Qp + t)); double λ, φ; if (abs(abs(Up) - 1) < EPSILON) { λ = 0.0; φ = copySign(PI/2, Up); } else { λ = -atan2((Sp*cosgamma0 - Vp*singamma0), cos(y)) / B; φ = cphi2(pow(E / sqrt((1 + Up) / (1 - Up)), 1/B)); } dstPts[dstOff ] = unrollLongitude(λ); dstPts[dstOff+1] = φ; } /** * Compares the given object with this transform for equivalence. */ @Override public boolean equals(final Object object, final ComparisonMode mode) { if (super.equals(object, mode)) { final ObliqueMercator that = (ObliqueMercator) object; return epsilonEqual(this.B, that.B, mode) && epsilonEqual(this.E, that.E, mode) && epsilonEqual(this.singamma0, that.singamma0, mode) && epsilonEqual(this.cosgamma0, that.cosgamma0, mode); } return false; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy