diff --git a/cdm-test/src/test/java/ucar/nc2/ft/coverage/TestCoverageSubsetTime.java b/cdm-test/src/test/java/ucar/nc2/ft/coverage/TestCoverageSubsetTime.java index b0faa95cc9..88c539cbfe 100644 --- a/cdm-test/src/test/java/ucar/nc2/ft/coverage/TestCoverageSubsetTime.java +++ b/cdm-test/src/test/java/ucar/nc2/ft/coverage/TestCoverageSubsetTime.java @@ -792,6 +792,46 @@ public void testRegularIntervalSubsetTimeRange() throws IOException, InvalidRang } } + @Test + public void testDiscontiguousIntervalSubsetTimeRangeStartNoInInterval() throws IOException, InvalidRangeException { + String endpoint = TestDir.cdmUnitTestDir + "datasets/NDFD-CONUS-5km/NDFD-CONUS-5km.ncx4"; + String covName = "Minimum_temperature_height_above_ground_12_Hour_Minimum"; + + logger.debug("testDiscontiguousIntervalTime Dataset {} coverage {}", endpoint, covName); + + try (FeatureDatasetCoverage featureDatasetCoverage = CoverageDatasetFactory.open(endpoint)) { + assertThat(featureDatasetCoverage).isNotNull(); + CoverageCollection coverageCollection = featureDatasetCoverage.findCoverageDataset(FeatureType.GRID); + assertThat(coverageCollection).isNotNull(); + Coverage coverage = coverageCollection.findCoverage(covName); + assertThat(coverage).isNotNull(); + + SubsetParams params = new SubsetParams(); + + // key point of test - start time lands between two discontiguous time windows + CalendarDate subsetTimeStart = CalendarDate.parseISOformat(null, "2013-12-18T20:00:00Z"); + CalendarDate subsetTimeEnd = CalendarDate.parseISOformat(null, "2013-12-19T13:00:00Z"); + // expect: any interval containing or ending on the start or end times + // idx keep interval + // 0 N ( 84.000000, 96.000000) == (2013-12-15T12:00:00Z, 2013-12-16T00:00:00Z) + // 1 N (108.000000, 120.000000) == (2013-12-16T12:00:00Z, 2013-12-17T00:00:00Z) + // 2 N (132.000000, 144.000000) == (2013-12-17T12:00:00Z, 2013-12-18T00:00:00Z) + // 3 Y (156.000000, 168.000000) == (2013-12-18T12:00:00Z, 2013-12-19T00:00:00Z) + // 4 Y (180.000000, 192.000000) == (2013-12-19T12:00:00Z, 2013-12-20T00:00:00Z) + // 5 N (204.000000, 216.000000) == (2013-12-20T12:00:00Z, 2013-12-21T00:00:00Z) + int expectedStartIndex = 3; + int expectedEndIndex = 4; + params.setTimeRange(CalendarDateRange.of(subsetTimeStart, subsetTimeEnd)); + logger.debug(" subset {}", params); + + GeoReferencedArray geo = coverage.readData(params); + assertThat(geo).isNotNull(); + CoverageCoordAxis timeAxis = geo.findCoordAxis("time1"); + assertThat(timeAxis).isNotNull(); + assertThat(timeAxis.getSpacing()).isEqualTo(Spacing.discontiguousInterval); + assertThat(timeAxis.getRange()).isEqualTo(new Range(expectedStartIndex, expectedEndIndex)); + } + } /////////////////////////////////////////////////////////////////////////////////////////// // ENsemble diff --git a/cdm/core/src/main/java/ucar/nc2/ft2/coverage/CoordAxisHelper.java b/cdm/core/src/main/java/ucar/nc2/ft2/coverage/CoordAxisHelper.java index 4ce291d799..cd015bd6b2 100644 --- a/cdm/core/src/main/java/ucar/nc2/ft2/coverage/CoordAxisHelper.java +++ b/cdm/core/src/main/java/ucar/nc2/ft2/coverage/CoordAxisHelper.java @@ -205,13 +205,16 @@ else if (upperIndex > axis.getCoordEdgeLast()) // same contract as findCoordElement(); in addition, -1 is returned when the target is not contained in any interval private int findCoordElementDiscontiguousInterval(double target, boolean bounded) { int idx = findSingleHit(target); - // multiple hits = choose closest (definition of closest will be based on axis type) - if (idx == MULTIPLE_HITS) { - return findClosestDiscontiguousInterval(target); - } if (bounded && (idx >= axis.getNcoords())) { return -1; } + // multiple hits = choose closest (definition of closest will be based on axis type) + // - OR - + // idx will be Ncoords if target not contained within an interval window - however, that + // does not mean target isn't between two discontiguous windows, so find the closest one + if (idx == MULTIPLE_HITS || idx == axis.getNcoords()) { + return findClosestDiscontiguousInterval(target); + } return idx; } @@ -272,28 +275,25 @@ private int findClosestDiscontiguousTimeInterval(double target) { int idxFound = -1; for (int i = 0; i < axis.getNcoords(); i++) { - // only check if target is in discontiguous interval i - if (intervalContains(target, i)) { - // find the end of the time interval - double coord = axis.getCoordEdge2(i); - // We want to make sure the interval includes our target point, and that means the end of the interval - // must be greater than or equal to the target. - if (coord >= target) { - // compute the width (in time) of the interval - double width = coord - axis.getCoordEdge1(i); - // we want to identify the interval with the end point closest to our target - // why? Because a statistic computed over a time window will only have meaning at the end - // of that interval, so the closer we can get to that the better. - double diff = Math.abs(coord - target); - // Here we minimize the difference between the end of an interval and our target value. If multiple - // intervals result in the same difference value, we will pick the one with the smallest non-zero - // width interval. - boolean tiebreaker = (diff == minDiff) && (width != 0) && (width < useValue); - if (diff < minDiff || tiebreaker) { - minDiff = diff; - idxFound = i; - useValue = width; - } + // find the end of the time interval + double coord = axis.getCoordEdge2(i); + // We want to make sure the interval includes our target point, and that means the end of the interval + // must be greater than or equal to the target. + if (coord >= target) { + // compute the width (in time) of the interval + double width = coord - axis.getCoordEdge1(i); + // we want to identify the interval with the end point closest to our target + // why? Because a statistic computed over a time window will only have meaning at the end + // of that interval, so the closer we can get to that the better. + double diff = Math.abs(coord - target); + // Here we minimize the difference between the end of an interval and our target value. If multiple + // intervals result in the same difference value, we will pick the one with the smallest non-zero + // width interval. + boolean tiebreaker = (diff == minDiff) && (width != 0) && (width < useValue); + if (diff < minDiff || tiebreaker) { + minDiff = diff; + idxFound = i; + useValue = width; } } }