From 7128226bc401958b58146633401260b2714c3fb2 Mon Sep 17 00:00:00 2001 From: Sean Arms <67096+lesserwhirls@users.noreply.github.com> Date: Mon, 22 Jun 2026 17:33:05 -0600 Subject: [PATCH] Allow center longitude to be set without normalization Some GRIB GDS parameters result in longitudes outside of -180 - 180, or even greater than 360. This change allows for the center longitude of the lat lon projection to be set without normalization, such that it can accuratly represent the longitude range computed from the GDS. While it would probably be better to shift the longitude values to fit within -180 - 180, this change maintains the current behavior. Addresses unidata/netcdf-java#1519 --- .../geoloc/projection/LatLonProjection.java | 51 ++++++++++++++++--- .../java/ucar/nc2/grib/grib1/Grib1Gds.java | 6 +-- .../java/ucar/nc2/grib/grib2/Grib2Gds.java | 5 +- 3 files changed, 48 insertions(+), 14 deletions(-) diff --git a/cdm/core/src/main/java/ucar/unidata/geoloc/projection/LatLonProjection.java b/cdm/core/src/main/java/ucar/unidata/geoloc/projection/LatLonProjection.java index 7b40bbd175..46ade27b46 100644 --- a/cdm/core/src/main/java/ucar/unidata/geoloc/projection/LatLonProjection.java +++ b/cdm/core/src/main/java/ucar/unidata/geoloc/projection/LatLonProjection.java @@ -1,7 +1,8 @@ /* - * Copyright (c) 1998-2018 University Corporation for Atmospheric Research/Unidata + * Copyright (c) 1998-2026 University Corporation for Atmospheric Research/Unidata * See LICENSE for license information. */ + package ucar.unidata.geoloc.projection; import ucar.nc2.constants.CF; @@ -248,23 +249,59 @@ public double[][] latLonToProj(double[][] from, double[][] to, int latIndex, int /** - * Set the center of the Longitude range. It is normalized to +/- 180. + * Set the center longitude of this projection, normalizing it into the range [-180, 180]. + * + *
* The cylinder is cut at the "seam" = centerLon +- 180. * Use this to keep the Longitude values kept in the range [centerLon +-180], which * makes seam handling easier. + * {@link #latLonToProj} returns longitudes in the range + * [{@code centerLon} - 180, {@code centerLon} + 180], so the center longitude controls where the + * seam falls and therefore which longitude values are produced. + * + *
+ * This method always normalizes the supplied value to [-180, 180], which discards information + * about grids whose longitude coordinates legitimately lie outside that range. To preserve + * such information, use + * {@link #setCenterLon(double, boolean)} with {@code normalize = false}. * - * @param centerLon the center of the Longitude range. - * @return centerLon normalized to +/- 180. + * @param centerLon the center of the longitude range, in degrees east. + * @return centerLon normalized to [-180, 180]. + * @deprecated use {@link #setCenterLon(double, boolean)} with {@code normalize = true} */ + @Deprecated public double setCenterLon(double centerLon) { - this.centerLon = LatLonPoints.lonNormal(centerLon); + return setCenterLon(centerLon, true); + } + + /** + * Set the center longitude of this projection, optionally normalizing it into the range [-180, 180]. + * + *
+ * The center longitude controls the location of the projection "seam" (at {@code centerLon} +/- 180). + * This also means it controls the range of longitudes returned by the projection. When {@code normalize} + * is {@code false}, the supplied value is stored as given. This allows the projection to + * reproduce grid longitudes that lie outside [-180, 180]. + * + * @param centerLon the center of the longitude range, in degrees east. + * @param normalize if {@code true}, normalize {@code centerLon} into [-180, 180]; if {@code false}, + * store it unchanged. + * @return the center longitude that was stored. + */ + public double setCenterLon(double centerLon, boolean normalize) { + this.centerLon = normalize ? LatLonPoints.lonNormal(centerLon) : centerLon; return this.centerLon; } /** - * Get the center of the Longitude range. It is normalized to +/- 180. + * Get the center longitude of this projection, in degrees east. + * + *
+ * Note that this value is normalized to [-180, 180] only if it was set via + * {@link #setCenterLon(double)} (or {@link #setCenterLon(double, boolean)} with + * {@code normalize = true}). * - * @return the center longitude + * @return the center longitude. */ public double getCenterLon() { return centerLon; diff --git a/grib/src/main/java/ucar/nc2/grib/grib1/Grib1Gds.java b/grib/src/main/java/ucar/nc2/grib/grib1/Grib1Gds.java index 102f8dd7fa..0d688fd847 100644 --- a/grib/src/main/java/ucar/nc2/grib/grib1/Grib1Gds.java +++ b/grib/src/main/java/ucar/nc2/grib/grib1/Grib1Gds.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 John Caron and University Corporation for Atmospheric Research/Unidata + * Copyright (c) 1998-2026 John Caron and University Corporation for Atmospheric Research/Unidata * See LICENSE for license information. */ @@ -557,9 +557,7 @@ public String toString() { public GdsHorizCoordSys makeHorizCoordSys() { LatLonProjection proj = new LatLonProjection(getEarth()); double centerLon = ((int) ((lo2 - lo1 + deltaLon) / 2 / scale3)) * scale3; - proj.setCenterLon(centerLon); - - // ProjectionPoint startP = proj.latLonToProj(LatLonPoint.create(la1, lo1)); + proj.setCenterLon(centerLon, false); double startx = lo1; // startP.getX(); double starty = la1; // startP.getY(); return new GdsHorizCoordSys(getNameShort(), template, 0, scanMode, proj, startx, getDx(), starty, getDy(), diff --git a/grib/src/main/java/ucar/nc2/grib/grib2/Grib2Gds.java b/grib/src/main/java/ucar/nc2/grib/grib2/Grib2Gds.java index 3550412711..f2878c8499 100644 --- a/grib/src/main/java/ucar/nc2/grib/grib2/Grib2Gds.java +++ b/grib/src/main/java/ucar/nc2/grib/grib2/Grib2Gds.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 John Caron and University Corporation for Atmospheric Research/Unidata + * Copyright (c) 1998-2026 John Caron and University Corporation for Atmospheric Research/Unidata * See LICENSE for license information. */ @@ -519,8 +519,7 @@ public int[] getOptionalPoints() { public GdsHorizCoordSys makeHorizCoordSys() { LatLonProjection proj = new LatLonProjection(getEarth()); double centerLon = ((int) ((lo2 - lo1 + deltaLon) / 2 / getScale())) * getScale(); - proj.setCenterLon(centerLon); - // ProjectionPoint startP = proj.latLonToProj(LatLonPoint.create(la1, lo1)); + proj.setCenterLon(centerLon, false); double startx = lo1; // startP.getX(); double starty = la1; // startP.getY(); return new GdsHorizCoordSys(getNameShort(), template, numberOfDataPoints, scanMode, proj, startx, deltaLon,