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 6cffa03c4c Add the SRID argument when executing a spatial function
`ST_*` in a spatial database. The reprojection of geometry literal, if needed,
was already handled by `Optimization`. Add special case where the geometry
column declares the CRS but the literal does not. Redirect to data store
listeners the logs that may occur during geometry reprojection.
6cffa03c4c is described below
commit 6cffa03c4cf09898aa66ef2dc573b9a5af20e8de
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Wed Mar 19 17:33:44 2025 +0100
Add the SRID argument when executing a spatial function `ST_*` in a spatial
database.
The reprojection of geometry literal, if needed, was already handled by
`Optimization`.
Add special case where the geometry column declares the CRS but the literal
does not.
Redirect to data store listeners the logs that may occur during geometry
reprojection.
---
.../org/apache/sis/feature/internal/Resources.java | 5 +
.../sis/feature/internal/Resources.properties | 1 +
.../sis/feature/internal/Resources_fr.properties | 1 +
.../apache/sis/filter/BinaryGeometryFilter.java | 2 +-
.../main/org/apache/sis/filter/LeafExpression.java | 2 +-
.../main/org/apache/sis/filter/internal/Node.java | 24 +-
.../org/apache/sis/filter/privy/WarningEvent.java | 128 +++++++++++
.../geometry/wrapper/jts/GeometryTransform.java | 10 +-
.../org/apache/sis/geometry/wrapper/jts/JTS.java | 20 +-
.../sis/geometry/wrapper/esri/FactoryTest.java | 8 +-
.../apache/sis/geometry/wrapper/jts/JTSTest.java | 2 +-
.../apache/sis/metadata/sql/privy/SQLBuilder.java | 57 +++--
.../org/apache/sis/metadata/sql/privy/Syntax.java | 6 +-
.../main/org/apache/sis/openoffice/CalcAddins.java | 2 +-
.../sis/storage/netcdf/base/GridMapping.java | 2 +-
.../org/apache/sis/storage/sql/DataAccess.java | 2 +
.../sis/storage/sql/feature/FeatureIterator.java | 26 ++-
.../sis/storage/sql/feature/FeatureStream.java | 41 ++--
.../sis/storage/sql/feature/InfoStatements.java | 4 +-
.../apache/sis/storage/sql/feature/Resources.java | 5 +
.../sis/storage/sql/feature/Resources.properties | 1 +
.../storage/sql/feature/Resources_fr.properties | 1 +
.../sis/storage/sql/feature/SelectionClause.java | 250 +++++++++++++++++++--
.../storage/sql/feature/SelectionClauseWriter.java | 30 ++-
.../org/apache/sis/storage/sql/feature/Table.java | 2 +-
.../storage/sql/postgis/ExtendedClauseWriter.java | 5 +
.../sis/storage/sql/postgis/ExtentEstimator.java | 2 +-
.../storage/sql/feature/GeometryGetterTest.java | 1 +
.../sis/storage/sql/postgis/PostgresTest.java | 98 ++++++--
.../sis/storage/sql/postgis/SpatialFeatures.sql | 8 +
.../org/apache/sis/storage/wkt/StoreFormat.java | 2 +-
.../main/org/apache/sis/setup/GeometryLibrary.java | 37 ++-
.../org/apache/sis/util/collection/WeakEntry.java | 2 +-
.../apache/sis/storage/geoheif/GeoHeifStore.java | 2 +-
34 files changed, 664 insertions(+), 125 deletions(-)
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/internal/Resources.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/internal/Resources.java
index 3bdf50aeb5..c3ad59f00a 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/internal/Resources.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/internal/Resources.java
@@ -336,6 +336,11 @@ public class Resources extends IndexedResourceBundle {
*/
public static final short MismatchedValueClass_3 = 48;
+ /**
+ * Mixed geometry implementations from two libraries: {0} and {1}.
+ */
+ public static final short MixedGeometryImplementation_2 = 91;
+
/**
* No category for value {0}.
*/
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/internal/Resources.properties
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/internal/Resources.properties
index df2a468bba..1aeaa4f3bf 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/internal/Resources.properties
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/internal/Resources.properties
@@ -74,6 +74,7 @@ MismatchedPropertyType_1 = Mismatched type for
\u201c{0}\u201d property
MismatchedSampleModel = The two images use different sample models.
MismatchedTileGrid = The two images have different tile grid.
MismatchedValueClass_3 = An attribute for \u2018{1}\u2019 values
where expected, but the \u201c{0}\u201d attribute specifies values of type
\u2018{2}\u2019.
+MixedGeometryImplementation_2 = Mixed geometry implementations from two
libraries: {0} and {1}.
NoCategoryForValue_1 = No category for value {0}.
NoNDimensionalSlice_3 = Cannot infer a {0}-dimensional slice from
the grid envelope. Dimension {1} has {2,number} cells.
NonLinearInDimensions_1 = non-linear in {0}
dimension{0,choice,1#|2#s}:
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/internal/Resources_fr.properties
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/internal/Resources_fr.properties
index 4dfb9f41af..b2b4f45237 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/internal/Resources_fr.properties
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/internal/Resources_fr.properties
@@ -79,6 +79,7 @@ MismatchedPropertyType_1 = Le type de la
propri\u00e9t\u00e9 \u00ab\u20
MismatchedSampleModel = Les deux images disposent les pixels
diff\u00e9remment.
MismatchedTileGrid = Les deux images utilisent des grilles de
tuiles diff\u00e9rentes.
MismatchedValueClass_3 = Un attribut pour des valeurs de type
\u2018{1}\u2019 \u00e9tait attendu, mais l\u2019attribut
\u00ab\u202f{0}\u202f\u00bb sp\u00e9cifie des valeurs de type \u2018{2}\u2019.
+MixedGeometryImplementation_2 = Les g\u00e9om\u00e9tries m\u00e9langent
des impl\u00e9mentations de deux biblioth\u00e8ques: {0} et {1}.
NoCategoryForValue_1 = Aucune cat\u00e9gorie n\u2019est
d\u00e9finie pour la valeur {0}.
NoNDimensionalSlice_3 = Ne peut pas inf\u00e9rer une tranche
\u00e0 {0} dimensions \u00e0 partir de l\u2019enveloppe de la grille. La
dimension {1} a {2,number} cellules.
NonLinearInDimensions_1 = non-lin\u00e9aire dans {0}
dimension{0,choice,1#|2#s}\u00a0:
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/BinaryGeometryFilter.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/BinaryGeometryFilter.java
index f35237d4d7..685c3ea900 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/BinaryGeometryFilter.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/BinaryGeometryFilter.java
@@ -120,7 +120,7 @@ abstract class BinaryGeometryFilter<R> extends Node
implements SpatialOperator<R
if (value != null) {
final GeometryWrapper gt = context.transform(value);
if (gt != value) {
- final Expression<R, GeometryWrapper> tr = new
LeafExpression.Transformed<>(gt, literal);
+ final var tr = new LeafExpression.Transformed<R,
GeometryWrapper>(gt, literal);
switch (index) {
case 0: expression1 = tr; break;
case 1: expression2 = tr; break;
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/LeafExpression.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/LeafExpression.java
index 6350cc0eb6..308c9adc7c 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/LeafExpression.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/LeafExpression.java
@@ -243,7 +243,7 @@ abstract class LeafExpression<R,V> extends Node implements
FeatureExpression<R,V
try {
return original.toValueType(target);
} catch (RuntimeException bis) {
- final ClassCastException c = new
ClassCastException(Errors.format(
+ final var c = new ClassCastException(Errors.format(
Errors.Keys.CanNotConvertValue_2,
getFunctionName(), target));
c.initCause(e);
c.addSuppressed(bis);
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/internal/Node.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/internal/Node.java
index 9602325c2f..8afbdfd5d0 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/internal/Node.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/internal/Node.java
@@ -24,6 +24,7 @@ import java.util.Collections;
import java.util.function.Predicate;
import java.util.logging.Logger;
import java.io.Serializable;
+import java.util.function.Consumer;
import org.opengis.util.CodeList;
import org.opengis.util.LocalName;
import org.opengis.util.ScopedName;
@@ -31,6 +32,7 @@ import org.apache.sis.math.FunctionProperty;
import org.apache.sis.feature.DefaultAttributeType;
import org.apache.sis.feature.internal.Resources;
import org.apache.sis.feature.privy.FeatureExpression;
+import org.apache.sis.filter.privy.WarningEvent;
import org.apache.sis.geometry.wrapper.Geometries;
import org.apache.sis.geometry.wrapper.GeometryWrapper;
import org.apache.sis.util.iso.Names;
@@ -144,7 +146,7 @@ public abstract class Node implements Serializable {
*
* @return the name of this function.
*/
- private Object getDisplayName() {
+ public final Object getDisplayName() {
if (this instanceof Expression<?,?>) {
return ((Expression<?,?>) this).getFunctionName();
} else if (this instanceof Filter<?>) {
@@ -181,11 +183,12 @@ public abstract class Node implements Serializable {
final Geometries<G> library, final Expression<R,?> expression)
{
if (expression instanceof GeometryConverter<?,?>) {
- if (library.equals(((GeometryConverter<?,?>) expression).library))
{
+ final Geometries<?> other = ((GeometryConverter<?,?>)
expression).library;
+ if (library.equals(other)) {
return (GeometryConverter<R,G>) expression;
- } else {
- throw new InvalidFilterValueException(); // TODO:
provide a message.
}
+ throw new InvalidFilterValueException(Resources.format(
+ Resources.Keys.MixedGeometryImplementation_2,
library.library, other.library));
}
return new GeometryConverter<>(library, expression);
}
@@ -389,11 +392,16 @@ public abstract class Node implements Serializable {
* @see <a href="https://issues.apache.org/jira/browse/SIS-460">SIS-460</a>
*/
protected final void warning(final Exception e, final boolean recoverable)
{
- final String method = (this instanceof Predicate) ? "test" : "apply";
- if (recoverable) {
- Logging.recoverableException(LOGGER, getClass(), method, e);
+ final Consumer<WarningEvent> listener = WarningEvent.LISTENER.get();
+ if (listener != null) {
+ listener.accept(new WarningEvent(this, e));
} else {
- Logging.unexpectedException(LOGGER, getClass(), method, e);
+ final String method = (this instanceof Predicate) ? "test" :
"apply";
+ if (recoverable) {
+ Logging.recoverableException(LOGGER, getClass(), method, e);
+ } else {
+ Logging.unexpectedException(LOGGER, getClass(), method, e);
+ }
}
}
}
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/privy/WarningEvent.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/privy/WarningEvent.java
new file mode 100644
index 0000000000..09a6f891d5
--- /dev/null
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/privy/WarningEvent.java
@@ -0,0 +1,128 @@
+/*
+ * 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.filter.privy;
+
+import java.util.Optional;
+import java.util.function.Consumer;
+import org.opengis.util.CodeList;
+import org.opengis.util.ScopedName;
+import org.apache.sis.filter.internal.Node;
+
+// Specific to the geoapi-3.1 and geoapi-4.0 branches:
+import org.opengis.filter.Filter;
+import org.opengis.filter.Expression;
+
+
+/**
+ * A warning emitted during operations on filters or expressions.
+ * This class is a first draft that may move to public API in a future version.
+ *
+ * @author Martin Desruisseaux (Geomatys)
+ *
+ * @see <a href="https://issues.apache.org/jira/browse/SIS-460">SIS-460</a>
+ */
+public final class WarningEvent {
+ /**
+ * Where to send the warning. If the value is {@code null},
+ * then the warning will be logged to a default logger.
+ */
+ public static final ThreadLocal<Consumer<WarningEvent>> LISTENER = new
ThreadLocal<>();
+
+ /**
+ * The filter or expression that produced this warning.
+ */
+ private final Node source;
+
+ /**
+ * The exception that occurred.
+ */
+ public final Exception exception;
+
+ /**
+ * Creates a new warning.
+ *
+ * @param source the filter or expression that produced this warning.
+ * @param exception the exception that occurred.
+ */
+ public WarningEvent(final Node source, final Exception exception) {
+ this.source = source;
+ this.exception = exception;
+ }
+
+ /**
+ * If the source is a filter, returns the operator type.
+ * Otherwise, returns an empty value.
+ *
+ * @return the operator type if the source is a filter.
+ */
+ public Optional<CodeList<?>> getOperatorType() {
+ if (source instanceof Filter<?>) {
+ return Optional.of(((Filter<?>) source).getOperatorType());
+ }
+ return Optional.empty();
+ }
+
+ /**
+ * If the source is an expression, returns the function name.
+ * Otherwise, returns an empty value.
+ *
+ * @return the function name if the source is an expression.
+ */
+ public Optional<ScopedName> getFunctionName() {
+ if (source instanceof Expression<?,?>) {
+ return Optional.of(((Expression<?,?>) source).getFunctionName());
+ }
+ return Optional.empty();
+ }
+
+ /**
+ * If the source is an expression with at least one parameter of the given
type, returns that parameter.
+ * If there is many parameter assignable to the given type, then the first
occurrence is returned.
+ * The {@code type} argument is typically {@code Literal.class} or {@code
ValueReference.class}.
+ *
+ * @param type the desired type of the parameter to return.
+ * @return the first parameter of the given type, or empty if none.
+ */
+ @SuppressWarnings("unchecked")
+ public <P extends Expression<?,?>> Optional<P> getParameter(final Class<P>
type) {
+ if (source instanceof Filter<?>) {
+ for (Expression<?,?> parameter : ((Filter<?>)
source).getExpressions()) {
+ if (type.isInstance(parameter)) {
+ return Optional.of((P) parameter);
+ }
+ }
+ }
+ if (source instanceof Expression<?,?>) {
+ for (Expression<?,?> parameter : ((Expression<?,?>)
source).getParameters()) {
+ if (type.isInstance(parameter)) {
+ return Optional.of((P) parameter);
+ }
+ }
+ }
+ return Optional.empty();
+ }
+
+ /**
+ * Returns a string representation of the warning for debugging purposes.
+ *
+ * @return a string representation of the warning.
+ */
+ @Override
+ public String toString() {
+ return source.getDisplayName() + ": " + exception.toString();
+ }
+}
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/jts/GeometryTransform.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/jts/GeometryTransform.java
index bf638535c1..2cb88090ec 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/jts/GeometryTransform.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/jts/GeometryTransform.java
@@ -104,7 +104,7 @@ public abstract class GeometryTransform {
* @throws TransformException if an error occurred while transforming a
geometry.
*/
public MultiPoint transform(final MultiPoint geom) throws
TransformException {
- final Point[] subs = new Point[geom.getNumGeometries()];
+ final var subs = new Point[geom.getNumGeometries()];
for (int i = 0; i < subs.length; i++) {
subs[i] = transform((Point) geom.getGeometryN(i));
}
@@ -133,7 +133,7 @@ public abstract class GeometryTransform {
* @throws TransformException if an error occurred while transforming a
geometry.
*/
public MultiLineString transform(final MultiLineString geom) throws
TransformException {
- final LineString[] subs = new LineString[geom.getNumGeometries()];
+ final var subs = new LineString[geom.getNumGeometries()];
for (int i = 0; i < subs.length; i++) {
subs[i] = transform((LineString) geom.getGeometryN(i));
}
@@ -163,7 +163,7 @@ public abstract class GeometryTransform {
*/
public Polygon transform(final Polygon geom) throws TransformException {
final LinearRing exterior = transform(geom.getExteriorRing());
- final LinearRing[] holes = new LinearRing[geom.getNumInteriorRing()];
+ final var holes = new LinearRing[geom.getNumInteriorRing()];
for (int i = 0; i < holes.length; i++) {
holes[i] = transform(geom.getInteriorRingN(i));
}
@@ -179,7 +179,7 @@ public abstract class GeometryTransform {
* @throws TransformException if an error occurred while transforming a
geometry.
*/
public MultiPolygon transform(final MultiPolygon geom) throws
TransformException {
- final Polygon[] subs = new Polygon[geom.getNumGeometries()];
+ final var subs = new Polygon[geom.getNumGeometries()];
for (int i = 0; i < subs.length; i++) {
subs[i] = transform((Polygon) geom.getGeometryN(i));
}
@@ -195,7 +195,7 @@ public abstract class GeometryTransform {
* @throws TransformException if an error occurred while transforming a
geometry.
*/
public GeometryCollection transform(final GeometryCollection geom) throws
TransformException {
- final Geometry[] subs = new Geometry[geom.getNumGeometries()];
+ final var subs = new Geometry[geom.getNumGeometries()];
for (int i = 0; i < subs.length; i++) {
subs[i] = transform(geom.getGeometryN(i));
}
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/jts/JTS.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/jts/JTS.java
index 36cbe002c1..7201ad9ea3 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/jts/JTS.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/jts/JTS.java
@@ -30,6 +30,8 @@ import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.TransformException;
import org.apache.sis.referencing.CRS;
import org.apache.sis.referencing.IdentifiedObjects;
+import org.apache.sis.referencing.crs.AbstractCRS;
+import org.apache.sis.referencing.cs.AxesConvention;
import org.apache.sis.util.Static;
import org.apache.sis.util.Utilities;
import org.apache.sis.util.logging.Logging;
@@ -124,28 +126,32 @@ public final class JTS extends Static {
if (userData instanceof CoordinateReferenceSystem) {
return (CoordinateReferenceSystem) userData;
} else if (userData instanceof Map<?,?>) {
- final Map<?,?> map = (Map<?,?>) userData;
+ final var map = (Map<?,?>) userData;
final Object value = map.get(CRS_KEY);
if (value instanceof CoordinateReferenceSystem) {
return (CoordinateReferenceSystem) value;
}
}
/*
- * Fallback on SRID with the assumption that they are EPSG codes.
+ * Fallback on SRID with the assumption that they are EPSG codes
except for axis order which
+ * is (longitude, latitude). This is the order used in popular
spatial databases and is also
+ * the order frequently used by other libraries that use JTS.
*
* TODO: This is not necessarily EPSG code. We need a plugin
mechanism for specifying the authority.
* It may be for example the "spatial_ref_sys" table of a spatial
database.
*/
final int srid = source.getSRID();
if (srid > 0) {
- return CRS.forCode(Constants.EPSG + ':' + srid);
+ CoordinateReferenceSystem crs = CRS.forCode(Constants.EPSG +
':' + srid);
+ crs =
AbstractCRS.castOrCopy(crs).forConvention(AxesConvention.RIGHT_HANDED);
+ return crs;
}
}
return null;
}
/**
- * Sets the Coordinate Reference System (CRS) in the specified geometry.
This method overwrite any previous
+ * Sets the Coordinate Reference System (CRS) in the specified geometry.
This method overwrites any previous
* user data; it should be invoked only when the geometry is known to not
store any other information.
* In current Apache SIS usage, this method is invoked only for newly
created geometries.
*
@@ -155,7 +161,7 @@ public final class JTS extends Static {
* @param target the geometry where to store coordinate reference system
information.
* @param crs the CRS to store, or {@code null}.
*/
- static void setCoordinateReferenceSystem(final Geometry target, final
CoordinateReferenceSystem crs) {
+ public static void setCoordinateReferenceSystem(final Geometry target,
final CoordinateReferenceSystem crs) {
target.setUserData(crs);
int epsg = 0;
final Identifier id = IdentifiedObjects.getIdentifier(crs,
Citations.EPSG);
@@ -207,7 +213,7 @@ public final class JTS extends Static {
bbox = new DefaultGeographicBoundingBox();
try {
final Envelope e = areaOfInterest.getEnvelopeInternal();
- final GeneralEnvelope env = new GeneralEnvelope(sourceCRS);
// May be 3- or 4-dimensional.
+ final var env = new GeneralEnvelope(sourceCRS); // May be
3- or 4-dimensional.
env.setRange(0, e.getMinX(), e.getMaxX());
env.setRange(1, e.getMinY(), e.getMaxY());
bbox.setBounds(env);
@@ -296,7 +302,7 @@ public final class JTS extends Static {
*/
public static Geometry transform(Geometry geometry, final MathTransform
transform) throws TransformException {
if (geometry != null && transform != null && !transform.isIdentity()) {
- final GeometryCoordinateTransform gct = new
GeometryCoordinateTransform(transform, geometry.getFactory());
+ final var gct = new GeometryCoordinateTransform(transform,
geometry.getFactory());
geometry = gct.transform(geometry);
}
return geometry;
diff --git
a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/geometry/wrapper/esri/FactoryTest.java
b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/geometry/wrapper/esri/FactoryTest.java
index eaf53fa485..dfd1c6096b 100644
---
a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/geometry/wrapper/esri/FactoryTest.java
+++
b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/geometry/wrapper/esri/FactoryTest.java
@@ -82,7 +82,7 @@ public final class FactoryTest extends GeometriesTestCase {
@Override
public void testCreatePolyline() {
super.testCreatePolyline();
- final Polyline poly = (Polyline) geometry;
+ final var poly = (Polyline) geometry;
assertEquals(2, poly.getPathCount());
}
@@ -93,7 +93,7 @@ public final class FactoryTest extends GeometriesTestCase {
@Override
public void testMergePolylines() {
super.testMergePolylines();
- final Polyline poly = (Polyline) geometry;
+ final var poly = (Polyline) geometry;
assertEquals(3, poly.getPathCount());
}
@@ -105,7 +105,7 @@ public final class FactoryTest extends GeometriesTestCase {
@Override
protected void assertWktEquals(String expected, final String actual) {
assertTrue(actual.startsWith("MULTI"));
- final StringBuilder b = new StringBuilder(expected.length() +
7).append("MULTI").append(expected);
+ final var b = new StringBuilder(expected.length() +
7).append("MULTI").append(expected);
StringBuilders.replace(b, "(", "((");
StringBuilders.replace(b, ")", "))");
expected = b.toString();
@@ -121,6 +121,6 @@ public final class FactoryTest extends GeometriesTestCase {
final GeometryWrapper ogw = other.castOrWrap(other.createPoint(5, 6));
assertNotNull(other.getGeometry(ogw));
var e = assertThrows(ClassCastException.class, () ->
factory.getGeometry(ogw));
- assertMessageContains(e, "ESRI", "JAVA2D");
+ assertMessageContains(e, "ESRI", "Java2D");
}
}
diff --git
a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/geometry/wrapper/jts/JTSTest.java
b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/geometry/wrapper/jts/JTSTest.java
index 6c488a1238..8aec3d6a9e 100644
---
a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/geometry/wrapper/jts/JTSTest.java
+++
b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/geometry/wrapper/jts/JTSTest.java
@@ -79,7 +79,7 @@ public final class JTSTest extends TestCase {
*/
geometry.setUserData(null);
geometry.setSRID(4326);
- assertEquals(CommonCRS.WGS84.geographic(),
JTS.getCoordinateReferenceSystem(geometry));
+ assertEquals(CommonCRS.WGS84.normalizedGeographic(),
JTS.getCoordinateReferenceSystem(geometry));
}
/**
diff --git
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/privy/SQLBuilder.java
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/privy/SQLBuilder.java
index e914f49685..dba6153af5 100644
---
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/privy/SQLBuilder.java
+++
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/privy/SQLBuilder.java
@@ -159,55 +159,66 @@ public class SQLBuilder extends Syntax {
/**
* Appends an identifier between quote characters.
*
- * @param identifier the identifier to append.
+ * @param name the identifier to append.
* @return this builder, for method call chaining.
*/
- public final SQLBuilder appendIdentifier(final String identifier) {
- buffer.append(quote).append(identifier).append(quote);
+ public final SQLBuilder appendIdentifier(final String name) {
+ buffer.append(quote).append(name).append(quote);
return this;
}
/**
* Appends an identifier for an element in the given schema.
+ * The following rules apply:
* <ul>
* <li>The given schema will be written only if non-null.</li>
- * <li>The given schema will be quoted only if {@code quoteSchema} is
{@code true}.</li>
- * <li>The given identifier is always quoted.</li>
+ * <li>The given schema will be quoted only if {@link #quoteSchema} is
{@code true}.</li>
+ * <li>The given name is always quoted.</li>
* </ul>
*
- * @param schema the schema, or {@code null} or empty if none.
- * @param identifier the identifier to append.
+ * @param schema the schema, or {@code null} or empty if none.
+ * @param name the name part of the identifier to append.
* @return this builder, for method call chaining.
*/
- public final SQLBuilder appendIdentifier(final String schema, final String
identifier) {
- if (schema != null && !schema.isEmpty()) {
- if (quoteSchema) {
- appendIdentifier(schema);
- } else {
- buffer.append(schema);
- }
- buffer.append('.');
- }
- return appendIdentifier(identifier);
+ public final SQLBuilder appendIdentifier(final String schema, final String
name) {
+ return appendIdentifier(null, schema, name, true);
}
/**
* Appends an identifier for an element in the given schema and catalog.
+ * The schema is quoted only if {@link #quoteSchema} is {@code true}.
+ * The name part is quoted only if {@code quoteName} is {@code true}.
+ * Unquoted names are useful when the name is for built-in functions,
+ * which often use the lower/upper case convention of the database.
*
- * @param catalog the catalog, or {@code null} or empty if none.
- * @param schema the schema, or {@code null} or empty if none.
- * @param identifier the identifier to append.
+ * @param catalog the catalog, or {@code null} or empty if none.
+ * @param schema the schema, or {@code null} or empty if none.
+ * @param name the name part of the identifier to append.
+ * @param quoteName whether to quote the name part.
* @return this builder, for method call chaining.
*/
- public final SQLBuilder appendIdentifier(final String catalog, final
String schema, final String identifier) {
+ public final SQLBuilder appendIdentifier(final String catalog, final
String schema, final String name, final boolean quoteName) {
if (catalog != null && !catalog.isEmpty()) {
appendIdentifier(catalog);
buffer.append('.');
- if (schema == null) {
+ if (schema == null || schema.isEmpty()) {
buffer.append(quote).append(quote).append('.');
}
}
- return appendIdentifier(schema, identifier);
+ if (schema != null && !schema.isEmpty()) {
+ if (quoteSchema) {
+ appendIdentifier(schema);
+ } else {
+ buffer.append(schema);
+ }
+ buffer.append('.');
+ }
+ if (quoteName) {
+ return appendIdentifier(name);
+ } else {
+ buffer.append(name);
+ return this;
+ }
}
/**
diff --git
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/privy/Syntax.java
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/privy/Syntax.java
index 3db59a8239..335aa2f276 100644
---
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/privy/Syntax.java
+++
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/privy/Syntax.java
@@ -41,7 +41,11 @@ public class Syntax {
/**
* Whether the schema name should be written between quotes. If {@code
false},
- * we will let the database engine uses its default lower case / upper
case policy.
+ * Apache SIS lets the database engine uses its default lower case / upper
case policy.
+ * This flag is usually {@code true} when the schema was specified by the
user or has
+ * been discovered from database metadata. This flag is {@code false} when
the schema
+ * has been created by an Apache SIS script, which intentionally uses
unquoted schema
+ * for integration with database conventions.
*
* @see SQLBuilder#appendIdentifier(String, String)
*/
diff --git
a/endorsed/src/org.apache.sis.openoffice/main/org/apache/sis/openoffice/CalcAddins.java
b/endorsed/src/org.apache.sis.openoffice/main/org/apache/sis/openoffice/CalcAddins.java
index f98133f25d..d597cf0335 100644
---
a/endorsed/src/org.apache.sis.openoffice/main/org/apache/sis/openoffice/CalcAddins.java
+++
b/endorsed/src/org.apache.sis.openoffice/main/org/apache/sis/openoffice/CalcAddins.java
@@ -190,7 +190,7 @@ public abstract class CalcAddins extends WeakBase
implements XServiceName, XServ
final Logger logger = getLogger();
final LogRecord record = new LogRecord(Level.WARNING,
getLocalizedMessage(exception));
record.setLoggerName(logger.getName());
- record.setSourceClassName(getClass().getName());
+ record.setSourceClassName(getClass().getCanonicalName());
record.setSourceMethodName(method);
record.setThrown(exception);
logger.log(record);
diff --git
a/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/base/GridMapping.java
b/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/base/GridMapping.java
index 70412a7534..45015c40de 100644
---
a/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/base/GridMapping.java
+++
b/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/base/GridMapping.java
@@ -574,7 +574,7 @@ final class GridMapping {
if (warnings != null) {
final var record = new LogRecord(Level.WARNING,
warnings.toString());
record.setLoggerName(Modules.NETCDF);
- record.setSourceClassName(Variable.class.getCanonicalName());
+ record.setSourceClassName(Variable.class.getName());
record.setSourceMethodName("getGridGeometry");
mapping.decoder.listeners.warning(record);
}
diff --git
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/DataAccess.java
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/DataAccess.java
index 1b7aeee962..bed8c9d194 100644
---
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/DataAccess.java
+++
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/DataAccess.java
@@ -90,6 +90,8 @@ public class DataAccess implements AutoCloseable {
/**
* Helper methods for fetching information such as coordinate reference
systems.
* Created when first needed.
+ *
+ * @see #spatialInformation()
*/
private InfoStatements spatialInformation;
diff --git
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/FeatureIterator.java
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/FeatureIterator.java
index 13be593b51..450f625a5b 100644
---
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/FeatureIterator.java
+++
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/FeatureIterator.java
@@ -25,7 +25,6 @@ import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.apache.sis.metadata.sql.privy.SQLBuilder;
-import org.apache.sis.storage.InternalDataStoreException;
import org.apache.sis.util.collection.WeakValueHashMap;
// Specific to the geoapi-3.1 and geoapi-4.0 branches:
@@ -109,20 +108,29 @@ final class FeatureIterator implements
Spliterator<Feature>, AutoCloseable {
* @param table the source table.
* @param connection connection to the database, used for creating the
statement.
* @param distinct whether the set should contain distinct feature
instances.
- * @param filter condition to append, not including the {@code WHERE}
keyword.
+ * @param selection condition to append, not including the {@code WHERE}
keyword.
* @param sort the {@code ORDER BY} clauses, or {@code null} if
none.
* @param offset number of rows to skip in underlying SQL query, or ≤
0 for none.
* @param count maximum number of rows to return, or ≤ 0 for no
limit.
*/
- FeatureIterator(final Table table, final Connection connection,
- final boolean distinct, final String filter, final SortBy<? super
Feature> sort,
- final long offset, final long count)
- throws SQLException, InternalDataStoreException
+ FeatureIterator(final Table table,
+ final Connection connection,
+ final boolean distinct,
+ final SelectionClause selection,
+ final SortBy<? super Feature> sort,
+ final long offset,
+ final long count)
+ throws Exception
{
adapter = table.adapter(connection);
- spatialInformation = table.database.getSpatialSchema().isPresent()
- ? table.database.createInfoStatements(connection) : null;
- String sql = adapter.sql;
+ String sql = adapter.sql; // Will be completed below with `WHERE`
clause if needed.
+
+ if (table.database.getSpatialSchema().isPresent()) {
+ spatialInformation =
table.database.createInfoStatements(connection);
+ } else {
+ spatialInformation = null;
+ }
+ final String filter = (selection != null) ?
selection.query(connection, spatialInformation) : null;
if (distinct || filter != null || sort != null || offset > 0 || count
> 0) {
final var builder = new SQLBuilder(table.database).append(sql);
if (distinct) {
diff --git
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/FeatureStream.java
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/FeatureStream.java
index c24daa5e88..1cbd99ae93 100644
---
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/FeatureStream.java
+++
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/FeatureStream.java
@@ -35,6 +35,7 @@ import org.apache.sis.util.privy.Strings;
import org.apache.sis.util.stream.DeferredStream;
import org.apache.sis.util.stream.PaginedStream;
import org.apache.sis.filter.privy.SortByComparator;
+import org.apache.sis.filter.privy.WarningEvent;
import org.apache.sis.storage.DataStoreException;
// Specific to the geoapi-3.1 and geoapi-4.0 branches:
@@ -177,17 +178,22 @@ final class FeatureStream extends DeferredStream<Feature>
{
* if we have a "F₀ AND F₁ AND F₂" chain, it is possible to have some
Fₙ as SQL statements and
* other Fₙ executed in Java code.
*/
- final Optimization optimization = new Optimization();
- optimization.setFeatureType(table.featureType);
Stream<Feature> stream = this;
- for (final Filter<? super Feature> filter :
optimization.applyAndDecompose((Filter<? super Feature>) predicate)) {
- if (filter == Filter.include()) continue;
- if (filter == Filter.exclude()) return empty();
- if (!selection.tryAppend(filterToSQL, filter)) {
- // Delegate to Java code all filters that we cannot translate
to SQL statement.
- stream = super.filter(filter);
- hasPredicates = true;
+ try {
+ WarningEvent.LISTENER.set(selection);
+ final var optimization = new Optimization();
+ optimization.setFeatureType(table.featureType);
+ for (final Filter<? super Feature> filter :
optimization.applyAndDecompose((Filter<? super Feature>) predicate)) {
+ if (filter == Filter.include()) continue;
+ if (filter == Filter.exclude()) return empty();
+ if (!selection.tryAppend(filterToSQL, filter)) {
+ // Delegate to Java code all filters that we cannot
translate to SQL statement.
+ stream = super.filter(filter);
+ hasPredicates = true;
+ }
}
+ } finally {
+ WarningEvent.LISTENER.remove();
}
return stream;
}
@@ -323,12 +329,15 @@ final class FeatureStream extends DeferredStream<Feature>
{
sql.appendIdentifier(table.attributes[0].label);
}
table.appendFromClause(sql.append(')'));
- if (selection != null && !selection.isEmpty()) {
- sql.append(" WHERE ").append(selection.toString());
- }
lock(table.database.transactionLocks);
try (Connection connection = getConnection()) {
makeReadOnly(connection);
+ if (selection != null) {
+ final String filter = selection.query(connection, null);
+ if (filter != null) {
+ sql.append(" WHERE ").append(filter);
+ }
+ }
try (Statement st = connection.createStatement();
ResultSet rs = st.executeQuery(sql.toString()))
{
@@ -337,7 +346,7 @@ final class FeatureStream extends DeferredStream<Feature> {
if (!rs.wasNull()) return n;
}
}
- } catch (SQLException e) {
+ } catch (Exception e) {
throw cannotExecute(e);
} finally {
unlock();
@@ -394,15 +403,13 @@ final class FeatureStream extends DeferredStream<Feature>
{
*/
@Override
protected Spliterator<Feature> createSourceIterator() throws Exception {
- final String filter = (selection != null && !selection.isEmpty()) ?
selection.toString() : null;
- selection = null; // Let the garbage collector do its work.
-
lock(table.database.transactionLocks);
final Connection connection = getConnection();
setCloseHandler(connection); // Executed only if `FeatureIterator`
creation fails, discarded later otherwise.
makeReadOnly(connection);
- final var features = new FeatureIterator(table, connection, distinct,
filter, sort, offset, count);
+ final var features = new FeatureIterator(table, connection, distinct,
selection, sort, offset, count);
setCloseHandler(features);
+ selection = null; // Let the garbage collector do its work.
return features;
}
diff --git
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/InfoStatements.java
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/InfoStatements.java
index f8590e8d43..34d4a25d5c 100644
---
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/InfoStatements.java
+++
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/InfoStatements.java
@@ -489,7 +489,7 @@ public class InfoStatements implements Localized,
AutoCloseable {
private void log(final String method, final LogRecord warning) {
if (warning != null) {
warning.setLoggerName(Modules.SQL);
- warning.setSourceClassName(getClass().getName());
+ warning.setSourceClassName(getClass().getCanonicalName());
warning.setSourceMethodName(method);
database.listeners.warning(warning);
}
@@ -522,7 +522,7 @@ public class InfoStatements implements Localized,
AutoCloseable {
* responsible for holding a lock. It may be a read lock or write lock
depending
* on the {@link Connection#isReadOnly()} value.
*
- * @param crs the CRS for which to find a SRID, or {@code null}.
+ * @param crs the CRS for which to find a SRID, or {@code null}.
* @return SRID for the given CRS, or 0 if the given CRS was null.
* @throws Exception if an SQL error, parsing error or other error
occurred.
*/
diff --git
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/Resources.java
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/Resources.java
index 534146c158..7ea58675af 100644
---
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/Resources.java
+++
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/Resources.java
@@ -93,6 +93,11 @@ public class Resources extends IndexedResourceBundle {
*/
public static final short IllegalQualifiedName_1 = 3;
+ /**
+ * The literal of function “{0}” is not compatible with the reference
system of property “{1}”.
+ */
+ public static final short IncompatibleLiteralCRS_2 = 18;
+
/**
* Unexpected error while analyzing the database schema.
*/
diff --git
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/Resources.properties
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/Resources.properties
index 77620d600b..1060cb1e1c 100644
---
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/Resources.properties
+++
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/Resources.properties
@@ -27,6 +27,7 @@ DataSource = Provider of connections
to the database.
DuplicatedColumn_1 = Unexpected duplication of column named
\u201c{0}\u201d.
DuplicatedSRID_2 = Spatial Reference Identifier (SRID) {1}
has more than one entry in \u201c{0}\u201d table.
IllegalQualifiedName_1 = \u201c{0}\u201d is not a valid qualified
name for a table.
+IncompatibleLiteralCRS_2 = The literal of function \u201c{0}\u201d is
not compatible with the reference system of property \u201c{1}\u201d.
InternalError = Unexpected error while analyzing the
database schema.
MalformedForeignerKey_2 = Unexpected column \u201c{1}\u201d in the
\u201c{0}\u201d foreigner key.
MappedSQLQueries = Resource names mapped to SQL queries.
diff --git
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/Resources_fr.properties
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/Resources_fr.properties
index 437f789ad5..3d6a4a87f0 100644
---
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/Resources_fr.properties
+++
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/Resources_fr.properties
@@ -32,6 +32,7 @@ DataSource = Fournisseur de connexions
\u00e0 la base de
DuplicatedColumn_1 = Doublon inattendu d\u2019une colonne
nomm\u00e9e \u00ab\u202f{0}\u202f\u00bb.
DuplicatedSRID_2 = L\u2019identifiant de r\u00e9f\u00e9rence
spatiale (SRID) {1} a plusieurs entr\u00e9s dans la table
\u00ab\u202f{0}\u202f\u00bb.
IllegalQualifiedName_1 = \u00ab\u202f{0}\u202f\u00bb n\u2019est pas
un nom qualifi\u00e9 de table valide.
+IncompatibleLiteralCRS_2 = Le litt\u00e9ral de la fonction
\u00ab\u202f{0}\u202f\u00bb n'est pas compatible avec le syst\u00e8me de
r\u00e9f\u00e9rence de la propri\u00e9t\u00e9 \u00ab\u202f{1}\u202f\u00bb.
InternalError = Erreur inattendue pendant l\u2019analyse
du sch\u00e9ma de la base de donn\u00e9es.
MalformedForeignerKey_2 = Colonne \u00ab\u202f{1}\u202f\u00bb
inattendue dans la cl\u00e9 \u00e9trang\u00e8re \u00ab\u202f{0}\u202f\u00bb.
MappedSQLQueries = Noms de ressources associ\u00e9s \u00e0
des requ\u00eates SQL.
diff --git
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/SelectionClause.java
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/SelectionClause.java
index 819d204b07..aa0d7a92ab 100644
---
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/SelectionClause.java
+++
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/SelectionClause.java
@@ -16,14 +16,29 @@
*/
package org.apache.sis.storage.sql.feature;
+import java.util.Map;
+import java.util.AbstractMap;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Optional;
+import java.util.logging.Level;
+import java.util.logging.LogRecord;
+import java.util.function.Consumer;
+import java.sql.Connection;
+import org.opengis.util.CodeList;
import org.opengis.geometry.Envelope;
-import org.opengis.geometry.Geometry;
import org.opengis.metadata.extent.GeographicBoundingBox;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.apache.sis.geometry.GeneralEnvelope;
import org.apache.sis.geometry.WraparoundMethod;
import org.apache.sis.geometry.wrapper.Geometries;
import org.apache.sis.geometry.wrapper.GeometryWrapper;
import org.apache.sis.metadata.sql.privy.SQLBuilder;
+import org.apache.sis.filter.privy.WarningEvent;
+import org.apache.sis.storage.FeatureSet;
+import org.apache.sis.system.Modules;
+import org.apache.sis.util.Utilities;
+import org.apache.sis.util.Workaround;
// Specific to the geoapi-3.1 and geoapi-4.0 branches:
import org.opengis.feature.Feature;
@@ -37,20 +52,68 @@ import org.opengis.filter.ValueReference;
* @author Alexis Manin (Geomatys)
* @author Martin Desruisseaux (Geomatys)
*/
-public final class SelectionClause extends SQLBuilder {
+public final class SelectionClause extends SQLBuilder implements
Consumer<WarningEvent> {
+ /**
+ * Whether the database rejects spatial functions that mix geometries with
and without <abbr>CRS</abbr>.
+ * We observed that PostGIS 3.4 produces an error not only when the
geometry operands have different CRS,
+ * but also when one operand has a CRS and the other operand has no
explicit CRS. Example:
+ *
+ * <blockquote>Operation on mixed SRID geometries (Polygon, 4326) !=
(Polygon, 0)</blockquote>
+ *
+ * As a workaround, if the literal has no CRS, we assume that the CRS of
the geometry column was implied.
+ * In such cases, the SRID 0 is replaced by the SRID of the geometry
column. Current version applies this
+ * workaround for all databases, but we could make this field non-static
if a future version of Apache SIS
+ * decides to apply this policy on a case-by-case basis.
+ *
+ * <p>Note that above error is not always visible. For example, when using
{@code ST_Intersects},
+ * PostGIS first performs a quick filtering based on bounding boxes using
the {@code &&} operator.
+ * But that operator does not check the <abbr>SRID</abbr>. Therefore, no
error message is raised
+ * when the bounding boxes of the geometries do not intersect.</p>
+ */
+ @Workaround(library = "PostGIS", version = "3.4")
+ static final boolean REPLACE_UNSPECIFIED_CRS = true;
+
/**
* The table or view for which to create a SQL statement.
*/
private final Table table;
+ /**
+ * The parameters to set after the connection has been established.
+ * For each entry, the key is the index in {@link #buffer} of the
parameter to set
+ * and the value is the object to convert to a value that can be set in
the query.
+ * The following values need to be converted:
+ *
+ * <ul>
+ * <li>Instances of {@link CoordinateReferenceSystem} shall be replaced
by <abbr>SRID</abbr>.</li>
+ * </ul>
+ *
+ * Elements must be sorted in increasing order of keys.
+ *
+ * @see #query(InfoStatements)
+ */
+ private final List<Map.Entry<Integer, CoordinateReferenceSystem>>
parameters;
+
+ /**
+ * The coordinate reference system of the geometry columns referenced by
the spatial functions to write.
+ * This is {@code null} when not writing a spatial function, or if the
function does not reference any
+ * geometry column, or if it references more than one geometry column with
different <abbr>CRS</abbr>.
+ * If non-null, the optional may be empty if the geometry column does not
declare a reference system.
+ * In the latter case (empty), each row of the table can be a geometry in
a different <abbr>CRS</abbr>.
+ *
+ * @see #REPLACE_UNSPECIFIED_CRS
+ */
+ private Optional<CoordinateReferenceSystem> columnCRS;
+
/**
* Flag sets to {@code true} if a filter or expression cannot be converted
to SQL.
* When a SQL string become flagged as invalid, it is truncated to the
length that
* it had the last time that it was valid.
*
* @see #invalidate()
+ * @see #isInvalid()
*/
- boolean isInvalid;
+ private boolean isInvalid;
/**
* Creates a new builder for the given table.
@@ -60,6 +123,39 @@ public final class SelectionClause extends SQLBuilder {
SelectionClause(final Table table) {
super(table.database);
this.table = table;
+ parameters = new ArrayList<>();
+ }
+
+ /**
+ * Clears any <abbr>CRS</abbr> that was configured by {@code
acceptColumnCRS(…)}.
+ * This method should be invoked after the caller finished to write a
spatial function.
+ */
+ final void clearColumnCRS() {
+ columnCRS = null;
+ }
+
+ /**
+ * If the referenced column is a geometry or raster column, remembers its
default coordinate reference system.
+ * This method can be invoked before {@link #appendLiteral(Object)} in
order to set a default <abbr>CRS</abbr>
+ * for literals that do not declare themselves their <abbr>CRS</abbr>.
+ *
+ * @param ref reference to a property to insert in SQL statement.
+ * @return whether the caller needs to stop the check of operands.
+ *
+ * @see #REPLACE_UNSPECIFIED_CRS
+ */
+ final boolean acceptColumnCRS(final ValueReference<Feature,?> ref) {
+ final Column c = table.getColumn(ref.getXPath());
+ if (c != null && c.getGeometryType().isPresent()) {
+ final Optional<CoordinateReferenceSystem> crs = c.getDefaultCRS();
+ if (columnCRS == null) {
+ columnCRS = crs; // May be empty, which is not the same as
null for this class.
+ } else if (!Utilities.equalsIgnoreMetadata(columnCRS.orElse(null),
crs.orElse(null))) {
+ clearColumnCRS();
+ return true;
+ }
+ }
+ return false;
}
/**
@@ -84,10 +180,10 @@ public final class SelectionClause extends SQLBuilder {
appendGeometry(null, new GeneralEnvelope((GeographicBoundingBox)
value));
} else if (value instanceof Envelope) {
appendGeometry(null, (Envelope) value);
- } else if (value instanceof Geometry) {
- appendGeometry(Geometries.wrap((Geometry) value).orElse(null),
null);
} else {
- appendValue(value);
+ Geometries.wrap(value).ifPresentOrElse(
+ (wrapper) -> appendGeometry(wrapper, null),
+ () -> appendValue(value));
}
}
@@ -111,9 +207,14 @@ public final class SelectionClause extends SQLBuilder {
invalidate();
return;
}
+ /*
+ * Get the average span of the geometry. It will be used for computing
a flatness factor.
+ * It does not matter if the span is inaccurate, as the flatness
factor is only a hint
+ * ignored by most geometry libraries.
+ */
final double span = (bounds.getSpan(0) + bounds.getSpan(1)) /
Geometries.BIDIMENSIONAL;
if (Double.isNaN(span)) {
- final GeneralEnvelope e = new GeneralEnvelope(bounds);
+ final var e = new GeneralEnvelope(bounds);
for (int i=0; i<Geometries.BIDIMENSIONAL; i++) {
final double lower = clampInfinity(e.getLower(i));
final double upper = clampInfinity(e.getUpper(i));
@@ -128,8 +229,40 @@ public final class SelectionClause extends SQLBuilder {
if (wrapper == null) {
wrapper = table.database.geomLibrary.toGeometry2D(bounds,
WraparoundMethod.SPLIT);
}
- final String wkt = wrapper.formatWKT(0.05 * span);
- append("ST_GeomFromText(").appendValue(wkt).append(')');
+ final String wkt = wrapper.formatWKT(0.05 * span); // Arbitrary
flateness factor.
+ /*
+ * Format a spatial function for building the geometry from the
Well-Known Text.
+ * The CRS, if available, while be specified as a SRID if the spatial
support has
+ * been recognized (otherwise we cannot map the CRS to the
database-dependent SRID).
+ */
+ appendSpatialFunction("ST_GeomFromText");
+ append('(').appendValue(wkt);
+ if (table.database.getSpatialSchema().isPresent()) {
+ CoordinateReferenceSystem crs =
wrapper.getCoordinateReferenceSystem();
+ if (REPLACE_UNSPECIFIED_CRS && columnCRS != null) {
+ if (crs == null) {
+ crs = columnCRS.orElse(null);
+ } else {
+ /*
+ * If `columnCRS` is empty, then we have a geometry column
without CRS (SRID = 0).
+ * For making the literal consistent with the column, we
could set `crs` to null.
+ * However, while the column has no CRS, the geometry
instances on each row may have a CRS
+ * and clearing the literal CRS will result in "Operation
on mixed SRID geometries" error.
+ *
+ * The opposite problem also exists: we could really have
no CRS at all, neither in the column
+ * and in the geometry instances. In such case, not
clearing the CRS may also cause above error.
+ * We have no easy way to determine if we should clear the
CRS or not. The conservative approach
+ * applied for now is to leave the literal as the user
specified it.
+ */
+ }
+ }
+ if (crs != null) {
+ buffer.append(", ");
+ parameters.add(new AbstractMap.SimpleEntry<>(buffer.length(),
crs));
+ buffer.append('?');
+ }
+ }
+ append(')');
}
/**
@@ -142,11 +275,16 @@ public final class SelectionClause extends SQLBuilder {
}
/**
- * Declares the SQL as invalid. It does not means that the whole SQL needs
to be discarded.
- * The SQL may be truncated to the last point where it was considered
valid.
+ * Appends the name of a spatial function. The catalog and schema names are
+ * included for making sure that it works even if the search path is not
set.
+ * The function name is written without quotes, because the functions kept
by
+ * {@link SelectionClauseWriter#removeUnsupportedFunctions(Database)} use
the
+ * case convention of the database.
+ *
+ * @param name name of the spatial function to append.
*/
- final void invalidate() {
- isInvalid = true;
+ final void appendSpatialFunction(final String name) {
+ appendIdentifier(table.database.catalogOfSpatialTables,
table.database.schemaOfSpatialTables, name, false);
}
/**
@@ -170,4 +308,90 @@ public final class SelectionClause extends SQLBuilder {
}
return true;
}
+
+ /**
+ * Returns whether an error occurred while writing the <abbr>SQL</abbr>
statement.
+ * If this method returns {@code true}, then the caller should truncate
the SQL to
+ * the last length which was known to be valid and fallback on Java code
for the rest.
+ */
+ final boolean isInvalid() {
+ return isInvalid;
+ }
+
+ /**
+ * Declares the SQL as invalid. It does not means that the whole SQL needs
to be discarded.
+ * The SQL may be truncated to the last point where it was considered
valid.
+ */
+ final void invalidate() {
+ isInvalid = true;
+ }
+
+ /**
+ * Returns the localized resources for warnings and error messages.
+ */
+ private Resources resources() {
+ return Resources.forLocale(table.database.listeners.getLocale());
+ }
+
+ /**
+ * Sets the logger, class and method names of the given record, then logs
it.
+ * This method declares {@link FeatureSet#features(boolean)} as the public
source of the log.
+ *
+ * @param record the record to configure and log.
+ */
+ private void log(final LogRecord record) {
+ record.setSourceClassName(FeatureSet.class.getName());
+ record.setSourceMethodName("features");
+ record.setLoggerName(Modules.SQL);
+ table.database.listeners.warning(record);
+ }
+
+ /**
+ * Invoked when a warning occurred during operations on filters or
expressions.
+ *
+ * @param event the warning.
+ */
+ @Override
+ public void accept(final WarningEvent event) {
+ final LogRecord record = resources().getLogRecord(
+ Level.WARNING,
+ Resources.Keys.IncompatibleLiteralCRS_2,
+
event.getOperatorType().flatMap(CodeList::identifier).orElse("?"),
+
event.getParameter(ValueReference.class).map(ValueReference<?,?>::getXPath).orElse("?"));
+ record.setThrown(event.exception);
+ log(record);
+ }
+
+ /**
+ * Returns the <abbr>SQL</abbr> fragment built by this {@code
SelectionClause}.
+ * This method completes the information that we deferred until a
connection is established.
+ *
+ * @param spatialInformation a cache of statements for fetching spatial
information, or {@code null}.
+ * @return the <abbr>SQL</abbr> fragment, or {@code null} if there is no
{@code WHERE} clause to add.
+ * @throws Exception if an SQL error, parsing error or other error
occurred.
+ */
+ final String query(final Connection connection, InfoStatements
spatialInformation) throws Exception {
+ if (isEmpty()) {
+ return null;
+ }
+ boolean close = false;
+ for (int i = parameters.size(); --i >= 0;) {
+ if (spatialInformation == null) {
+ spatialInformation =
table.database.createInfoStatements(connection);
+ close = true;
+ }
+ final var entry = parameters.get(i);
+ final int index = entry.getKey();
+ final int srid = spatialInformation.findSRID(entry.getValue());
+ buffer.replace(index, index + 1, Integer.toString(srid));
+ }
+ if (close) {
+ /*
+ * We could put this in a `finally` block, but this method is
already invoked
+ * in a context where the caller will close the connection in case
of failure.
+ */
+ spatialInformation.close();
+ }
+ return buffer.toString();
+ }
}
diff --git
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/SelectionClauseWriter.java
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/SelectionClauseWriter.java
index 208fb33c89..a9e96dd22c 100644
---
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/SelectionClauseWriter.java
+++
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/SelectionClauseWriter.java
@@ -231,7 +231,7 @@ public class SelectionClauseWriter extends Visitor<Feature,
SelectionClause> {
@SuppressWarnings("unchecked")
final boolean write(final SelectionClause sql, final Filter<? super
Feature> filter) {
visit((Filter<Feature>) filter, sql);
- return sql.isInvalid;
+ return sql.isInvalid();
}
/**
@@ -243,7 +243,7 @@ public class SelectionClauseWriter extends Visitor<Feature,
SelectionClause> {
*/
private boolean write(final SelectionClause sql, final Expression<Feature,
?> expression) {
visit(expression, sql);
- return sql.isInvalid;
+ return sql.isInvalid();
}
/**
@@ -387,7 +387,8 @@ public class SelectionClauseWriter extends Visitor<Feature,
SelectionClause> {
/**
* Appends a function name with an arbitrary number of parameters
(potentially zero).
* This method stops immediately if a parameter cannot be expressed in
SQL, leaving
- * the trailing part of the SQL in an invalid state.
+ * the trailing part of the SQL in an invalid state. Callers should check
if this is
+ * the case by invoking {@link SelectionClause#isInvalid()} after this
method call.
*/
private final class Function implements BiConsumer<Filter<Feature>,
SelectionClause> {
/** Name the function. */
@@ -398,10 +399,27 @@ public class SelectionClauseWriter extends
Visitor<Feature, SelectionClause> {
this.name = name;
}
- /** Writes the function as an SQL statement. */
+ /**
+ * Writes the function as an SQL statement. The function is usually
spatial (with geometry operands),
+ * but not necessarily. If the given {@code filter} contains geometry
operands specified as literal,
+ * {@link org.apache.sis.filter.Optimization} should have already
transformed the literals to the CRS
+ * of the geometry column when those CRS are known. Therefore, it
should not be needed to perform any
+ * geometry transformation in this method.
+ */
@Override public void accept(final Filter<Feature> filter, final
SelectionClause sql) {
- sql.append(name);
- writeParameters(sql, filter.getExpressions(), ", ", false);
+ sql.appendSpatialFunction(name);
+ final List<Expression<Feature, ?>> expressions =
filter.getExpressions();
+ if (SelectionClause.REPLACE_UNSPECIFIED_CRS) {
+ for (Expression<Feature,?> exp : expressions) {
+ if (exp instanceof ValueReference<?,?>) {
+ if (sql.acceptColumnCRS((ValueReference<Feature,?>)
exp)) {
+ break;
+ }
+ }
+ }
+ }
+ writeParameters(sql, expressions, ", ", false);
+ sql.clearColumnCRS();
}
}
}
diff --git
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/Table.java
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/Table.java
index 932e3fde96..13856b4c89 100644
---
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/Table.java
+++
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/Table.java
@@ -421,7 +421,7 @@ final class Table extends AbstractFeatureSet {
if (query != null) {
sql.append('(').append(query).append(") AS USER_QUERY");
} else {
- sql.appendIdentifier(name.catalog, name.schema, name.table);
+ sql.appendIdentifier(name.catalog, name.schema, name.table, true);
}
}
diff --git
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/postgis/ExtendedClauseWriter.java
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/postgis/ExtendedClauseWriter.java
index f2e40cd0b2..0e0ee0504b 100644
---
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/postgis/ExtendedClauseWriter.java
+++
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/postgis/ExtendedClauseWriter.java
@@ -26,6 +26,11 @@ import org.opengis.filter.SpatialOperatorName;
* Converter from filters/expressions to the {@code WHERE} part of SQL
statement
* with PostGIS-specific syntax where useful.
*
+ * This class adds the search by bounding box using the {@code &&} operator.
+ * Note that contrarily to standard operators such as {@code ST_Intersects},
+ * the {@code &&} operator does not verify the <abbr>CRS</abbr>.
+ * No error message is raised is case of mismatched CRS.
+ *
* @author Alexis Manin (Geomatys)
*/
final class ExtendedClauseWriter extends SelectionClauseWriter {
diff --git
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/postgis/ExtentEstimator.java
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/postgis/ExtentEstimator.java
index 5865e933c1..d11dd644fb 100644
---
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/postgis/ExtentEstimator.java
+++
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/postgis/ExtentEstimator.java
@@ -98,7 +98,7 @@ final class ExtentEstimator {
GeneralEnvelope estimate(final Statement statement, final boolean recall)
throws SQLException {
query(statement);
if (envelope == null && !recall) {
- builder.append("ANALYZE ").appendIdentifier(table.catalog,
table.schema, table.table);
+ builder.append("ANALYZE ").appendIdentifier(table.catalog,
table.schema, table.table, true);
final String sql = builder.toString();
builder.clear();
statement.execute(sql);
diff --git
a/endorsed/src/org.apache.sis.storage.sql/test/org/apache/sis/storage/sql/feature/GeometryGetterTest.java
b/endorsed/src/org.apache.sis.storage.sql/test/org/apache/sis/storage/sql/feature/GeometryGetterTest.java
index 3158ada9a8..9e13d21269 100644
---
a/endorsed/src/org.apache.sis.storage.sql/test/org/apache/sis/storage/sql/feature/GeometryGetterTest.java
+++
b/endorsed/src/org.apache.sis.storage.sql/test/org/apache/sis/storage/sql/feature/GeometryGetterTest.java
@@ -162,6 +162,7 @@ public final class GeometryGetterTest extends TestCase {
public static CoordinateReferenceSystem getExpectedCRS(final int srid)
throws FactoryException {
final String code;
switch (srid) {
+ case 0: return null;
case 3395: code = "EPSG:3395"; break;
case 4326: return CommonCRS.WGS84.normalizedGeographic();
default: throw new AssertionError(srid);
diff --git
a/endorsed/src/org.apache.sis.storage.sql/test/org/apache/sis/storage/sql/postgis/PostgresTest.java
b/endorsed/src/org.apache.sis.storage.sql/test/org/apache/sis/storage/sql/postgis/PostgresTest.java
index 2babdb9146..f9ed98cc6b 100644
---
a/endorsed/src/org.apache.sis.storage.sql/test/org/apache/sis/storage/sql/postgis/PostgresTest.java
+++
b/endorsed/src/org.apache.sis.storage.sql/test/org/apache/sis/storage/sql/postgis/PostgresTest.java
@@ -30,6 +30,8 @@ import java.nio.channels.ReadableByteChannel;
import java.lang.reflect.Method;
import org.locationtech.jts.geom.Point;
import org.locationtech.jts.geom.Geometry;
+import org.locationtech.jts.geom.GeometryFactory;
+import org.locationtech.jts.geom.Coordinate;
import org.opengis.geometry.Envelope;
import org.opengis.metadata.Metadata;
import org.opengis.metadata.identification.Identification;
@@ -46,7 +48,9 @@ import org.apache.sis.storage.sql.SQLStoreProvider;
import org.apache.sis.storage.sql.SimpleFeatureStore;
import org.apache.sis.storage.sql.ResourceDefinition;
import org.apache.sis.coverage.grid.GridCoverage;
+import org.apache.sis.filter.DefaultFilterFactory;
import org.apache.sis.io.stream.ChannelDataInput;
+import org.apache.sis.storage.DataStoreException;
import org.apache.sis.storage.sql.feature.BinaryEncoding;
import org.apache.sis.geometry.wrapper.jts.JTS;
import org.apache.sis.referencing.CommonCRS;
@@ -129,8 +133,8 @@ public final class PostgresTest extends TestCase {
database.executeSQL(List.of(resource("SpatialFeatures.sql")));
final var connector = new StorageConnector(database.source);
connector.setOption(OptionKey.GEOMETRY_LIBRARY,
GeometryLibrary.JTS);
- final ResourceDefinition table = ResourceDefinition.table(null,
SQLStoreTest.SCHEMA, "SpatialData");
- try (SimpleFeatureStore store = new SimpleFeatureStore(new
SQLStoreProvider(), connector, table)) {
+ final var table = ResourceDefinition.table(null,
SQLStoreTest.SCHEMA, "SpatialData");
+ try (var store = new SimpleFeatureStore(new SQLStoreProvider(),
connector, table)) {
validate(store.getMetadata());
/*
* Invoke the private `model()` method. We have to use
reflection because the class
@@ -151,12 +155,9 @@ public final class PostgresTest extends TestCase {
* Tests through public API.
*/
final FeatureSet resource = store.findResource("SpatialData");
- try (Stream<Feature> features = resource.features(false)) {
- features.forEach(PostgresTest::validate);
- }
- final Envelope envelope = resource.getEnvelope().get();
- assertEquals(envelope.getMinimum(0), -72, 1);
- assertEquals(envelope.getMaximum(1), 43, 1);
+ testAllFeatures(resource);
+ testFilteredFeatures(resource, false);
+ testFilteredFeatures(resource, true);
}
}
}
@@ -183,7 +184,7 @@ public final class PostgresTest extends TestCase {
* @throws Exception if an error occurred while testing the database.
*/
private static void testGeometryGetter(final ExtendedInfo info, final
Connection connection) throws Exception {
- final GeometryGetterTest test = new GeometryGetterTest();
+ final var test = new GeometryGetterTest();
test.testFromDatabase(connection, info, BinaryEncoding.HEXADECIMAL);
}
@@ -193,8 +194,8 @@ public final class PostgresTest extends TestCase {
private static void testRasterReader(final TestRaster test, final
ExtendedInfo info, final Connection connection)
throws Exception
{
- final BinaryEncoding encoding = BinaryEncoding.HEXADECIMAL;
- final RasterReader reader = new RasterReader(info);
+ final var encoding = BinaryEncoding.HEXADECIMAL;
+ final var reader = new RasterReader(info);
try (PreparedStatement stmt = connection.prepareStatement("SELECT
image FROM features.\"SpatialData\" WHERE filename=?")) {
stmt.setString(1, test.filename);
final ResultSet r = stmt.executeQuery();
@@ -206,6 +207,62 @@ public final class PostgresTest extends TestCase {
}
}
+ /**
+ * Tests iterating over all features without filters.
+ * Opportunistically verifies also the bounding boxes of all features.
+ *
+ * @param resource the set of all features.
+ * @throws DataStoreException if an error occurred while fetching the
envelope of feature instances.
+ */
+ private static void testAllFeatures(final FeatureSet resource) throws
DataStoreException {
+ final Envelope envelope = resource.getEnvelope().get();
+ assertEquals(-72, envelope.getMinimum(0), 1);
+ assertEquals( 43, envelope.getMaximum(1), 1);
+ try (Stream<Feature> features = resource.features(false)) {
+ features.forEach(PostgresTest::validate);
+ }
+ try (Stream<Feature> features = resource.features(false)) {
+ assertEquals(8, features.count());
+ }
+ }
+
+ /**
+ * Tests iterating over features with a filter applied, with our without
coordinate operation.
+ * The filter is {@code ST_Intersects(geometry, literal)} where the
literal is a polygon.
+ * The coordinate operation is simply an axis swapping.
+ *
+ * @param resource the set of all features.
+ * @param transform whether to apply a coordinate operation.
+ * @throws Exception if an error occurred while fetching the envelope of
feature instances.
+ */
+ private static void testFilteredFeatures(final FeatureSet resource, final
boolean transform) throws Exception {
+ final Coordinate[] coordinates = {
+ new Coordinate(-72, 42),
+ new Coordinate(-71, 42),
+ new Coordinate(-71, 43),
+ new Coordinate(-72, 43),
+ new Coordinate(-72, 42)
+ };
+ if (transform) {
+ for (Coordinate c : coordinates) {
+ double swp = c.x;
+ c.x = c.y;
+ c.y = swp;
+ }
+ }
+ final Geometry geom = new GeometryFactory().createPolygon(coordinates);
+ if (transform) {
+ JTS.setCoordinateReferenceSystem(geom,
CommonCRS.WGS84.geographic());
+ } else {
+ geom.setSRID(4326);
+ }
+ final var factory = DefaultFilterFactory.forFeatures();
+ final var filter = factory.intersects(factory.property("geom4326"),
factory.literal(geom));
+ try (Stream<Feature> features =
resource.features(false).filter(filter)) {
+ assertEquals(4, features.count());
+ }
+ }
+
/**
* Invoked for each feature instances for performing some checks on the
feature.
* This method performs only a superficial verification of geometries.
@@ -222,8 +279,15 @@ public final class PostgresTest extends TestCase {
assertSame(CommonCRS.WGS84.normalizedGeographic(),
raster.getCoordinateReferenceSystem());
return;
}
+ case "point-nocrs": {
+ var p = (Point) geometry;
+ assertEquals(3, p.getX());
+ assertEquals(4, p.getY());
+ geomSRID = 0;
+ break;
+ }
case "point-prj": {
- final Point p = (Point) geometry;
+ var p = (Point) geometry;
assertEquals(2, p.getX());
assertEquals(3, p.getY());
geomSRID = 3395;
@@ -239,9 +303,13 @@ public final class PostgresTest extends TestCase {
try {
final CoordinateReferenceSystem expected =
GeometryGetterTest.getExpectedCRS(geomSRID);
final CoordinateReferenceSystem actual =
JTS.getCoordinateReferenceSystem(geometry);
- assertNotNull(actual);
- if (expected != null) {
- assertEquals(expected, actual);
+ if (geomSRID == 0) {
+ assertNull(actual);
+ } else {
+ assertNotNull(actual);
+ if (expected != null) {
+ assertEquals(expected, actual);
+ }
}
} catch (FactoryException e) {
throw new AssertionError(e);
diff --git
a/endorsed/src/org.apache.sis.storage.sql/test/org/apache/sis/storage/sql/postgis/SpatialFeatures.sql
b/endorsed/src/org.apache.sis.storage.sql/test/org/apache/sis/storage/sql/postgis/SpatialFeatures.sql
index 2dec522994..f6853ae912 100644
---
a/endorsed/src/org.apache.sis.storage.sql/test/org/apache/sis/storage/sql/postgis/SpatialFeatures.sql
+++
b/endorsed/src/org.apache.sis.storage.sql/test/org/apache/sis/storage/sql/postgis/SpatialFeatures.sql
@@ -11,6 +11,7 @@ SET search_path TO public;
CREATE TABLE features."SpatialData" (
"filename" VARCHAR(20) NOT NULL,
"geometry" GEOMETRY,
+ "geom4326" GEOMETRY(Geometry, 4326),
"image" RASTER,
CONSTRAINT "PK_SpatialData" PRIMARY KEY ("filename")
@@ -32,6 +33,7 @@ INSERT INTO features."SpatialData" ("filename", "image")
VALUES
-- Geometries with arbitrary coordinate values.
--
INSERT INTO features."SpatialData" ("filename", "geometry") VALUES
+ ('point-nocrs', ST_GeomFromText('POINT(3 4)')),
('point-prj', ST_GeomFromText('POINT(2 3)', 3395)),
('linestring', ST_GeomFromText('LINESTRING(-71.160281 42.258729,-71.160837
42.259113,-71.161144 42.25932)', 4326)),
('polygon-prj', ST_GeomFromText('POLYGON((0 0,0 1,1 1,1 0,0 0))', 3395)),
@@ -60,6 +62,12 @@ INSERT INTO features."SpatialData" ("filename", "geometry")
VALUES
|| '-71.1043850704575 42.3150793250568,-71.1043632495873
42.315113108546)))', 4326));
+--
+-- A column where all geometries are in the same CRS.
+--
+UPDATE features."SpatialData" SET geom4326 = geometry WHERE filename NOT LIKE
'%-prj';
+
+
--
-- Geometries with WKT representation in one column and WKB in another column.
-- Used for parsing the same geometry in two ways and comparing the results.
diff --git
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/wkt/StoreFormat.java
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/wkt/StoreFormat.java
index 4262b5461d..fc3a29b6d7 100644
---
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/wkt/StoreFormat.java
+++
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/wkt/StoreFormat.java
@@ -171,7 +171,7 @@ public final class StoreFormat extends WKTFormat {
private void log(final Exception e) {
final LogRecord record = Resources.forLocale(listeners.getLocale())
.getLogRecord(Level.WARNING,
Resources.Keys.CanNotReadCRS_WKT_1, listeners.getSourceName());
- record.setSourceClassName(listeners.getSource().getClass().getName());
+
record.setSourceClassName(listeners.getSource().getClass().getCanonicalName());
record.setSourceMethodName("getMetadata");
record.setLoggerName(Loggers.WKT);
listeners.warning(record);
diff --git
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/setup/GeometryLibrary.java
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/setup/GeometryLibrary.java
index d11db13b12..acc07cbf18 100644
---
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/setup/GeometryLibrary.java
+++
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/setup/GeometryLibrary.java
@@ -28,7 +28,7 @@ import org.opengis.metadata.acquisition.GeometryType;
* All those libraries are optional.
*
* @author Martin Desruisseaux (Geomatys)
- * @version 1.4
+ * @version 1.5
*
* @see OptionKey#GEOMETRY_LIBRARY
* @see
org.apache.sis.feature.builder.FeatureTypeBuilder#addAttribute(GeometryType)
@@ -52,7 +52,7 @@ public enum GeometryLibrary {
* Note that contrarily to JTS and ESRI libraries,
* a point does not extend any root geometry class in Java2D.
*/
- JAVA2D,
+ JAVA2D("Java2D"),
/**
* The ESRI geometry API library. This library can be used for spatial
vector data processing.
@@ -70,7 +70,7 @@ public enum GeometryLibrary {
*
* @see <a href="https://github.com/Esri/geometry-api-java/wiki">API wiki
page</a>
*/
- ESRI,
+ ESRI("ESRI"),
/**
* The Java Topology Suite (JTS) library. This open source library
provides an object model
@@ -90,7 +90,7 @@ public enum GeometryLibrary {
*
* @since 1.0
*/
- JTS,
+ JTS("JTS"),
/**
* The GeoAPI geometry interfaces.
@@ -111,5 +111,32 @@ public enum GeometryLibrary {
*
* @since 1.4
*/
- GEOAPI
+ GEOAPI("GeoAPI");
+
+ /**
+ * Human-readable name of this library.
+ */
+ private final String name;
+
+ /**
+ * Creates a new enumeration value.
+ *
+ * @param name human-readable name of this library.
+ */
+ private GeometryLibrary(final String name) {
+ this.name = name;
+ }
+
+ /**
+ * Returns the name of this geometry library in a way suitable to user
interfaces.
+ * This is the same as {@link #name()} but sometime with different cases.
+ * For example, {@link #JAVA2D} is shown as {@code "Java2D"}.
+ *
+ * @return human-readable name of this library.
+ *
+ * @since 1.5
+ */
+ public String toString() {
+ return name;
+ }
}
diff --git
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/collection/WeakEntry.java
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/collection/WeakEntry.java
index 1e2d88e2a1..cf021c4af3 100644
---
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/collection/WeakEntry.java
+++
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/collection/WeakEntry.java
@@ -173,7 +173,7 @@ abstract class WeakEntry<E> extends WeakReference<E>
implements Disposable {
final LogRecord record =
Messages.forLocale(null).getLogRecord(Level.FINEST,
Messages.Keys.ChangedContainerCapacity_2, oldTable.length,
table.length);
record.setSourceMethodName(callerMethod);
- record.setSourceClassName(entryType.getEnclosingClass().getName());
+
record.setSourceClassName(entryType.getEnclosingClass().getCanonicalName());
record.setLoggerName(LOGGER.getName());
LOGGER.log(record);
}
diff --git
a/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/GeoHeifStore.java
b/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/GeoHeifStore.java
index 530ceb6218..4a917c00f1 100644
---
a/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/GeoHeifStore.java
+++
b/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/GeoHeifStore.java
@@ -318,7 +318,7 @@ public class GeoHeifStore extends DataStore implements
Aggregate {
* Logs a warning emitted (usually indirectly) by {@link #components()}.
*/
final void warning(final LogRecord record) {
- record.setSourceClassName(GeoHeifStore.class.getSimpleName());
+ record.setSourceClassName(GeoHeifStore.class.getCanonicalName());
record.setSourceMethodName("components");
listeners.warning(record);
}