sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject [sis] branch geoapi-4.0 updated: Most accurate selection of best grid. In addition of maximal intersection area, try also to minimize area outside the Area Of Interest (AOI) to to prefer grids more centered on the AOI.
Date Tue, 01 Dec 2020 17:40:21 GMT
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 ec235ee  Most accurate selection of best grid. In addition of maximal intersection
area, try also to minimize area outside the Area Of Interest (AOI) to to prefer grids more
centered on the AOI.
ec235ee is described below

commit ec235eebe0a9fd675262e463a29a9cc496c3ea15
Author: Martin Desruisseaux <martin.desruisseaux@geomatys.com>
AuthorDate: Tue Dec 1 16:10:22 2020 +0100

    Most accurate selection of best grid. In addition of maximal intersection area,
    try also to minimize area outside the Area Of Interest (AOI) to to prefer grids
    more centered on the AOI.
---
 .../sis/internal/jaxb/gco/GO_CharacterString.java  |   2 +-
 .../apache/sis/metadata/PropertyComparator.java    |   4 +-
 .../sis/internal/referencing/ExtentSelector.java   | 150 ++++++++++++++++++---
 .../sis/referencing/crs/DefaultCompoundCRS.java    |   2 +-
 .../referencing/datum/DefaultGeodeticDatum.java    |   2 +-
 .../internal/referencing/ExtentSelectorTest.java   | 123 +++++++++++++++++
 .../sis/test/suite/ReferencingTestSuite.java       |   1 +
 .../java/org/apache/sis/internal/util/Strings.java |   2 +-
 .../org/apache/sis/util/logging/LoggerAdapter.java |   2 +-
 .../org/apache/sis/internal/netcdf/Variable.java   |   2 +-
 .../sis/internal/sql/feature/StreamWrapper.java    |   2 +-
 11 files changed, 266 insertions(+), 26 deletions(-)

diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/gco/GO_CharacterString.java
b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/gco/GO_CharacterString.java
index 88759a8..edddefb 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/gco/GO_CharacterString.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/gco/GO_CharacterString.java
@@ -177,7 +177,7 @@ public class GO_CharacterString {
             if (text != null && !value.equals(text)) {
                 /*
                  * The given value overwrite a previous one. Determine which value will be
discarded
-                 * using the 'type' value as a criterion, then emit a warning.
+                 * using the `type` value as a criterion, then emit a warning.
                  */
                 byte discarded = type;
                 boolean noset = false;
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/PropertyComparator.java
b/core/sis-metadata/src/main/java/org/apache/sis/metadata/PropertyComparator.java
index 5440ba4..d7f80ad 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/PropertyComparator.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/PropertyComparator.java
@@ -28,10 +28,10 @@ import org.opengis.annotation.Obligation;
 
 /**
  * The comparator for sorting the properties in a metadata object.
- * Since the comparator uses (among other criterion) the property names, this class
+ * Since the comparator uses (among other criteria) the property names, this class
  * incidentally defines static methods for inferring those names from the methods.
  *
- * <p>This comparator uses the following criterion, in priority order:</p>
+ * <p>This comparator uses the following criteria, in priority order:</p>
  * <ol>
  *   <li>Deprecated properties are last.</li>
  *   <li>If the property order is specified by a {@link XmlType} annotation,
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 59b1a8f..3dd3652 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
@@ -20,16 +20,46 @@ import org.opengis.metadata.extent.Extent;
 import org.opengis.metadata.extent.GeographicBoundingBox;
 import org.apache.sis.metadata.iso.extent.Extents;
 
+import static java.lang.Double.isNaN;
+
 
 /**
  * Selects an object in a sequence of objects using their extent as a criterion.
- * Current implementation uses only the geographic extent.
- * This may be extended to other kind of extent in any future SIS version.
+ * The selection is based on the geographic area using the following rules:
+ *
+ * <ol>
+ *   <li>Largest intersection with the {@linkplain #areaOfInterest area of interest}
(AOI) is selected.</li>
+ *   <li>If two or more candidates have the same intersection area with AOI, then the
one with the less
+ *       "irrelevant" material is selected. "Irrelevant" material are area outside the AOI.</li>
+ *   <li>If two or more candidates are considered equal after above criteria,
+ *       the one best centered on the AOI is selected.</li>
+ *   <li>If two or more candidates are considered equal after above criteria,
+ *       then the first of those candidates is selected.</li>
+ * </ol>
+ *
+ * <div class="note"><b>Rational:</b>
+ * the "minimize area outside" criterion (rule 2) is before "best centered" criterion (rule
3)
+ * for consistency with criteria applied on the temporal axis. If "geographic area" is replaced
+ * by "time range", we could have the following scenario: a user specified a "time of interest"
+ * (TOI) of 1 day. By coincidence a raster containing monthly averages has a median time
closer
+ * to TOI center than raster containing daily averages. If rules 2 and 3 were interchanged,
the
+ * monthly averages would be selected. By checking time outside TOI first, the daily data
is
+ * returned instead.</div>
+ *
+ * Usage:
+ *
+ * {@preformat java
+ *     ExtentSelector<Foo> selector = new ExtentSelector<>(areaOfInterest);
+ *     for (Foo candidate : candidates) {
+ *         selector.evaluate(candidate.extent, candidate),
+ *     }
+ *     Foo best = selector.best();
+ * }
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @version 1.1
  *
- * @param <T>  the type of object to be selected.
+ * @param  <T>  the type of object to be selected.
  *
  * @since 0.4
  * @module
@@ -38,6 +68,8 @@ public final class ExtentSelector<T> {
     /**
      * The area of interest, or {@code null} if none.
      * This is specified at construction time, but can be modified later.
+     *
+     * @see #setAreaOfInterest(GeographicBoundingBox, Extent)
      */
     private GeographicBoundingBox areaOfInterest;
 
@@ -50,10 +82,33 @@ public final class ExtentSelector<T> {
      * The area covered by the {@linkplain #best} object (m²). The initial value is zero,
      * which imply that only intersection areas greater than zero will be accepted.
      * This is the desired behavior in order to filter out empty intersections.
+     *
+     * <p>This is the first criterion cited in class javadoc.</p>
      */
     private double largestArea;
 
     /**
+     * Area of {@linkplain #best} object which is outside the area of interest.
+     * This is used as a discriminatory criterion only when {@link #largestArea}
+     * has the same value for two or more objects.
+     *
+     * <p>This is the second criterion cited in class javadoc.</p>
+     */
+    private double outsideArea;
+
+    /**
+     * A pseudo-distance from {@linkplain #best} object center to {@link #areaOfInterest}
center.
+     * This is <strong>not</strong> a real distance, neither great circle distance
or rhumb line.
+     * The only requirements are: a value equals to zero when the two centers are coincident
and
+     * increasing when the centers are mowing away.
+     *
+     * <p>This value is used as a discriminatory criterion only when {@link #largestArea}
+     * and {@link #outsideArea} have the same values for two or more objects.
+     * This is the third criterion cited in class javadoc.</p>
+     */
+    private double pseudoDistance;
+
+    /**
      * Creates a selector for the given area of interest.
      *
      * @param areaOfInterest  the area of interest, or {@code null} if none.
@@ -73,6 +128,9 @@ public final class ExtentSelector<T> {
 
     /**
      * Sets the area of interest to the intersection of the two given arguments.
+     * This method should be invoked only if {@link #best()} returned {@code null}.
+     * It allows to make a second search with a different AOI when the search using
+     * previous AOI gave no result.
      *
      * @param  a1  first area of interest as a bounding box, or {@code null}.
      * @param  a2  second area of interest as an extent, or {@code null}.
@@ -82,31 +140,89 @@ public final class ExtentSelector<T> {
     }
 
     /**
-     * Evaluates the given extent against the criteria represented by the {@code ExtentSelector}.
+     * 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.
+     *
+     * @see #pseudoDistance
+     */
+    private double pseudoDistance(final GeographicBoundingBox area) {
+        if (areaOfInterest == null || area == null) {
+            return Double.NaN;
+        }
+        /*
+         * Following calculation omits division by 2 and square root because we do not need
a real distance;
+         * we only need increasing values as `area` center moves away from `areaOfInterest`
center. Note that
+         * even if above operations were applied, it still NOT a valid great circle or rhumb
line distance.
+         * A cheap calculation is sufficient here.
+         */
+        final double cφ = areaOfInterest.getNorthBoundLatitude()
+                        + areaOfInterest.getSouthBoundLatitude();
+        final double dφ = (area.getNorthBoundLatitude() + area.getSouthBoundLatitude())
- cφ;
+        final double dλ = (area.getEastBoundLongitude() - areaOfInterest.getEastBoundLongitude())
+                        + (area.getWestBoundLongitude() - areaOfInterest.getWestBoundLongitude())
+                        * Math.cos(cφ * (Math.PI/180 / 2));
+        return dφ*dφ + dλ*dλ;
+    }
+
+    /**
+     * Evaluates the given extent against the criteria represented by this {@code ExtentSelector}.
      * If the intersection between the given extent and the area of interest is greater than
any
-     * previous intersection, then the given extent and object are remembered as the best
match
-     * found so far.
+     * previous intersection, then the given object is remembered as the best match found
so far.
+     * Otherwise other criteria documented in class javadoc are applied.
      *
      * @param  extent  the extent to evaluate, or {@code null} if none.
      * @param  object  an optional user object associated to the given extent.
-     * @return {@code true} if the given extent is a better match than any previous extents
given to this method.
      */
-    public boolean evaluate(final Extent extent, final T object) {
-        final double area = Extents.area(Extents.intersection(Extents.getGeographicBoundingBox(extent),
areaOfInterest));
-        if (best != null && !(area > largestArea)) {    // Use '!' for catching
NaN.
+    public void evaluate(final Extent extent, final T object) {
+        final GeographicBoundingBox bbox = Extents.getGeographicBoundingBox(extent);
+        final GeographicBoundingBox intersection = Extents.intersection(bbox, areaOfInterest);
+        final double area = Extents.area(intersection);
+        /*
+         * Accept the given object if it is the first one (`best = null`), or if it covers
a larger area than
+         * previous object, or if the previous object had no extent information at all (`largestArea`
is NaN)
+         * while the new object has a valid extent.
+         */
+        // Use `!(…)` form for accepting NaN in `area > largestArea`.
+        if (!(best == null || area > largestArea || (isNaN(largestArea) && !isNaN(area))))
{
+            if (notEquals(area, largestArea)) {
+                return;
+            }
             /*
-             * At this point, the given extent is not greater than the previous one.
-             * However if the previous object had no extent information at all (i.e.
-             * 'largestArea' is NaN) while the new object has a valid extent, then
-             * the new object will have precedence.
+             * If the two extents have the same area, second criterion is to select the object
having
+             * smallest amount of area outside the AOI, with same NaN handling than for intersection.
+             * If still equal, third and last criterion is to select the object closest to
center
+             * (determined in an approximated way).
              */
-            if (Double.isNaN(area) || !Double.isNaN(largestArea)) {
-                return false;
+            final double out = Extents.area(bbox) - area;
+            if (!(out < outsideArea || (isNaN(outsideArea) && !isNaN(out)))) {
+                if (notEquals(out, outsideArea)) {
+                    return;
+                }
+                final double pd = pseudoDistance(intersection);
+                if (!(pd < pseudoDistance)) {
+                    return;
+                }
+                pseudoDistance = pd;
+            } else {
+                pseudoDistance = pseudoDistance(intersection);
             }
+            outsideArea = out;
+        } else {
+            pseudoDistance = pseudoDistance(intersection);
+            outsideArea    = Extents.area(bbox) - area;
         }
         largestArea = area;
         best = object;
-        return true;
+    }
+
+    /**
+     * Returns {@code true} if the given values are not equals.
+     * {@link Double#NaN} values are compared equal to other NaN values.
+     * Sign of positive/negative zero is ignored.
+     */
+    private static boolean notEquals(final double a, final double b) {
+        return (a != b) && !(isNaN(a) && isNaN(b));
     }
 
     /**
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultCompoundCRS.java
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultCompoundCRS.java
index 2b57f12..b97c150 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultCompoundCRS.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultCompoundCRS.java
@@ -417,7 +417,7 @@ public class DefaultCompoundCRS extends AbstractCRS implements CompoundCRS
{
      *   <li>Optionally followed by a {@code TemporalCRS}.</li>
      * </ul>
      *
-     * This method verifies the above criterion with the following flexibilities:
+     * This method verifies the above criteria with the following flexibilities:
      *
      * <ul>
      *   <li>Accepts three-dimensional {@code GeodeticCRS} followed by a {@code TemporalCRS}.</li>
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultGeodeticDatum.java
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultGeodeticDatum.java
index 4d72af4..895b54f 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultGeodeticDatum.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultGeodeticDatum.java
@@ -387,7 +387,7 @@ public class DefaultGeodeticDatum extends AbstractDatum implements GeodeticDatum
      * in EPSG dataset version 8.9, all datum shifts that can be represented by this method
use Greenwich as the
      * prime meridian, both in source and target datum.</div>
      *
-     * <h4>Search criterion</h4>
+     * <h4>Search criteria</h4>
      * If the given {@code areaOfInterest} is non-null and contains at least one geographic
bounding box, then this
      * method ignores any Bursa-Wolf parameters having a {@linkplain BursaWolfParameters#getDomainOfValidity()
domain
      * of validity} that does not intersect the given geographic extent.
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
new file mode 100644
index 0000000..387b6e7
--- /dev/null
+++ b/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/ExtentSelectorTest.java
@@ -0,0 +1,123 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.internal.referencing;
+
+import org.opengis.metadata.extent.Extent;
+import org.apache.sis.metadata.iso.extent.DefaultGeographicBoundingBox;
+import org.apache.sis.metadata.iso.extent.DefaultExtent;
+import org.apache.sis.test.TestCase;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+
+/**
+ * Tests {@link ExtentSelector}.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.1
+ * @since   1.1
+ * @module
+ */
+public final strictfp class ExtentSelectorTest extends TestCase {
+    /**
+     * Tests the selector when intersection with AOI is a sufficient criterion.
+     */
+    @Test
+    public void testInsideAreaCriterion() {
+        assertBestEquals(extent(-20,  30, -10, 5), 2,
+                         extent(-30, -17,  -5, 3),
+                         extent(-32, -16,  -5, 3),      // Largest area inside.
+                         extent( 28,  32,  -5, 3));
+    }
+
+    /**
+     * Tests the selector where the "area outside" criterion must be used.
+     */
+    @Test
+    public void testOutsideAreaCriterion() {
+        assertBestEquals(extent(-20,  30, -10, 5), 2,
+                         extent(-25, -15,  -5, 3),
+                         extent(-24, -15,  -5, 3),      // Same area inside, smaller area
outside.
+                         extent(-21, -16,  -5, 3));     // Smallest area outside, but smaller
area inside too.
+    }
+
+    /**
+     * Tests the selector where the "closer to AOI center" criterion must be used.
+     */
+    @Test
+    public void testCentreCriterion() {
+        assertBestEquals(extent(-20,  30, -10, 5), 3,
+                         extent(-20, -10,  -5, 3),
+                         extent(-15,  -5,  -5, 3),      // Closer to centre.
+                         extent(  0,  10,  -5, 3));     // Yet closer to centre.
+    }
+
+    /**
+     * Creates an extent for a geographic bounding box having the given boundaries.
+     */
+    private static Extent extent(final double westBoundLongitude,
+                                 final double eastBoundLongitude,
+                                 final double southBoundLatitude,
+                                 final double northBoundLatitude)
+    {
+        return new DefaultExtent(null, new DefaultGeographicBoundingBox(
+                westBoundLongitude, eastBoundLongitude,
+                southBoundLatitude, northBoundLatitude),
+                null, null);
+    }
+
+    /**
+     * Tests evaluating the <var>a</var>, <var>b</var> and <var>c</var>
elements in various order.
+     *
+     * @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)
+    {
+        ExtentSelector<Integer> selector = new ExtentSelector<>(aoi);
+        selector.evaluate(a, 1);
+        selector.evaluate(b, 2);
+        selector.evaluate(c, 3);
+        assertEquals(expected, selector.best());
+
+        selector = new ExtentSelector<>(aoi);
+        selector.evaluate(b, 2);
+        selector.evaluate(c, 3);
+        selector.evaluate(a, 1);
+        assertEquals(expected, selector.best());
+
+        selector = new ExtentSelector<>(aoi);
+        selector.evaluate(c, 3);
+        selector.evaluate(a, 1);
+        selector.evaluate(b, 2);
+        assertEquals(expected, selector.best());
+
+        selector = new ExtentSelector<>(aoi);
+        selector.evaluate(a, 1);
+        selector.evaluate(c, 3);
+        selector.evaluate(b, 2);
+        assertEquals(expected, selector.best());
+
+        selector = new ExtentSelector<>(aoi);
+        selector.evaluate(b, 2);
+        selector.evaluate(a, 1);
+        selector.evaluate(c, 3);
+        assertEquals(expected, selector.best());
+    }
+}
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/test/suite/ReferencingTestSuite.java
b/core/sis-referencing/src/test/java/org/apache/sis/test/suite/ReferencingTestSuite.java
index 074d59f..cd66eb8 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/test/suite/ReferencingTestSuite.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/test/suite/ReferencingTestSuite.java
@@ -38,6 +38,7 @@ import org.junit.BeforeClass;
     org.apache.sis.internal.referencing.PositionalAccuracyConstantTest.class,
     org.apache.sis.internal.referencing.ReferencingUtilitiesTest.class,
     org.apache.sis.internal.referencing.WraparoundAdjustmentTest.class,
+    org.apache.sis.internal.referencing.ExtentSelectorTest.class,
     org.apache.sis.internal.referencing.WKTKeywordsTest.class,
     org.apache.sis.internal.referencing.WKTUtilitiesTest.class,
     org.apache.sis.internal.jaxb.referencing.CodeTest.class,
diff --git a/core/sis-utility/src/main/java/org/apache/sis/internal/util/Strings.java b/core/sis-utility/src/main/java/org/apache/sis/internal/util/Strings.java
index 94d8c5d..925a528 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/internal/util/Strings.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/internal/util/Strings.java
@@ -354,7 +354,7 @@ public final class Strings extends Static {
             for (int i=0,n=0; i<length; i += n) {
                 if (--precision < 0) {
                     /*
-                     * Found the amount of characters to keep. The 'n' variable can be
+                     * Found the amount of characters to keep. The `n` variable can be
                      * zero only if precision == 0, in which case the string is empty.
                      */
                     if (n == 0) {
diff --git a/core/sis-utility/src/main/java/org/apache/sis/util/logging/LoggerAdapter.java
b/core/sis-utility/src/main/java/org/apache/sis/util/logging/LoggerAdapter.java
index bd6359e..0ccb0f2 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/util/logging/LoggerAdapter.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/util/logging/LoggerAdapter.java
@@ -365,7 +365,7 @@ public abstract class LoggerAdapter extends Logger {
          * The filter should always be null since we overrode the 'setFilter' method as a
no-op.
          * But we check it anyway as matter of principle just in case some subclass overrides
the
          * 'getFilter()' method. This is the only method where we can do this check cheaply.
Note
-         * that this is NOT the check for logging level; Filters are for user-specified criterions.
+         * that this is NOT the check for logging level; Filters are for user-specified criteria.
          */
         final Filter filter = getFilter();
         if (filter != null && !filter.isLoggable(record)) {
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Variable.java
b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Variable.java
index 133bf00..29461cf 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Variable.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Variable.java
@@ -618,7 +618,7 @@ public abstract class Variable extends Node {
         /*
          * At this point we finished collecting all dimensions to use in the grid. Search
a grid containing
          * those dimensions in the same order (the order is enforced by Grid.forDimensions(…)
method call).
-         * If we find a grid meting all criterion, we return it immediately. Otherwise select
a fallback in
+         * If we find a grid meting all criteria, we return it immediately. Otherwise select
a fallback in
          * the following precedence order:
          *
          *   1) grid having all axes requested by the customized convention (usually there
is none).
diff --git a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/StreamWrapper.java
b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/StreamWrapper.java
index 6836770..77a636d 100644
--- a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/StreamWrapper.java
+++ b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/StreamWrapper.java
@@ -49,7 +49,7 @@ import java.util.stream.Stream;
  *
  * Another example would be intermediate operations. Let's keep SQL queries as example. If
a stream is executing
  * a statement on terminal operations, a subclasses can override {@link #limit(long)} and
{@link #skip(long)}
- * methods to set LIMIT and OFFSET criterion in an SQL query.</div>
+ * methods to set LIMIT and OFFSET criteria in an SQL query.</div>
  *
  * For an advanced example, see the {@link org.apache.sis.internal.sql.feature.StreamSQL}
class.
  *


Mime
View raw message