/**
  * Transform a geographic point (in radians), producing a projected result (in the units of the
  * target coordinate system).
  *
  * @param x the geographic x ordinate (in radians)
  * @param y the geographic y ordinate (in radians)
  * @param dst the projected coordinate (in coordinate system units)
  * @return the target coordinate
  */
 private ProjCoordinate projectRadians(double x, double y, ProjCoordinate dst) {
   project(x, y, dst);
   if (unit == Units.DEGREES) {
     // convert radians to DD
     dst.x *= RTD;
     dst.y *= RTD;
   } else {
     // assume result is in metres
     dst.x = totalScale * dst.x + totalFalseEasting;
     dst.y = totalScale * dst.y + totalFalseNorthing;
   }
   return dst;
 }
 public ProjCoordinate project(double lplam, double lpphi, ProjCoordinate xy) {
   if (spherical) {
     xy.x = Math.asin(Math.cos(lpphi) * Math.sin(lplam));
     xy.y = Math.atan2(Math.tan(lpphi), Math.cos(lplam)) - projectionLatitude;
   } else {
     xy.y = ProjectionMath.mlfn(lpphi, n = Math.sin(lpphi), c = Math.cos(lpphi), en);
     n = 1. / Math.sqrt(1. - es * n * n);
     tn = Math.tan(lpphi);
     t = tn * tn;
     a1 = lplam * c;
     c *= es * c / (1 - es);
     a2 = a1 * a1;
     xy.x = n * a1 * (1. - a2 * t * (C1 - (8. - t + 8. * c) * a2 * C2));
     xy.y -= m0 - n * tn * a2 * (.5 + (5. - t + 6. * c) * a2 * C3);
   }
   return xy;
 }
 /**
  * Inverse-transforms a point (in the units defined by the coordinate system), producing a
  * geographic result (in radians)
  *
  * @param src the input projected coordinate (in coordinate system units)
  * @param dst the inverse-projected geographic coordinate (in radians)
  * @return the target coordinate
  */
 public ProjCoordinate inverseProjectRadians(ProjCoordinate src, ProjCoordinate dst) {
   double x;
   double y;
   if (unit == Units.DEGREES) {
     // convert DD to radians
     x = src.x * DTR;
     y = src.y * DTR;
   } else {
     x = (src.x - totalFalseEasting) / totalScale;
     y = (src.y - totalFalseNorthing) / totalScale;
   }
   projectInverse(x, y, dst);
   if (dst.x < -Math.PI) dst.x = -Math.PI;
   else if (dst.x > Math.PI) dst.x = Math.PI;
   if (projectionLongitude != 0)
     dst.x = ProjectionMath.normalizeLongitude(dst.x + projectionLongitude);
   return dst;
 }
  public ProjCoordinate projectInverse(double xyx, double xyy, ProjCoordinate out) {
    if (spherical) {
      out.y = Math.asin(Math.sin(dd = xyy + projectionLatitude) * Math.cos(xyx));
      out.x = Math.atan2(Math.tan(xyx), Math.cos(dd));
    } else {
      double ph1;

      ph1 = ProjectionMath.inv_mlfn(m0 + xyy, es, en);
      tn = Math.tan(ph1);
      t = tn * tn;
      n = Math.sin(ph1);
      r = 1. / (1. - es * n * n);
      n = Math.sqrt(r);
      r *= (1. - es) * n;
      dd = xyx / n;
      d2 = dd * dd;
      out.y = ph1 - (n * tn / r) * d2 * (.5 - (1. + 3. * t) * d2 * C3);
      out.x = dd * (1. + t * d2 * (-C4 + (1. + 3. * t) * d2 * C5)) / Math.cos(ph1);
    }
    return out;
  }
  public ProjCoordinate project(ProjCoordinate in, ProjCoordinate out) {
    double lon = ProjectionMath.normalizeLongitude(in.x - projectionLongitude);
    double lat = in.y;
    double rho, theta, hold1, hold2, hold3;

    hold2 =
        Math.pow(
            ((1.0 - eccentricity * Math.sin(lat)) / (1.0 + eccentricity * Math.sin(lat))),
            0.5 * eccentricity);
    hold3 = Math.tan(ProjectionMath.QUARTERPI - 0.5 * lat);
    hold1 = (hold3 == 0.0) ? 0.0 : Math.pow(hold3 / hold2, n);
    rho = radius * f * hold1;
    theta = n * lon;

    out.x = rho * Math.sin(theta);
    out.y = rho0 - rho * Math.cos(theta);
    return out;
  }
  public ProjCoordinate inverseProject(ProjCoordinate in, ProjCoordinate out) {
    double theta, temp, rho, t, tphi, phi = 0, delta;

    theta = Math.atan(in.x / (rho0 - in.y));
    out.x = (theta / n) + projectionLongitude;

    temp = in.x * in.x + (rho0 - in.y) * (rho0 - in.y);
    rho = Math.sqrt(temp);
    if (n < 0) rho = -rho;
    t = Math.pow((rho / (radius * f)), 1. / n);
    tphi = ProjectionMath.HALFPI - 2.0 * Math.atan(t);
    delta = 1.0;
    for (int i = 0; i < 100 && delta > 1.0e-8; i++) {
      temp = (1.0 - eccentricity * Math.sin(tphi)) / (1.0 + eccentricity * Math.sin(tphi));
      phi = ProjectionMath.HALFPI - 2.0 * Math.atan(t * Math.pow(temp, 0.5 * eccentricity));
      delta = Math.abs(Math.abs(tphi) - Math.abs(phi));
      tphi = phi;
    }
    out.y = phi;
    return out;
  }
 /**
  * Computes the inverse projection of a given point (i.e. from projection space to geographics).
  * This should be overridden for all projections.
  *
  * @param x the projected x ordinate (in coordinate system units)
  * @param y the projected y ordinate (in coordinate system units)
  * @param dst the inverse-projected geographic coordinate (in radians)
  * @return the target coordinate
  */
 protected ProjCoordinate projectInverse(double x, double y, ProjCoordinate dst) {
   dst.x = x;
   dst.y = y;
   return dst;
 }
 /**
  * Inverse-projects a point (in the units defined by the coordinate system), producing a
  * geographic result (in degrees)
  *
  * @param src the input projected coordinate (in coordinate system units)
  * @param dst the inverse-projected geographic coordinate (in degrees)
  * @return the target coordinate
  */
 public ProjCoordinate inverseProject(ProjCoordinate src, ProjCoordinate dst) {
   inverseProjectRadians(src, dst);
   dst.x *= RTD;
   dst.y *= RTD;
   return dst;
 }
  public ProjCoordinate projectInverse(double x, double y, ProjCoordinate lp) {
    if (spherical) {
      double c, rh, sinc, cosc;

      sinc = Math.sin(c = 2. * Math.atan((rh = ProjectionMath.distance(x, y)) / akm1));
      cosc = Math.cos(c);
      lp.x = 0.;
      switch (mode) {
        case EQUATOR:
          if (Math.abs(rh) <= EPS10) lp.y = 0.;
          else lp.y = Math.asin(y * sinc / rh);
          if (cosc != 0. || x != 0.) lp.x = Math.atan2(x * sinc, cosc * rh);
          break;
        case OBLIQUE:
          if (Math.abs(rh) <= EPS10) lp.y = projectionLatitude;
          else lp.y = Math.asin(cosc * sinphi0 + y * sinc * cosphi0 / rh);
          if ((c = cosc - sinphi0 * Math.sin(lp.y)) != 0. || x != 0.)
            lp.x = Math.atan2(x * sinc * cosphi0, c * rh);
          break;
        case NORTH_POLE:
          y = -y;
        case SOUTH_POLE:
          if (Math.abs(rh) <= EPS10) lp.y = projectionLatitude;
          else lp.y = Math.asin(mode == SOUTH_POLE ? -cosc : cosc);
          lp.x = (x == 0. && y == 0.) ? 0. : Math.atan2(x, y);
          break;
      }
    } else {
      double cosphi, sinphi, tp, phi_l, rho, halfe, halfpi;

      rho = ProjectionMath.distance(x, y);
      switch (mode) {
        case OBLIQUE:
        case EQUATOR:
        default: // To prevent the compiler complaining about uninitialized vars.
          cosphi = Math.cos(tp = 2. * Math.atan2(rho * cosphi0, akm1));
          sinphi = Math.sin(tp);
          phi_l = Math.asin(cosphi * sinphi0 + (y * sinphi * cosphi0 / rho));
          tp = Math.tan(.5 * (ProjectionMath.HALFPI + phi_l));
          x *= sinphi;
          y = rho * cosphi0 * cosphi - y * sinphi0 * sinphi;
          halfpi = ProjectionMath.HALFPI;
          halfe = .5 * e;
          break;
        case NORTH_POLE:
          y = -y;
        case SOUTH_POLE:
          phi_l = ProjectionMath.HALFPI - 2. * Math.atan(tp = -rho / akm1);
          halfpi = -ProjectionMath.HALFPI;
          halfe = -.5 * e;
          break;
      }
      for (int i = 8; i-- != 0; phi_l = lp.y) {
        sinphi = e * Math.sin(phi_l);
        lp.y = 2. * Math.atan(tp * Math.pow((1. + sinphi) / (1. - sinphi), halfe)) - halfpi;
        if (Math.abs(phi_l - lp.y) < EPS10) {
          if (mode == SOUTH_POLE) lp.y = -lp.y;
          lp.x = (x == 0. && y == 0.) ? 0. : Math.atan2(x, y);
          return lp;
        }
      }
      throw new ConvergenceFailureException("Iteration didn't converge");
    }
    return lp;
  }
  public ProjCoordinate project(double lam, double phi, ProjCoordinate xy) {
    double coslam = Math.cos(lam);
    double sinlam = Math.sin(lam);
    double sinphi = Math.sin(phi);

    if (spherical) {
      double cosphi = Math.cos(phi);

      switch (mode) {
        case EQUATOR:
          xy.y = 1. + cosphi * coslam;
          if (xy.y <= EPS10) throw new ProjectionException();
          xy.x = (xy.y = akm1 / xy.y) * cosphi * sinlam;
          xy.y *= sinphi;
          break;
        case OBLIQUE:
          xy.y = 1. + sinphi0 * sinphi + cosphi0 * cosphi * coslam;
          if (xy.y <= EPS10) throw new ProjectionException();
          xy.x = (xy.y = akm1 / xy.y) * cosphi * sinlam;
          xy.y *= cosphi0 * sinphi - sinphi0 * cosphi * coslam;
          break;
        case NORTH_POLE:
          coslam = -coslam;
          phi = -phi;
        case SOUTH_POLE:
          if (Math.abs(phi - ProjectionMath.HALFPI) < TOL) throw new ProjectionException();
          xy.x = sinlam * (xy.y = akm1 * Math.tan(ProjectionMath.QUARTERPI + .5 * phi));
          xy.y *= coslam;
          break;
      }
    } else {
      double sinX = 0, cosX = 0, X, A;

      if (mode == OBLIQUE || mode == EQUATOR) {
        sinX = Math.sin(X = 2. * Math.atan(ssfn(phi, sinphi, e)) - ProjectionMath.HALFPI);
        cosX = Math.cos(X);
      }
      switch (mode) {
        case OBLIQUE:
          A = akm1 / (cosphi0 * (1. + sinphi0 * sinX + cosphi0 * cosX * coslam));
          xy.y = A * (cosphi0 * sinX - sinphi0 * cosX * coslam);
          xy.x = A * cosX;
          break;
        case EQUATOR:
          A = 2. * akm1 / (1. + cosX * coslam);
          xy.y = A * sinX;
          xy.x = A * cosX;
          break;
        case SOUTH_POLE:
          phi = -phi;
          coslam = -coslam;
          sinphi = -sinphi;
        case NORTH_POLE:
          xy.x = akm1 * ProjectionMath.tsfn(phi, sinphi, e);
          xy.y = -xy.x * coslam;
          break;
      }
      xy.x = xy.x * sinlam;
    }
    return xy;
  }