From d7462414202633054ae0933d539dcf16784d11d5 Mon Sep 17 00:00:00 2001 From: Sean Arms <67096+lesserwhirls@users.noreply.github.com> Date: Wed, 8 Jul 2026 17:44:22 -0600 Subject: [PATCH] Improve memory usage when computing the true bounding box MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A potentially large amount of memory was being consumed by the list of HorizontalPosition objects in pursuit of computing the true grid bounding box. This change skips constructing the list saving memory. This change also caches the bounding box, as multiple grids may share the same set of horizontal axes, which can save quite a lot of time for high resolution datasets with a large number of grids. --- .../edal/grid/cdm/CdmTransformedGrid.java | 89 +++++++++++++------ 1 file changed, 61 insertions(+), 28 deletions(-) diff --git a/cdm/src/main/java/uk/ac/rdg/resc/edal/grid/cdm/CdmTransformedGrid.java b/cdm/src/main/java/uk/ac/rdg/resc/edal/grid/cdm/CdmTransformedGrid.java index 9733e5153..37712c804 100644 --- a/cdm/src/main/java/uk/ac/rdg/resc/edal/grid/cdm/CdmTransformedGrid.java +++ b/cdm/src/main/java/uk/ac/rdg/resc/edal/grid/cdm/CdmTransformedGrid.java @@ -31,18 +31,22 @@ import java.util.Collections; import java.util.List; +import java.util.Objects; import org.apache.sis.metadata.iso.extent.DefaultGeographicBoundingBox; +import org.ehcache.Cache; +import org.ehcache.config.builders.CacheConfigurationBuilder; +import org.ehcache.config.builders.ResourcePoolsBuilder; import org.opengis.metadata.extent.GeographicBoundingBox; import org.opengis.referencing.crs.CoordinateReferenceSystem; import ucar.nc2.dataset.CoordinateAxis1D; import ucar.nc2.dt.GridCoordSystem; import ucar.unidata.geoloc.LatLonPoint; -import ucar.unidata.geoloc.LatLonRect; import ucar.unidata.geoloc.Projection; import ucar.unidata.geoloc.ProjectionImpl; import ucar.unidata.geoloc.ProjectionPoint; import ucar.unidata.geoloc.projection.RotatedPole; +import uk.ac.rdg.resc.edal.cache.EdalCache; import uk.ac.rdg.resc.edal.domain.Extent; import uk.ac.rdg.resc.edal.geometry.BoundingBox; import uk.ac.rdg.resc.edal.geometry.BoundingBoxImpl; @@ -68,6 +72,11 @@ */ public class CdmTransformedGrid extends AbstractTransformedGrid { private static final long serialVersionUID = 1L; + + private static final int MAX_HEAP_ENTRIES = 50; + private static final String CACHE_NAME = "BBOX_CACHE"; + private static final Cache bboxCache; + private final ProjectionImpl proj; private final ReferenceableAxis xAxis; @@ -76,6 +85,19 @@ public class CdmTransformedGrid extends AbstractTransformedGrid { private transient Array2D domainObjs = null; + static { + Cache existing = EdalCache.cacheManager + .getCache("BBOX_CACHE", Integer.class, BoundingBox.class); + if (existing == null) { + bboxCache = EdalCache.cacheManager.createCache(CACHE_NAME, + CacheConfigurationBuilder.newCacheConfigurationBuilder(Integer.class, + BoundingBox.class, + ResourcePoolsBuilder.heap(MAX_HEAP_ENTRIES))); + } else { + bboxCache = existing; + } + } + /** * Create a new {@link CdmTransformedGrid} from a defined * {@link ProjectionImpl} and a pair of axes @@ -133,44 +155,50 @@ public CdmTransformedGrid(GridCoordSystem coordSys) { xAxis = CdmUtils.createReferenceableAxis((CoordinateAxis1D) coordSys.getXHorizAxis(), xAxisIsLongitude); yAxis = CdmUtils.createReferenceableAxis((CoordinateAxis1D) coordSys.getYHorizAxis()); - - List gridPoints = new ArrayList<>(); - for(Double x : xAxis.getCoordinateValues()) { - for(Double y : yAxis.getCoordinateValues() ) { - LatLonPoint ll = proj.projToLatLon(x, y); - gridPoints.add(new HorizontalPosition(ll.getLongitude(), ll.getLatitude())); + // create the key for the bbox cache + final int hcsHash = Objects.hash(xAxis, yAxis); + if (!bboxCache.containsKey(hcsHash)) { + // compute the bounding box (can be expensive for large grids) + System.out.println("Computing bounding box for grid with hash " + hcsHash); + double latMin = Double.MAX_VALUE; + double lonMin = Double.MAX_VALUE; + double latMax = Double.MIN_VALUE; + double lonMax = Double.MIN_VALUE; + + for (Double x : xAxis.getCoordinateValues()) { + for (Double y : yAxis.getCoordinateValues()) { + LatLonPoint ll = proj.projToLatLon(x, y); + lonMin = Math.min(lonMin, ll.getLongitude()); + lonMax = Math.max(lonMax, ll.getLongitude()); + latMin = Math.min(latMin, ll.getLatitude()); + latMax = Math.max(latMax, ll.getLatitude()); } - } + } - BoundingBox latLonBoundingBox = GISUtils.getBoundingBox(gridPoints); - /* - * Some projections do not have a well-defined lat-lon bounding box and - * return NaNs. In these cases, we fall back to using global limits - */ - double lonMin = latLonBoundingBox.getMinX(); - double lonMax = latLonBoundingBox.getMaxX(); - double latMin = latLonBoundingBox.getMinY(); - double latMax = latLonBoundingBox.getMaxY(); - if (Double.isNaN(lonMin)) { + /* + * Some projections do not have a well-defined lat-lon bounding box and + * return NaNs. In these cases, we fall back to using global limits + */ + if (Double.isNaN(lonMin)) { lonMin = -180.0; - } - if (Double.isNaN(lonMax)) { + } + if (Double.isNaN(lonMax)) { lonMax = 180.0; - } - if (Double.isNaN(latMin) || Math.abs(latMin + 90) < 1) { + } + if (Double.isNaN(latMin) || Math.abs(latMin + 90) < 1) { latMin = -90.0; - } - if (Double.isNaN(latMax) || Math.abs(latMax - 90) < 1) { + } + if (Double.isNaN(latMax) || Math.abs(latMax - 90) < 1) { latMax = 90.0; - } + } - if (latMin != latMax && lonMin != lonMax) { + if (latMin != latMax && lonMin != lonMax) { /* * The normal situation - use the bounds to create the bounding box */ bbox = new BoundingBoxImpl(lonMin, latMin, lonMax, latMax, - GISUtils.defaultGeographicCRS()); - } else { + GISUtils.defaultGeographicCRS()); + } else { /* * Some projections (MSGnavigation as returned by GRIB is the only * one I've actually found so far) end up returning a bounding box @@ -180,6 +208,11 @@ public CdmTransformedGrid(GridCoordSystem coordSys) { * It seems to be the case that the projection just doesn't work. */ bbox = BoundingBoxImpl.global(); + } + bboxCache.put(hcsHash, bbox); + } else { + System.out.println("Obtain bounding box for grid with hash " + hcsHash + " from cache"); + bbox = bboxCache.get(hcsHash); } }