Class MercatorProjection

java.lang.Object
edu.cnu.mdi.mapping.projection.MercatorProjection
All Implemented Interfaces:
IMapProjection

public class MercatorProjection extends Object implements IMapProjection
Spherical Mercator projection (EPSG:3857 style, unit sphere).

Forward equations

   x = λ - λ₀          (relative longitude, wrapped to [-π, π))
   y = ln(tan(π/4 + φ/2))
 

Inverse equations

   λ = wrap(x + λ₀)
   φ = 2 atan(exp(y)) - π/2
 

Domain

Latitude is clamped to ±89° to avoid infinite Y values at the poles. The projection space bounding box is therefore [-π, π] × [mercatorY(-89°), mercatorY(89°)].

Seam handling

The Mercator projection has a seam at longitude λ₀ ± π. The crossesSeam(double, double) override detects when a polygon edge spans more than π radians of relative longitude, which signals that the edge wraps around the seam and must be split by CountryRenderer.

  • Constructor Details

    • MercatorProjection

      public MercatorProjection(MapTheme theme)
      Creates a Mercator projection with the supplied theme and the default central longitude of -70° (centred on the western Atlantic).
      Parameters:
      theme - the map theme to use; must not be null
  • Method Details

    • getCentralLongitude

      public double getCentralLongitude()
      Returns the central longitude λ₀ in radians.
      Returns:
      current central longitude (radians)
    • setCentralLongitude

      public void setCentralLongitude(double centralLongitude)
      Sets the central longitude λ₀ in radians.

      The supplied value is normalized to (-π, π] via IMapProjection.wrapLongitude(double) before storage, so callers may pass any radian value without pre-normalizing.

      Parameters:
      centralLongitude - desired central longitude in radians
    • crossesSeam

      public boolean crossesSeam(double lon1, double lon2)
      Tests whether the segment between two longitudes crosses the projection seam (the anti-meridian relative to the central longitude).

      Global cylindrical projections such as MercatorProjection and MollweideProjection override this method to detect wrap-around so that polygon edges that cross the seam can be split rather than drawn as a horizontal streak across the map.

      The default implementation returns false, which is correct for hemispherical projections (OrthographicProjection, LambertEqualAreaProjection) where no seam split is needed.

      Returns true when the absolute difference of the two longitudes — each wrapped relative to λ₀ — exceeds π radians, indicating that the segment crosses the antimeridian seam at λ₀ ± π.

      Specified by:
      crossesSeam in interface IMapProjection
      Parameters:
      lon1 - first longitude in radians
      lon2 - second longitude in radians
      Returns:
      true if the segment crosses the seam; false otherwise
    • isLongitudePeriodic

      public boolean isLongitudePeriodic()
      Reports whether the projection's horizontal domain represents a complete, periodically wrapped 360-degree longitude range.

      Adaptive graticule rendering uses this distinction when the entire projected width is visible. Sampling the two horizontal edges of such a projection produces the same seam longitude and can otherwise make half the world appear to be outside the visible longitude extent.

      Specified by:
      isLongitudePeriodic in interface IMapProjection
      Returns:
      true for full-width periodic projections such as Mercator and Mollweide; otherwise false
    • latLonToXY

      public void latLonToXY(Point2D.Double latLon, Point2D.Double xy)
      Forward projection: geographic (λ, φ) → projection-space (x, y).

      Implementations should clamp the latitude to their valid range before computing to avoid NaN or Infinity results at the poles.

      For hemispherical projections (e.g. orthographic) a point on the far side of the globe should set xy to (Double.NaN, Double.NaN) rather than throwing, because callers test isPointVisible separately.

      Latitude is clamped to [MIN_LAT, MAX_LAT] before projection. Longitude is wrapped relative to the central longitude so that the result always lies in [-π, π).

      Specified by:
      latLonToXY in interface IMapProjection
      Parameters:
      latLon - input geographic point in radians (x=λ, y=φ); must not be null
      xy - output projection-space point; must not be null
    • latLonFromXY

      public void latLonFromXY(Point2D.Double latLon, Point2D.Double xy)
      Inverse projection: projection-space (x, y) → geographic (λ, φ).

      If the input point lies outside the valid projection domain, implementations should set latLon to (Double.NaN, Double.NaN) rather than throwing.

      The inverse Mercator un-wraps X by adding λ₀, then uses the standard Gudermannian inverse to recover latitude.

      Specified by:
      latLonFromXY in interface IMapProjection
      Parameters:
      latLon - output geographic point in radians (x=λ, y=φ); must not be null
      xy - input projection-space point; must not be null
    • isPointVisible

      public boolean isPointVisible(Point2D.Double latLon)
      Tests whether a geographic location is visible in this projection.

      Global projections (Mercator, Mollweide, Lambert) generally return true for any point within their latitude range. Hemispherical projections (Orthographic) return true only for points on the visible hemisphere.

      Returns true for all latitudes in [MIN_LAT, MAX_LAT]; the poles are excluded because the Mercator Y diverges there.

      Specified by:
      isPointVisible in interface IMapProjection
      Parameters:
      latLon - geographic point in radians (x=λ, y=φ)
      Returns:
      true if the point is visible; false otherwise
    • isPointOnMap

      public boolean isPointOnMap(Point2D.Double xy)
      Tests whether a projection-space point lies within the valid domain of this projection.

      This is used after IMapProjection.latLonToXY(java.awt.geom.Point2D.Double, java.awt.geom.Point2D.Double) to guard against rendering points that fall slightly outside the projection boundary due to floating-point rounding.

      The Mercator map domain is the rectangle [MIN_LON, MAX_LON] × [MIN_Y, MAX_Y].

      Specified by:
      isPointOnMap in interface IMapProjection
      Parameters:
      xy - projection-space point
      Returns:
      true if the point is on (or inside) the map; false otherwise
    • drawMapOutline

      public void drawMapOutline(Graphics2D g2, IContainer container)
      Draws the outer boundary of the projection domain.

      Typical implementations build a Path2D in world coordinates, transform each vertex to screen space via container.worldToLocal, then stroke the path using colors and widths from the current MapTheme.

      Draws the rectangular bounding box of the projection domain using the theme's outline color and stroke width.

      Specified by:
      drawMapOutline in interface IMapProjection
      Parameters:
      g2 - graphics context to draw into
      container - container providing the world-to-local transform
    • drawLatitudeLine

      public void drawLatitudeLine(Graphics2D g2, IContainer container, double latitude)
      Draws a line of constant latitude (a parallel) at the given latitude.

      Implementations should approximate the curve by sampling at a sufficient number of longitude values, projecting each sample, and connecting the resulting screen points with a polyline. The line color must be MapTheme.getGraticuleColor() so that theme switching takes full effect.

      Draws a horizontal line at the given latitude by sampling 360 evenly-spaced longitudes across the full longitude range and connecting the projected screen points. Uses MapTheme.getGraticuleColor() so the color respects theme switching.

      Specified by:
      drawLatitudeLine in interface IMapProjection
      Parameters:
      g2 - graphics context
      container - container providing the world-to-local transform
      latitude - latitude φ in radians, typically in [-π/2, π/2]
    • drawLongitudeLine

      public void drawLongitudeLine(Graphics2D g2, IContainer container, double longitude)
      Draws a line of constant longitude (a meridian) at the given longitude.

      Implementations should approximate the curve by sampling at a sufficient number of latitude values, projecting each sample, and connecting the resulting screen points with a polyline. The line color must be MapTheme.getGraticuleColor() so that theme switching takes full effect.

      Draws a vertical line at the given longitude by sampling 360 evenly-spaced latitudes and connecting the projected screen points. Uses MapTheme.getGraticuleColor() so the color respects theme switching.

      Specified by:
      drawLongitudeLine in interface IMapProjection
      Parameters:
      g2 - graphics context
      container - container providing the world-to-local transform
      longitude - longitude λ in radians, typically in [-π, π]
    • getProjection

      public EProjection getProjection()
      Returns the EProjection enum constant that identifies this implementation, if this projection is one of MDI's built-in projections.

      Application-supplied projections are allowed to return null. This permits an application to provide a projection without modifying EProjection, ProjectionFactory, or other MDI built-in projection registries.

      Specified by:
      getProjection in interface IMapProjection
      Returns:
      the built-in projection type, or null for a custom application-supplied projection
    • getXYBounds

      public Rectangle2D.Double getXYBounds()
      Returns the bounding rectangle of the projection-space domain in world coordinates.

      This rectangle is used by IContainer implementations to build the initial world-to-screen transform. The returned rectangle should tightly enclose the entire valid projected domain (e.g. [-π, π] × [MIN_Y, MAX_Y] for Mercator).

      Returns [-π, π] × [MIN_Y, MAX_Y] where the Y bounds correspond to the Mercator projection of ±89°.

      Specified by:
      getXYBounds in interface IMapProjection
      Returns:
      bounding rectangle in projection space; never null
    • getTheme

      public MapTheme getTheme()
      Returns the MapTheme currently used by this projection for drawing outlines and graticule lines.

      Implementations must never return null once the theme has been set.

      Specified by:
      getTheme in interface IMapProjection
      Returns:
      the active map theme
    • setTheme

      public void setTheme(MapTheme theme)
      Sets the MapTheme used by this projection for all subsequent drawing operations.
      Specified by:
      setTheme in interface IMapProjection
      Parameters:
      theme - the new theme; must not be null
      Throws:
      IllegalArgumentException - if theme is null
    • createClipShape

      public Shape createClipShape(IContainer container)
      Creates a clip Shape in device coordinates that matches the projection's valid domain.

      This shape is used to:

      Returns a rectangular clip path matching getXYBounds(), converted to device coordinates via the container transform. Used by IMapProjection.fillOcean(java.awt.Graphics2D, edu.cnu.mdi.container.IContainer) to restrict the ocean fill to the map rectangle.

      Specified by:
      createClipShape in interface IMapProjection
      Parameters:
      container - container providing the world-to-local transform
      Returns:
      a device-space Shape, or null if no clip is defined for this projection