You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by de...@apache.org on 2021/12/01 17:29:20 UTC
[sis] branch geoapi-4.0 updated: Add limited options to `ExtentSelector` for configuring the selection criteria.
This is an automated email from the ASF dual-hosted git repository.
desruisseaux pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git
The following commit(s) were added to refs/heads/geoapi-4.0 by this push:
new de04f27 Add limited options to `ExtentSelector` for configuring the selection criteria.
de04f27 is described below
commit de04f27807d5be3bdb80fdf196496628c20cbc3b
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Wed Dec 1 15:17:20 2021 +0100
Add limited options to `ExtentSelector` for configuring the selection criteria.
---
.../sis/internal/referencing/ExtentSelector.java | 99 ++++++++++++++++++++--
.../internal/referencing/ExtentSelectorTest.java | 87 ++++++++++++++++---
2 files changed, 165 insertions(+), 21 deletions(-)
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ExtentSelector.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ExtentSelector.java
index 11da980..c95f9fd 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ExtentSelector.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ExtentSelector.java
@@ -24,6 +24,7 @@ import org.opengis.metadata.extent.GeographicBoundingBox;
import org.apache.sis.metadata.iso.extent.Extents;
import org.apache.sis.math.MathFunctions;
import org.apache.sis.measure.Range;
+import org.apache.sis.util.resources.Errors;
/**
@@ -58,8 +59,15 @@ import org.apache.sis.measure.Range;
* then the first of those candidates is selected.</li>
* </ol>
*
+ * <h2>Change of rule order</h2>
+ * The following configuration flags change the order in which above rules are applied.
+ *
+ * <ul>
+ * <li>{@link #alternateOrdering} — whether the time center criterion is tested last.</li>
+ * </ul>
+ *
* <h2>Usage</h2>
- * Example!
+ * Example:
*
* {@preformat java
* ExtentSelector<Foo> selector = new ExtentSelector<>(areaOfInterest);
@@ -70,7 +78,7 @@ import org.apache.sis.measure.Range;
* }
*
* @author Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.2
*
* @param <T> the type of object to be selected.
*
@@ -95,6 +103,27 @@ public final class ExtentSelector<T> {
private Instant minTOI, maxTOI;
/**
+ * Granularity of the time of interest in seconds, or 0 if none.
+ * If non-zero, this this is always positive.
+ *
+ * @see #setTimeGranularity(Duration)
+ * @see #round(Duration)
+ */
+ private long granularity;
+
+ /**
+ * Whether to use an alternate conditions order where the time center criterion is tested last.
+ * This flag can be set to {@code true} if the Time Of Interest (TOI) is expected to be larger
+ * than the temporal extent of candidate objects, in which case many objects may fit in the TOI.
+ * Those candidates may be considered practically equally good regarding temporal aspect,
+ * in which case the caller may want to give precedence to geographic area.
+ *
+ * <p>This flag is often used together with {@link #setTimeGranularity(Duration)} method
+ * for reducing the preponderance of temporal criteria.</p>
+ */
+ public boolean alternateOrdering;
+
+ /**
* The best object found so far.
*/
private T best;
@@ -108,6 +137,7 @@ public final class ExtentSelector<T> {
/**
* Duration of the {@linkplain #best} object, or {@code null} if none.
* This is equivalent to {@link #largestArea} in the temporal domain.
+ * Value is rounded by {@link #round(Duration)}.
*/
private Duration longestTime;
@@ -222,6 +252,48 @@ public final class ExtentSelector<T> {
}
/**
+ * Sets the temporal granularity of the Time of Interest (TOI). If non-null, intersections with TOI
+ * will be rounded to an integer amount of this granularity. This is useful if data are expected at
+ * an approximately regular interval (for example one remote sensing image per day) and we want to
+ * ignore slight variations in the temporal extent declared for each image.
+ *
+ * <p>This method is often used together with {@link #alternateOrdering} flag
+ * for reducing the preponderance of temporal criteria.</p>
+ *
+ * @param resolution granularity of the time of interest, or {@code null} if none.
+ * @throws IllegalArgumentException if the given resolution is zero or negative.
+ */
+ public final void setTimeGranularity(final Duration resolution) {
+ if (resolution == null) {
+ granularity = 0;
+ } else if (resolution.isZero() || resolution.isNegative()) {
+ throw new IllegalArgumentException(Errors.format(Errors.Keys.IllegalArgumentValue_2, "resolution", resolution));
+ } else {
+ granularity = resolution.getSeconds();
+ }
+ }
+
+ /**
+ * Returns the given duration rounded to the nearest integer amount of temporal granularity.
+ * If no granularity has been specified, then this method returns the given duration unmodified.
+ */
+ private Duration round(Duration duration) {
+ if (duration != null && !duration.isZero() && granularity != 0) {
+ long t = duration.getSeconds();
+ long n = t / granularity;
+ long r = t % granularity;
+ if (r != 0) {
+ t -= r;
+ if (t == 0 || r >= (granularity >> 1)) {
+ t += granularity;
+ };
+ duration = Duration.ofSeconds(t);
+ }
+ }
+ return duration;
+ }
+
+ /**
* Computes a pseudo-distance between the center of given area and of {@link #areaOfInterest}.
* This is <strong>not</strong> a real distance, neither great circle distance or rhumb line.
* May be {@link Double#NaN} if information is unknown.
@@ -274,10 +346,16 @@ public final class ExtentSelector<T> {
/**
* Computes the amount of time outside the time of interest (TOI). The returned value is always positive
* because {@code intersection} should always be less than {@code endTime} − {@code startTime} duration.
+ * Value is rounded by {@link #round(Duration)}.
+ *
+ * @param startTime start time of of the candidate object, or {@code null} if none (unbounded).
+ * @param endTime end time of the candidate object, or {@code null} if none (unbounded).
+ * @param intersection duration of the intersection of [{@code startTime} … {@code endTime}]
+ * with [{@link #minTOI} … {@link #maxTOI}].
*/
- private static Duration overtime(final Instant startTime, final Instant endTime, final Duration intersection) {
+ private Duration overtime(final Instant startTime, final Instant endTime, final Duration intersection) {
return (startTime != null && endTime != null && intersection != null)
- ? Duration.between(startTime, endTime).minus(intersection) : null;
+ ? round(Duration.between(startTime, endTime).minus(intersection)) : null;
}
/**
@@ -349,8 +427,9 @@ public final class ExtentSelector<T> {
* // Compute and test criteria.
* }
*/
+ final Duration durationRounded = round(duration);
int comparison, remainingFieldsToCompute = OVERTIME;
- if (best != null && (comparison = compare(duration, longestTime, -1)) <= 0) {
+ if (best != null && (comparison = compare(durationRounded, longestTime, -1)) <= 0) {
if (comparison != 0) return;
/*
* Criterion #2: select the object having smallest amount of time outside Time Of Interest (TOI).
@@ -362,10 +441,11 @@ public final class ExtentSelector<T> {
if (comparison != 0) return;
/*
* Criterion #3: select the object having median time closest to TOI median time.
+ * This condition is skipped in the "alternate condition ordering" mode.
*/
remainingFieldsToCompute = OUTSIDE_AREA;
final double td = temporalDistance(startTime, endTime);
- if ((comparison = compare(td, temporalDistance, +1)) >= 0) {
+ if (alternateOrdering || (comparison = compare(td, temporalDistance, +1)) >= 0) {
if (comparison != 0) return;
/*
* Criterion #4: select the object covering largest geographic area.
@@ -383,22 +463,27 @@ public final class ExtentSelector<T> {
/*
* Criterion #5: select the object having center closest to AOI center.
* Distances are computed with inexact formulas (not a real distance).
+ * TOI is also compared here in "alternate condition ordering" mode.
*/
remainingFieldsToCompute = NONE;
final double pd = pseudoDistance(bbox);
if (compare(pd, pseudoDistance, +1) >= 0) {
return;
}
+ if (alternateOrdering && compare(td, temporalDistance, +1) >= 0) {
+ return;
+ }
pseudoDistance = pd;
}
outsideArea = out;
}
+ // largestArea = area; assigned below because was computed early.
}
temporalDistance = td;
}
overtime = et;
}
- longestTime = duration;
+ longestTime = durationRounded;
largestArea = area;
switch (remainingFieldsToCompute) { // Intentional fallthrough in every cases.
case OVERTIME: overtime = overtime(startTime, endTime, duration);
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/ExtentSelectorTest.java b/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/ExtentSelectorTest.java
index 219094f..6d54eaa 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/ExtentSelectorTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/ExtentSelectorTest.java
@@ -17,6 +17,7 @@
package org.apache.sis.internal.referencing;
import java.util.Date;
+import java.time.Duration;
import org.opengis.metadata.extent.Extent;
import org.apache.sis.metadata.iso.extent.DefaultGeographicBoundingBox;
import org.apache.sis.metadata.iso.extent.DefaultTemporalExtent;
@@ -32,12 +33,24 @@ import static org.junit.Assert.*;
* Tests {@link ExtentSelector}.
*
* @author Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.2
* @since 1.1
* @module
*/
public final strictfp class ExtentSelectorTest extends TestCase {
/**
+ * Whether to test an alternate ordering where distance to TOI is tested last.
+ *
+ * @see ExtentSelector#alternateOrdering
+ */
+ private boolean alternateOrdering;
+
+ /**
+ * The temporal granularity of the Time Of Interest (TOI), or {@code null} if none.
+ */
+ private Duration granularity;
+
+ /**
* Tests the selector when intersection with AOI is a sufficient criterion.
*/
@Test
@@ -76,10 +89,37 @@ public final strictfp class ExtentSelectorTest extends TestCase {
@Test
@Ignore("Require temporal module, not yet available in SIS.")
public void testTemporal() {
- assertBestEquals(time(1000, 2000), 2,
- time(1500, 3000),
- time(1300, 1800), // Same duration than above, but better centered.
- time(1400, 1600)); // Well centered but intersection is small.
+ assertBestEquals(time(1000, 2000, true), 2,
+ time(1500, 2000, true),
+ time(1300, 1800, false), // Same duration than above, but better centered.
+ time(1400, 1600, true)); // Well centered but intersection is small.
+ }
+
+ /**
+ * Tests using temporal ranges with {@link ExtentSelector#alternateOrdering} set to {@code true}.
+ * The criterion should give precedence to larger geographic area instead of better centered.
+ */
+ @Test
+ @Ignore("Require temporal module, not yet available in SIS.")
+ public void testAlternateOrdering() {
+ alternateOrdering = true;
+ assertBestEquals(time(1000, 2000, true), 1,
+ time(1500, 2000, true), // Not well centered by has larger geographic area.
+ time(1300, 1800, false), // Better centered but smaller geographic area.
+ time(1400, 1600, true));
+ }
+
+ /**
+ * Tests using temporal ranges with {@link ExtentSelector#setTimeGranularity(Duration)} defined.
+ */
+ @Test
+ @Ignore("Require temporal module, not yet available in SIS.")
+ public void testTimeGranularity() {
+ granularity = Duration.ofSeconds(20);
+ assertBestEquals(time(10000, 70000, true), 3,
+ time(14000, 47000, false), // 2 units of temporal resolution.
+ time(15000, 46000, true), // Same size if counted in units of temporal resolution.
+ time(25000, 55000, true)); // Same size in units of resolution, but better centered.
}
/**
@@ -98,11 +138,30 @@ public final strictfp class ExtentSelectorTest extends TestCase {
/**
* Creates an extent for a temporal range having the given boundaries.
+ * A geographic extent is also added, but may be ignored because temporal extent has precedence.
+ *
+ * @param startTime arbitrary start time in milliseconds.
+ * @param endTime arbitrary end time in milliseconds.
+ * @param largeArea {@code false} for associating a small geographic area,
+ * {@code true} for associating a larger geographic area.
*/
- private static Extent time(final long startTime, final long endTime) {
+ private static Extent time(final long startTime, final long endTime, final boolean largeArea) {
+ final DefaultGeographicBoundingBox bbox = new DefaultGeographicBoundingBox(
+ largeArea ? -20 : -10, 10,
+ largeArea ? 10 : 20, 30);
final DefaultTemporalExtent range = new DefaultTemporalExtent();
range.setBounds(new Date(startTime), new Date(endTime));
- return new DefaultExtent(null, null, null, range);
+ return new DefaultExtent(null, bbox, null, range);
+ }
+
+ /**
+ * Creates the selector to use for testing purpose.
+ */
+ private ExtentSelector<Integer> create(final Extent aoi) {
+ ExtentSelector<Integer> selector = new ExtentSelector<>(aoi);
+ selector.alternateOrdering = alternateOrdering;
+ selector.setTimeGranularity(granularity);
+ return selector;
}
/**
@@ -111,34 +170,34 @@ public final strictfp class ExtentSelectorTest extends TestCase {
* @param aoi area of interest to give to {@link ExtentSelector} constructor.
* @param expected expected best result: 1 for <var>a</var>, 2 for <var>b</var> or 3 for <var>c</var>.
*/
- private static void assertBestEquals(final Extent aoi, final Integer expected,
- final Extent a, final Extent b, final Extent c)
+ private void assertBestEquals(final Extent aoi, final Integer expected,
+ final Extent a, final Extent b, final Extent c)
{
- ExtentSelector<Integer> selector = new ExtentSelector<>(aoi);
+ ExtentSelector<Integer> selector = create(aoi);
selector.evaluate(a, 1);
selector.evaluate(b, 2);
selector.evaluate(c, 3);
assertEquals(expected, selector.best());
- selector = new ExtentSelector<>(aoi);
+ selector = create(aoi);
selector.evaluate(b, 2);
selector.evaluate(c, 3);
selector.evaluate(a, 1);
assertEquals(expected, selector.best());
- selector = new ExtentSelector<>(aoi);
+ selector = create(aoi);
selector.evaluate(c, 3);
selector.evaluate(a, 1);
selector.evaluate(b, 2);
assertEquals(expected, selector.best());
- selector = new ExtentSelector<>(aoi);
+ selector = create(aoi);
selector.evaluate(a, 1);
selector.evaluate(c, 3);
selector.evaluate(b, 2);
assertEquals(expected, selector.best());
- selector = new ExtentSelector<>(aoi);
+ selector = create(aoi);
selector.evaluate(b, 2);
selector.evaluate(a, 1);
selector.evaluate(c, 3);