MapArea.java

  1. /*
  2.  * *************************************************************************************************************************************************************
  3.  *
  4.  * MapView: a JavaFX map renderer for tile-based servers
  5.  * http://tidalwave.it/projects/mapview
  6.  *
  7.  * Copyright (C) 2024 - 2025 by Tidalwave s.a.s. (http://tidalwave.it)
  8.  *
  9.  * *************************************************************************************************************************************************************
  10.  *
  11.  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
  12.  * You may obtain a copy of the License at
  13.  *
  14.  *     http://www.apache.org/licenses/LICENSE-2.0
  15.  *
  16.  * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
  17.  * CONDITIONS OF ANY KIND, either express or implied.  See the License for the specific language governing permissions and limitations under the License.
  18.  *
  19.  * *************************************************************************************************************************************************************
  20.  *
  21.  * git clone https://bitbucket.org/tidalwave/mapview-src
  22.  * git clone https://github.com/tidalwave-it/mapview-src
  23.  *
  24.  * *************************************************************************************************************************************************************
  25.  */
  26. package it.tidalwave.mapviewer;

  27. import jakarta.annotation.Nonnull;
  28. import lombok.EqualsAndHashCode;
  29. import lombok.Getter;
  30. import lombok.RequiredArgsConstructor;
  31. import static lombok.AccessLevel.PRIVATE;

  32. /***************************************************************************************************************************************************************
  33.  *
  34.  * A rectangular area on the map.
  35.  *
  36.  * @author  Fabrizio Giudici
  37.  *
  38.  **************************************************************************************************************************************************************/
  39. @RequiredArgsConstructor(access = PRIVATE) @Getter @EqualsAndHashCode
  40. public class MapArea
  41.   {
  42.     private final double north;

  43.     private final double east;

  44.     private final double south;

  45.     private final double west;

  46.     /***********************************************************************************************************************************************************
  47.      * {@return a new area}.
  48.      * @param   north     the north limit
  49.      * @param   east      the east limit
  50.      * @param   south     the south limit
  51.      * @param   west      the west limit
  52.      * @throws            IllegalArgumentException when the values are unfeasible
  53.      **********************************************************************************************************************************************************/
  54.     public static MapArea of (final double north, final double east, final double south, final double west)
  55.       {
  56.         checkLatitude(north, "north");
  57.         checkLatitude(south, "south");
  58.         checkLongitude(east, "east");
  59.         checkLongitude(west, "west");

  60.         if (north < south)
  61.           {
  62.             throw new IllegalArgumentException(String.format("north (%f) must be greater on equal than south (%f)", north, south));
  63.           }

  64.         return new MapArea(north, east, south, west);
  65.       }

  66.     /***********************************************************************************************************************************************************
  67.      * {@return the center of this area}.
  68.      **********************************************************************************************************************************************************/
  69.     @Nonnull
  70.     public MapCoordinates getCenter()
  71.       {
  72.         var longitudeCenter = (west + east) / 2;

  73.         if (isAcrossGreenwichAntimeridian())
  74.           {
  75.             longitudeCenter = longitudeCenter - 180;

  76.             if (longitudeCenter <= -180)
  77.               {
  78.                 longitudeCenter = 360 + longitudeCenter;
  79.               }
  80.           }

  81.         return MapCoordinates.of((north + south) / 2, longitudeCenter);
  82.       }

  83.     /***********************************************************************************************************************************************************
  84.      * {@return {@code true} if this area spans across the Greenwich antimeridian (180° W)}.
  85.      **********************************************************************************************************************************************************/
  86.     public boolean isAcrossGreenwichAntimeridian()
  87.       {
  88.         return east < west;
  89.       }

  90.     /***********************************************************************************************************************************************************
  91.      * {@return {@code true} if this area contains the given coordinates}.
  92.      * @param   coordinates   the coordinates
  93.      **********************************************************************************************************************************************************/
  94.     public boolean contains (@Nonnull final MapCoordinates coordinates)
  95.       {
  96.         return coordinates.latitude() <= north && coordinates.latitude() >= south
  97.                && isAcrossGreenwichAntimeridian() ? coordinates.longitude() >= west && coordinates.longitude() <= east
  98.                                                   :  coordinates.longitude() >= east && coordinates.longitude() <= west;
  99.       }

  100.     /***********************************************************************************************************************************************************
  101.      * {@return {@code true} if this area contains the given area}.
  102.      * @param   that   the area to compare
  103.      **********************************************************************************************************************************************************/
  104.     public boolean contains (@Nonnull final MapArea that)
  105.       {
  106.         return north >= that.north && south <= that.south && west <= that.west && east >= that.east;
  107.       }

  108.     /***********************************************************************************************************************************************************
  109.      * {@inheritDoc}
  110.      **********************************************************************************************************************************************************/
  111.     @Override @Nonnull
  112.     public String toString()
  113.       {
  114.         return String.format("(n=%.6f, e=%.6f, s=%.6f, w=%.6f)", north, east, south, west);
  115.       }

  116.     /***********************************************************************************************************************************************************
  117.      *
  118.      **********************************************************************************************************************************************************/
  119.     private static void checkLatitude (final double latitude, @Nonnull final String name)
  120.       {
  121.         if (latitude < -90 || latitude > 90)
  122.           {
  123.             throw new IllegalStateException("Latitude must be in range [-90, 90]: " + name + "=" + latitude);
  124.           }
  125.       }

  126.     /***********************************************************************************************************************************************************
  127.      *
  128.      **********************************************************************************************************************************************************/
  129.     private static void checkLongitude (final double longitude, @Nonnull final String name)
  130.       {
  131.         if (longitude <= -180 || longitude > 180)
  132.           {
  133.             throw new IllegalStateException("Longitude must be in range (-180, 180]: " + name + "=" + longitude);
  134.           }
  135.       }
  136.   }