TransformWgs84DutchRdNew.java

  1. package org.opentrafficsim.animation.gis;

  2. import java.awt.geom.Point2D;
  3. import java.awt.geom.Rectangle2D;

  4. /**
  5.  * Convert geographical coordinates between WGS84 and the Dutch RD (Rijksdriehoek) system. <br>
  6.  * Specific MathTransform for WGS84 (EPSG:4326) to RD_new (EPSG:28992) conversions. Code based on C code by Peter Knoppers as
  7.  * applied <a href="https://www.regiolab-delft.nl/?q=node/36">here</a>, which is based on
  8.  * <a href="https://home.solcon.nl/pvanmanen/Download/Transformatieformules.pdf">this</a> paper.
  9.  * <p>
  10.  * Copyright (c) ~2000-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
  11.  * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
  12.  * </p>
  13.  * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
  14.  * @author <a href="https://github.com/peter-knoppers">Peter Knoppers</a>
  15.  * @author Gert-Jan Stolk
  16.  **/
  17. public final class TransformWgs84DutchRdNew
  18. {

  19.     /** Western boundary of the Dutch RD system. */
  20.     private static final double WGS84_WEST_LIMIT = 3.2;

  21.     /** Eastern boundary of the Dutch RD system. */
  22.     private static final double WGS84_EAST_LIMIT = 7.3;

  23.     /** Northern boundary of the Dutch RD system. */
  24.     private static final double WGS84_SOUTH_LIMIT = 50.6;

  25.     /** Southern boundary of the Dutch RD system. */
  26.     private static final double WGS84_NORTH_LIMIT = 53.7;

  27.     /** Western boundary of the Dutch RD system. */
  28.     private static final double RD_MINIMUM_X = 11000;

  29.     /** Eastern boundary of the Dutch RD system. */
  30.     private static final double RD_MAXIMUM_X = 280000;

  31.     /** Southern boundary of the Dutch RD system. */
  32.     private static final double RD_MINIMUM_Y = 300000;

  33.     /** Northern boundary of the Dutch RD system. */
  34.     private static final double RD_MAXIMUM_Y = 630000;

  35.     /** This class is a utility class and instances cannot be constructed. */
  36.     private TransformWgs84DutchRdNew()
  37.     {
  38.         // This class is a utility class and instances cannot be constructed.
  39.     }

  40.     /**
  41.      * Convert from WGS84 to RD coordinates.
  42.      * @param wgs84East Degrees East of Greenwich
  43.      * @param wgs84North Degrees North of the equator
  44.      * @return equivalent location in the Dutch RD system
  45.      */
  46.     private static Point2D.Double ellipseWgs84ToRd(final double wgs84East, final double wgs84North)
  47.     {
  48.         if (wgs84North > WGS84_NORTH_LIMIT || wgs84North < WGS84_SOUTH_LIMIT || wgs84East < WGS84_WEST_LIMIT
  49.                 || (wgs84East > WGS84_EAST_LIMIT))
  50.         {
  51.             throw new IllegalArgumentException("ellipswgs842rd input out of range (" + wgs84East + ", " + wgs84North + ")");
  52.         }
  53.         //@formatter:off
  54.         @SuppressWarnings("checkstyle:nowhitespacebefore")
  55.         /** Coefficients for ellipsewgs842rd. */
  56.         final double[][] r =
  57.         { /* p down, q right */
  58.             {  155000.00, 190094.945,   -0.008, -32.391, 0.0   , },
  59.             {     -0.705, -11832.228,    0.0  ,   0.608, 0.0   , },
  60.             {      0.0  ,   -114.221,    0.0  ,   0.148, 0.0   , },
  61.             {      0.0  ,     -2.340,    0.0  ,   0.0  , 0.0   , },
  62.             {      0.0  ,      0.0  ,    0.0  ,   0.0  , 0.0   , }
  63.         };
  64.         @SuppressWarnings("checkstyle:nowhitespacebefore")
  65.         final double[][] s =
  66.         { /* p down, q right */
  67.             { 463000.00 ,      0.433, 3638.893,   0.0  ,  0.092, },
  68.             { 309056.544,     -0.032, -157.984,   0.0  , -0.054, },
  69.             {     73.077,      0.0  ,   -6.439,   0.0  ,  0.0  , },
  70.             {     59.788,      0.0  ,    0.0  ,   0.0  ,  0.0  , },
  71.             {      0.0  ,      0.0  ,    0.0  ,   0.0  ,  0.0  , }
  72.         };
  73.         //@formatter:on
  74.         double resultX = 0;
  75.         double resultY = 0;
  76.         double powNorth = 1;
  77.         double dNorth = 0.36 * (wgs84North - 52.15517440);
  78.         double dEast = 0.36 * (wgs84East - 5.38720621);

  79.         for (int p = 0; p < 5; p++)
  80.         {
  81.             double powEast = 1;
  82.             for (int q = 0; q < 5; q++)
  83.             {
  84.                 resultX += r[p][q] * powEast * powNorth;
  85.                 resultY += s[p][q] * powEast * powNorth;
  86.                 powEast *= dEast;
  87.             }
  88.             powNorth *= dNorth;
  89.         }
  90.         return new Point2D.Double(resultX, resultY);
  91.     }

  92.     /**
  93.      * Convert coordinates from WGS84 to the Dutch RD system.
  94.      * @param rdX X coordinate in the Dutch RD system
  95.      * @param rdY Y coordinate in the Dutch RD system
  96.      * @return equivalent location in the WGS84 system
  97.      * @throws IllegalArgumentException when the given coordinates are not within the area of the Dutch RD system
  98.      */
  99.     private static Point2D rdToEllipseWgs84(final double rdX, final double rdY) throws IllegalArgumentException
  100.     {
  101.         if (rdX < RD_MINIMUM_X || rdX > RD_MAXIMUM_X || rdY < RD_MINIMUM_Y || rdY > RD_MAXIMUM_Y)
  102.         {
  103.             throw new IllegalArgumentException(
  104.                     "Location (" + rdX + "," + rdY + ") is not within the range " + "of the Dutch RD system");
  105.         }
  106.         final double dX = (rdX - 155000) / 100000;
  107.         final double dY = (rdY - 463000) / 100000;
  108.         /* Coefficients are for zone 31 (0E .. 6E); roughly west of Apeldoorn */
  109.         //@formatter:off
  110.         @SuppressWarnings("checkstyle:nowhitespacebefore")
  111.         final double[][] k =
  112.         { /* p down, q right */
  113.             { 3600 * 52.15517440, 3235.65389, -0.24750, -0.06550, 0.0    , },
  114.             {        -0.00738   ,   -0.00012,  0.0    ,  0.0    , 0.0    , },
  115.             {       -32.58297   ,   -0.84978, -0.01709, -0.00039, 0.0    , },
  116.             {         0.0       ,    0.0    ,  0.0    ,  0.0    , 0.0    , },
  117.             {         0.00530   ,    0.00033,  0.0    ,  0.0    , 0.0    , },
  118.             {         0.0       ,    0.0    ,  0.0    ,  0.0    , 0.0    , }
  119.         };
  120.         @SuppressWarnings("checkstyle:nowhitespacebefore")
  121.         final double[][] l = { /* p down, q right */
  122.             {  3600 * 5.38720621,    0.01199,  0.00022,  0.0    , 0.0    , },
  123.             {      5260.52916   ,  105.94684,  2.45656,  0.05594, 0.00128, },
  124.             {        -0.00022   ,    0.0    ,  0.0    ,  0.0    , 0.0    , },
  125.             {        -0.81885   ,   -0.05607, -0.00256,  0.0    , 0.0    , },
  126.             {         0.0       ,    0.0    ,  0.0    ,  0.0    , 0.0    , },
  127.             {         0.00026   ,    0.0    ,  0.0    ,  0.0    , 0.0    , }
  128.         };
  129.         //@formatter:on
  130.         double resultNorth = 0;
  131.         double resultEast = 0;
  132.         double powX = 1;
  133.         for (int p = 0; p < 6; p++)
  134.         {
  135.             double powY = 1;
  136.             for (int q = 0; q < 5; q++)
  137.             {
  138.                 resultNorth += k[p][q] * powX * powY / 3600;
  139.                 resultEast += l[p][q] * powX * powY / 3600;
  140.                 powY *= dY;
  141.             }
  142.             powX *= dX;
  143.         }
  144.         return new Point2D.Double(resultEast, resultNorth);
  145.     }

  146.     /**
  147.      * Convert a coordinate pair in the local system to WGS84 coordinates.
  148.      * @param local coordinates in the local system.
  149.      * @return the equivalent location in degrees in the WGS84 coordinate system
  150.      * @throws IllegalArgumentException when <cite>local</cite> is not valid in the local system
  151.      */
  152.     public static Point2D toWgs84(final Point2D local) throws IllegalArgumentException
  153.     {
  154.         return toWgs84(local.getX(), local.getY());
  155.     }

  156.     /**
  157.      * Convert a coordinate pair in the local system to WGS84 coordinates.
  158.      * @param localX X-coordinate in the local system
  159.      * @param localY Y-coordinate in the local system
  160.      * @return the equivalent location in degrees in the WGS84 coordinate system
  161.      * @throws IllegalArgumentException when <cite>localX</cite>, <cite>localY</cite> is not valid in the local system
  162.      */
  163.     public static Point2D toWgs84(final double localX, final double localY) throws IllegalArgumentException
  164.     {
  165.         return rdToEllipseWgs84(localX, localY);
  166.     }

  167.     /**
  168.      * Convert a coordinate pair in WGS84 coordinates to local coordinates.
  169.      * @param wgs84 coordinates in degrees in the WGS84 coordinate system
  170.      * @return the equivalent location in the local coordinate system
  171.      * @throws IllegalArgumentException when <cite>wgs84</cite> is not valid in the local system
  172.      */
  173.     public static Point2D fromWgs84(final Point2D wgs84) throws IllegalArgumentException
  174.     {
  175.         return fromWgs84(wgs84.getX(), wgs84.getY());
  176.     }

  177.     /**
  178.      * Convert a coordinate pair in WGS84 coordinates to local coordinates.
  179.      * @param wgs84East East coordinate in degrees in the WGS84 system (negative value indicates West)
  180.      * @param wgs84North North coordinate in degrees in the WGS84 system (negative value indicates South)
  181.      * @return the equivalent location in the local coordinate system
  182.      * @throws IllegalArgumentException when <cite>wgs84</cite> is not valid in the local system
  183.      */
  184.     public static Point2D fromWgs84(final double wgs84East, final double wgs84North) throws IllegalArgumentException
  185.     {
  186.         return ellipseWgs84ToRd(wgs84East, wgs84North);
  187.     }

  188.     /**
  189.      * Report the bounding box for conversion to the local coordinate system.<br>
  190.      * Conversions from WGS84 to the local coordinate system should fail for locations outside this bounding box. If the valid
  191.      * range is not adequately described by a rectangular bounding box, conversions for some areas within this bounding box may
  192.      * also fail (with an IllegalArgumentException). There is no guarantee that the result of a conversion lies within the
  193.      * bounding box for the reverse conversion.
  194.      * @return bounding box in WGS84 degrees
  195.      */
  196.     public static Rectangle2D fromWgs84Bounds()
  197.     {
  198.         return new Rectangle2D.Double(WGS84_WEST_LIMIT, WGS84_SOUTH_LIMIT, WGS84_EAST_LIMIT - WGS84_WEST_LIMIT,
  199.                 WGS84_NORTH_LIMIT - WGS84_SOUTH_LIMIT);
  200.     }

  201.     /**
  202.      * Report the bounding box for conversions from the local coordinate system. <br>
  203.      * Conversions from the local coordinate system to WGS84 should fail for locations outside this bounding box. If the valid
  204.      * range is not adequately described by a rectangular bounding box, conversions for some areas within this bounding box may
  205.      * also fail (with an IllegalArgumentException). There is no guarantee that the result of a conversion lies within the
  206.      * bounding box for the reverse conversion.
  207.      * @return bounding box of the local coordinate system
  208.      */
  209.     public static Rectangle2D toWgs84Bounds()
  210.     {
  211.         return new Rectangle2D.Double(RD_MINIMUM_X, RD_MINIMUM_Y, RD_MAXIMUM_X - RD_MINIMUM_X, RD_MAXIMUM_Y - RD_MINIMUM_Y);
  212.     }

  213. }