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
commit 4ae8cc9c0d7a18dc2bb1f52d4f33c1819c2db9c5 Author: Martin Desruisseaux <[email protected]> AuthorDate: Mon Sep 22 10:23:33 2025 +0200 Avoid too long `AREA` description in WKT. In particular, the area of EPSG:4326 is more than 3000 characters long because it enumerates all countries. This commit contains also opportunistic cleaning such as anticipating that `NamedExpression` may become a record in a future release. --- .../iso/extent/DefaultGeographicDescription.java | 20 +++- .../apache/sis/metadata/iso/extent/Extents.java | 44 ++++++++- .../apache/sis/metadata/MetadataCopierTest.java | 16 +-- .../sis/metadata/iso/extent/ExtentsTest.java | 11 ++- .../main/org/apache/sis/io/wkt/Formatter.java | 2 +- .../apache/sis/io/wkt/GeodeticObjectParser.java | 3 +- .../main/org/apache/sis/referencing/Builder.java | 2 +- .../sis/referencing/ImmutableIdentifier.java | 4 +- .../referencing/factory/sql/EPSGDataAccess.java | 110 +++++++++++++-------- .../sis/referencing/internal/DeprecatedCode.java | 45 ++++++--- .../apache/sis/referencing/privy/WKTUtilities.java | 44 ++++++++- .../apache/sis/storage/netcdf/MetadataReader.java | 2 +- .../main/org/apache/sis/storage/FeatureQuery.java | 49 ++++++++- .../apache/sis/storage/base/MetadataBuilder.java | 17 ++-- .../main/org/apache/sis/util/Locales.java | 5 +- .../sis/storage/shapefile/ShapefileStore.java | 6 +- .../org/apache/sis/gui/coverage/CellFormat.java | 5 + 17 files changed, 284 insertions(+), 101 deletions(-) diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/extent/DefaultGeographicDescription.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/extent/DefaultGeographicDescription.java index 478a525e06..4b744cc6bf 100644 --- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/extent/DefaultGeographicDescription.java +++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/extent/DefaultGeographicDescription.java @@ -57,7 +57,7 @@ import org.apache.sis.util.iso.Types; * @author Martin Desruisseaux (IRD, Geomatys) * @author Touraïvane (IRD) * @author Cédric Briançon (Geomatys) - * @version 1.4 + * @version 1.5 * @since 0.3 */ @XmlType(name = "EX_GeographicDescription_Type") @@ -80,6 +80,19 @@ public class DefaultGeographicDescription extends AbstractGeographicExtent imple public DefaultGeographicDescription() { } + /** + * Constructs a geographic description initialized to the given identifier. + * + * @param value the new geographic identifier, or {@code null} if none. + * + * @see #setGeographicIdentifier(Identifier) + * + * @since 1.5 + */ + public DefaultGeographicDescription(final Identifier value) { + geographicIdentifier = value; + } + /** * Creates an inclusive geographic description initialized to the given identifier. * This constructor sets the {@linkplain #getInclusion() inclusion} property to {@code true}. @@ -118,11 +131,14 @@ public class DefaultGeographicDescription extends AbstractGeographicExtent imple * @param description the natural language description of the meaning of the code value, or {@code null} if none. * * @since 0.6 + * + * @deprecated This constructor applies too arbitrary rules. */ + @Deprecated(since = "1.5", forRemoval = true) public DefaultGeographicDescription(final CharSequence description) { super(true); if (description != null) { - final DefaultIdentifier id = new DefaultIdentifier(); + final var id = new DefaultIdentifier(); if (CharSequences.isUnicodeIdentifier(description)) { id.setCode(description.toString()); if (description instanceof InternationalString) { diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/extent/Extents.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/extent/Extents.java index e4a5d16c39..aafadf5498 100644 --- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/extent/Extents.java +++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/extent/Extents.java @@ -18,7 +18,6 @@ package org.apache.sis.metadata.iso.extent; import java.util.Date; import java.util.Locale; -import java.util.List; import java.util.ArrayList; import java.util.Set; import java.util.LinkedHashSet; @@ -58,6 +57,7 @@ import org.opengis.referencing.operation.CoordinateOperation; import org.apache.sis.metadata.InvalidMetadataException; import org.apache.sis.metadata.privy.ReferencingServices; import org.apache.sis.metadata.iso.ISOMetadata; +import org.apache.sis.measure.Latitude; import org.apache.sis.measure.Longitude; import org.apache.sis.measure.MeasurementRange; import org.apache.sis.measure.Range; @@ -120,10 +120,14 @@ public final class Extents extends Static { /** * A geographic extent ranging from 180°W to 180°E and 90°S to 90°N. * This extent has no vertical and no temporal components. + * + * @see #isWorld(Extent) */ public static final Extent WORLD; static { - final var box = new DefaultGeographicBoundingBox(-180, 180, -90, 90); + final var box = new DefaultGeographicBoundingBox( + Longitude.MIN_VALUE, Longitude.MAX_VALUE, + Latitude.MIN_VALUE, Latitude.MAX_VALUE); box.transitionTo(DefaultGeographicBoundingBox.State.FINAL); var world = new DefaultExtent(Vocabulary.formatInternational(Vocabulary.Keys.World), box, null, null); world.transitionTo(DefaultExtent.State.FINAL); @@ -276,7 +280,7 @@ public final class Extents extends Static { if (extent == null) { return null; } - final Extents m = new Extents(); + final var m = new Extents(); try { m.addHorizontal(extent); } catch (TransformException e) { @@ -294,14 +298,14 @@ public final class Extents extends Static { */ private void addHorizontal(final Extent extent) throws TransformException { boolean useOnlyGeographicEnvelopes = false; - final List<Envelope> fallbacks = new ArrayList<>(); + final var fallbacks = new ArrayList<Envelope>(); for (final GeographicExtent element : nonNull(extent.getGeographicElements())) { /* * If a geographic bounding box can be obtained, add it to the previous boxes (if any). * All exclusion boxes before the first inclusion box are ignored. */ if (element instanceof GeographicBoundingBox) { - final GeographicBoundingBox item = (GeographicBoundingBox) element; + final var item = (GeographicBoundingBox) element; if (bounds == null) { /* * We use DefaultGeographicBoundingBox.getInclusion(Boolean) below because @@ -846,4 +850,34 @@ public final class Extents extends Static { if (result.equals(e2, ComparisonMode.BY_CONTRACT)) return e2; return (I) result; } + + /** + * Returns {@code true} if the given extent covers the world. + * The current implementation checks if at least one geographic bounding box has + * a latitude range of {@value Latitude#MIN_VALUE} to {@value Latitude#MAX_VALUE} and + * a longitude range of {@value Longitude#MIN_VALUE} to {@value Longitude#MAX_VALUE}. + * + * @param extent the extent to check, or {@code null} if none. + * @return whether the given extent covers the world. + * + * @see #WORLD + * @since 1.5 + */ + public static boolean isWorld(final Extent extent) { + if (extent != null) { + for (final GeographicExtent element : nonNull(extent.getGeographicElements())) { + if (element instanceof GeographicBoundingBox) { + final var item = (GeographicBoundingBox) element; + if (item.getWestBoundLongitude() <= Longitude.MIN_VALUE && + item.getEastBoundLongitude() >= Longitude.MAX_VALUE && + item.getSouthBoundLatitude() <= Latitude.MIN_VALUE && + item.getNorthBoundLatitude() >= Latitude.MAX_VALUE) + { + return true; + } + } + } + } + return false; + } } diff --git a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/MetadataCopierTest.java b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/MetadataCopierTest.java index e0609fc0de..61bfc965b1 100644 --- a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/MetadataCopierTest.java +++ b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/MetadataCopierTest.java @@ -56,9 +56,9 @@ public final class MetadataCopierTest extends TestCase { */ @Test public void testCopy() { - final MetadataCopier copier = new MetadataCopier(MetadataStandard.ISO_19115); + final var copier = new MetadataCopier(MetadataStandard.ISO_19115); final DefaultCitation original = HardCodedCitations.EPSG; - final DefaultCitation copy = (DefaultCitation) copier.copy(original); + final var copy = (DefaultCitation) copier.copy(original); assertNotSame(original, copy); assertNotSame(getSingleton(original.getCitedResponsibleParties()), getSingleton(copy.getCitedResponsibleParties())); @@ -70,7 +70,7 @@ public final class MetadataCopierTest extends TestCase { */ @Test public void testCopyWithType() { - final MetadataCopier copier = new MetadataCopier(MetadataStandard.ISO_19115); + final var copier = new MetadataCopier(MetadataStandard.ISO_19115); final DefaultCitation original = HardCodedCitations.EPSG; final Citation copy = copier.copy(Citation.class, original); assertNotSame(original, copy); @@ -85,8 +85,8 @@ public final class MetadataCopierTest extends TestCase { */ @Test public void testCopyWithSuperType() { - final MetadataCopier copier = new MetadataCopier(MetadataStandard.ISO_19115); - final DefaultGeographicDescription original = new DefaultGeographicDescription("Some area."); + final var copier = new MetadataCopier(MetadataStandard.ISO_19115); + final var original = new DefaultGeographicDescription(null, "Some area."); final GeographicExtent copy = copier.copy(GeographicExtent.class, original); assertNotSame(original, copy); assertEquals (original, copy); @@ -97,7 +97,7 @@ public final class MetadataCopierTest extends TestCase { */ @Test public void testWrongArgument() { - final MetadataCopier copier = new MetadataCopier(MetadataStandard.ISO_19115); + final var copier = new MetadataCopier(MetadataStandard.ISO_19115); final DefaultCitation original = HardCodedCitations.EPSG; var e = assertThrows(IllegalArgumentException.class, () -> copier.copy(DefaultCitation.class, original)); assertMessageContains(e, "DefaultCitation"); @@ -109,8 +109,8 @@ public final class MetadataCopierTest extends TestCase { */ @Test public void testLocaleAndCharsets() { - final MetadataCopier copier = new MetadataCopier(MetadataStandard.ISO_19115); - final DefaultMetadata original = new DefaultMetadata(); + final var copier = new MetadataCopier(MetadataStandard.ISO_19115); + final var original = new DefaultMetadata(); original.getLocalesAndCharsets().put(Locale.FRENCH, StandardCharsets.UTF_8); original.getLocalesAndCharsets().put(Locale.JAPANESE, StandardCharsets.UTF_16); final Metadata copy = copier.copy(Metadata.class, original); diff --git a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/extent/ExtentsTest.java b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/extent/ExtentsTest.java index 93928b1e3f..6a19364377 100644 --- a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/extent/ExtentsTest.java +++ b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/extent/ExtentsTest.java @@ -62,6 +62,15 @@ public final class ExtentsTest extends TestCase { public ExtentsTest() { } + /** + * Tests the {@link Extents#isWorld(Extent)} method. + */ + @Test + public void testIsWorld() { + assertTrue (Extents.isWorld(Extents.WORLD)); + assertFalse(Extents.isWorld(new DefaultExtent(null, new DefaultGeographicBoundingBox(10, 20, 30, 40), null, null))); + } + /** * Tests {@link Extents#getVerticalRange(Extent)}. * @@ -198,7 +207,7 @@ public final class ExtentsTest extends TestCase { * because it has a dependency to a referencing implementation class. */ public static void testCentroid() { - final DefaultGeographicBoundingBox bbox = new DefaultGeographicBoundingBox(140, 160, 30, 50); + final var bbox = new DefaultGeographicBoundingBox(140, 160, 30, 50); DirectPosition pos = Extents.centroid(bbox); assertEquals(150, pos.getCoordinate(0), "longitude"); assertEquals( 40, pos.getCoordinate(1), "latitude"); diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/Formatter.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/Formatter.java index 608d66edd6..828d83f699 100644 --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/Formatter.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/Formatter.java @@ -954,7 +954,7 @@ public class Formatter implements Localized { warning(e, WKTKeywords.BBox, WKTKeywords.Usage); bbox = null; } - appendOnNewLine(WKTKeywords.Area, area.getDescription(), ElementKind.EXTENT); + appendOnNewLine(WKTKeywords.Area, WKTUtilities.descriptionOfMediumLength(area), ElementKind.EXTENT); append(bbox, BBOX_ACCURACY); appendVerticalExtent(area); appendTemporalExtent(area); diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/GeodeticObjectParser.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/GeodeticObjectParser.java index 95e05df498..c8858f1611 100644 --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/GeodeticObjectParser.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/GeodeticObjectParser.java @@ -75,7 +75,6 @@ import org.apache.sis.referencing.internal.PositionalAccuracyConstant; import org.apache.sis.metadata.iso.citation.Citations; import org.apache.sis.metadata.iso.extent.DefaultExtent; import org.apache.sis.metadata.iso.extent.DefaultGeographicBoundingBox; -import org.apache.sis.metadata.iso.extent.DefaultGeographicDescription; import org.apache.sis.metadata.iso.extent.DefaultVerticalExtent; import org.apache.sis.metadata.iso.extent.DefaultTemporalExtent; import org.apache.sis.metadata.privy.AxisNames; @@ -573,7 +572,7 @@ class GeodeticObjectParser extends MathTransformParser implements Comparator<Coo if (extent == null) { extent = new DefaultExtent(area, null, null, null); } else { - extent.getGeographicElements().add(new DefaultGeographicDescription(area)); + extent.setDescription(Types.toInternationalString(area)); } } /* diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/Builder.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/Builder.java index f091311567..b10504c51a 100644 --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/Builder.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/Builder.java @@ -343,7 +343,7 @@ public abstract class Builder<B extends Builder<B>> { */ private Identifier createIdentifier(final Citation authority, final String codeSpace, final String identifier, final String version) { if (isDeprecated()) { - return new DeprecatedCode(authority, codeSpace, identifier, version, null, getRemarks()); + return new DeprecatedCode(authority, codeSpace, identifier, version, getDescription(), null, getRemarks()); } else { return new ImmutableIdentifier(authority, codeSpace, identifier, version, getDescription()); } diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/ImmutableIdentifier.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/ImmutableIdentifier.java index 864357dacc..5ab09616c4 100644 --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/ImmutableIdentifier.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/ImmutableIdentifier.java @@ -435,7 +435,7 @@ public class ImmutableIdentifier extends FormattableObject implements Identifier return true; } if (object != null && object.getClass() == getClass()) { - final ImmutableIdentifier that = (ImmutableIdentifier) object; + final var that = (ImmutableIdentifier) object; return Objects.equals(code, that.code) && Objects.equals(codeSpace, that.codeSpace) && Objects.equals(authority, that.authority) && @@ -498,7 +498,7 @@ public class ImmutableIdentifier extends FormattableObject implements Identifier * if the parameter value is the root element (in which case we will format full identifier). */ final FormattableObject enclosing = formatter.getEnclosingElement(1); - final boolean isRoot = formatter.getEnclosingElement(2) == null; + final boolean isRoot = (formatter.getEnclosingElement(2) == null); if (isRoot || !(enclosing instanceof ParameterValue<?>)) { final String citation = Identifiers.getIdentifier(authority, false); if (citation != null && !citation.equals(codeSpace)) { diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java index a18d39d05b..d4885dbd93 100644 --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java @@ -119,6 +119,7 @@ import org.apache.sis.metadata.iso.citation.DefaultCitation; import org.apache.sis.metadata.iso.citation.DefaultOnlineResource; import org.apache.sis.metadata.iso.extent.DefaultExtent; import org.apache.sis.metadata.iso.extent.DefaultGeographicBoundingBox; +import org.apache.sis.metadata.iso.extent.DefaultGeographicDescription; import org.apache.sis.metadata.iso.extent.DefaultVerticalExtent; import org.apache.sis.metadata.iso.extent.DefaultTemporalExtent; import org.apache.sis.metadata.sql.privy.SQLUtilities; @@ -1193,7 +1194,7 @@ public class EPSGDataAccess extends GeodeticAuthorityFactory implements CRSAutho * @param code the deprecated code. * @return the proposed replacement (may be the "(none)" text). Never empty. */ - private String getReplacement(final TableInfo source, final Integer code, final Locale locale) throws SQLException { + private String getReplacement(final TableInfo source, final int code, final Locale locale) throws SQLException { String reason = null; String replacedBy; search: try (ResultSet result = executeMetadataQuery("Deprecation", @@ -1233,7 +1234,43 @@ search: try (ResultSet result = executeMetadataQuery("Deprecation", } /** - * Returns the name and aliases for the {@link IdentifiedObject} to construct. + * Returns the identifier for the {@link IdentifiedObject} to construct. + * + * @param source information about the table on which a query has been executed. + * @param code the <abbr>EPSG</abbr> code of the object to construct. + * @param identifier the code to assign to the identifier, usually {@code code.toString()}. + * @param description a description associated with the identifier. May be {@code null}. + * @param deprecated {@code true} if the object to create is deprecated. + * @return an identifier for the object to create. + */ + private Identifier createIdentifier(final TableInfo source, final int code, final String identifier, + final InternationalString description, final Locale locale, final boolean deprecated) + throws SQLException, FactoryException + { + final Citation authority = owner.getAuthority(); + final String version = Types.toString(authority.getEdition(), locale); + if (deprecated) { + final String replacedBy = getReplacement(source, code, locale); + return new DeprecatedCode( + authority, + Constants.EPSG, + identifier, + version, + description, + Character.isDigit(replacedBy.charAt(0)) ? replacedBy : null, + Vocabulary.formatInternational(Vocabulary.Keys.SupersededBy_1, replacedBy)); + } else { + return new ImmutableIdentifier( + authority, + Constants.EPSG, + identifier, + version, + description); + } + } + + /** + * Returns the identifier, name and aliases for the {@link IdentifiedObject} to construct. * * <h4>Possible recursive calls</h4> * Invoking this method may cause a recursive call to {@link #createCoordinateReferenceSystem(String)} @@ -1358,29 +1395,14 @@ search: try (ResultSet result = executeMetadataQuery("Deprecation", */ properties.clear(); properties.put(IdentifiedObject.NAME_KEY, nameAsIdentifier); + properties.put(IdentifiedObject.IDENTIFIERS_KEY, createIdentifier( + source, code, code.toString(), scopedName.toInternationalString(), locale, deprecated)); if (!aliases.isEmpty()) { properties.put(IdentifiedObject.ALIAS_KEY, aliases.toArray(GenericName[]::new)); } - final ImmutableIdentifier identifier; if (deprecated) { properties.put(AbstractIdentifiedObject.DEPRECATED_KEY, Boolean.TRUE); - final String replacedBy = getReplacement(source, code, locale); - identifier = new DeprecatedCode( - authority, - Constants.EPSG, - code.toString(), - version, - Character.isDigit(replacedBy.charAt(0)) ? replacedBy : null, - Vocabulary.formatInternational(Vocabulary.Keys.SupersededBy_1, replacedBy)); - } else { - identifier = new ImmutableIdentifier( - authority, - Constants.EPSG, - code.toString(), - version, - scopedName.toInternationalString()); } - properties.put(IdentifiedObject.IDENTIFIERS_KEY, identifier); properties.put(IdentifiedObject.REMARKS_KEY, remarks); properties.put(AbstractIdentifiedObject.LOCALE_KEY, locale); properties.put(ReferencingFactoryContainer.MT_FACTORY, owner.mtFactory); @@ -2320,29 +2342,35 @@ search: try (ResultSet result = executeMetadataQuery("Deprecation", final QueryID previousSingletonQuery = currentSingletonQuery; try (ResultSet result = executeSingletonQuery( TableInfo.EXTENT, - "SELECT"+ /* column 1 */ " EXTENT_DESCRIPTION," - + /* column 2 */ " BBOX_SOUTH_BOUND_LAT," - + /* column 3 */ " BBOX_NORTH_BOUND_LAT," - + /* column 4 */ " BBOX_WEST_BOUND_LON," - + /* column 5 */ " BBOX_EAST_BOUND_LON," - + /* column 6 */ " VERTICAL_EXTENT_MIN," - + /* column 7 */ " VERTICAL_EXTENT_MAX," - + /* column 8 */ " VERTICAL_EXTENT_CRS_CODE," - + /* column 9 */ " TEMPORAL_EXTENT_BEGIN," - + /* column 10 */ " TEMPORAL_EXTENT_END" + "SELECT"+ /* column 1 */ " EXTENT_CODE," + + /* column 2 */ " EXTENT_NAME," + + /* column 3 */ " EXTENT_DESCRIPTION," + + /* column 4 */ " BBOX_SOUTH_BOUND_LAT," + + /* column 5 */ " BBOX_NORTH_BOUND_LAT," + + /* column 6 */ " BBOX_WEST_BOUND_LON," + + /* column 7 */ " BBOX_EAST_BOUND_LON," + + /* column 8 */ " VERTICAL_EXTENT_MIN," + + /* column 9 */ " VERTICAL_EXTENT_MAX," + + /* column 10 */ " VERTICAL_EXTENT_CRS_CODE," + + /* column 11 */ " TEMPORAL_EXTENT_BEGIN," + + /* column 12 */ " TEMPORAL_EXTENT_END," + + /* column 13 */ " DEPRECATED" + " FROM \"Extent\"" + " WHERE EXTENT_CODE = ?", code)) { while (result.next()) { - final String description = getOptionalString(result, 1); - double ymin = getOptionalDouble (result, 2); - double ymax = getOptionalDouble (result, 3); - double xmin = getOptionalDouble (result, 4); - double xmax = getOptionalDouble (result, 5); - double zmin = getOptionalDouble (result, 6); - double zmax = getOptionalDouble (result, 7); - Temporal tmin = getOptionalTemporal(result, 9, "createExtent"); - Temporal tmax = getOptionalTemporal(result, 10, "createExtent"); + Integer epsg = getOptionalInteger (result, 1); + String name = getOptionalString (result, 2); + String description = getOptionalString (result, 3); + double ymin = getOptionalDouble (result, 4); + double ymax = getOptionalDouble (result, 5); + double xmin = getOptionalDouble (result, 6); + double xmax = getOptionalDouble (result, 7); + double zmin = getOptionalDouble (result, 8); + double zmax = getOptionalDouble (result, 9); + Temporal tmin = getOptionalTemporal(result, 11, "createExtent"); + Temporal tmax = getOptionalTemporal(result, 12, "createExtent"); + boolean deprecated = getOptionalBoolean (result, 13); DefaultGeographicBoundingBox bbox = null; if (!(Double.isNaN(ymin) && Double.isNaN(ymax) && Double.isNaN(xmin) && Double.isNaN(xmax))) { /* @@ -2362,7 +2390,7 @@ search: try (ResultSet result = executeMetadataQuery("Deprecation", DefaultVerticalExtent vertical = null; if (!(Double.isNaN(zmin) && Double.isNaN(zmax))) { vertical = new DefaultVerticalExtent(zmin, zmax, null); - final Integer crs = getOptionalInteger(result, 8); + final Integer crs = getOptionalInteger(result, 10); if (crs != null) { var c = new QueryID("Coordinate Reference System", new int[] {crs}, currentSingletonQuery); if (!c.isAlreadyInProgress()) { @@ -2375,6 +2403,10 @@ search: try (ResultSet result = executeMetadataQuery("Deprecation", temporal = new DefaultTemporalExtent(tmin, tmax); } var extent = new DefaultExtent(description, bbox, vertical, temporal); + if (epsg != null && name != null) { // Should never be null, but we don't need to be strict. + var identifier = createIdentifier(TableInfo.EXTENT, epsg, name, null, getLocale(), deprecated); + extent.getGeographicElements().add(new DefaultGeographicDescription(identifier)); + } if (!extent.isEmpty()) { returnValue = ensureSingleton(extent, returnValue, code); } diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/DeprecatedCode.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/DeprecatedCode.java index 710980a629..d5b62fe4be 100644 --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/DeprecatedCode.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/DeprecatedCode.java @@ -16,6 +16,7 @@ */ package org.apache.sis.referencing.internal; +import java.util.Objects; import org.opengis.util.InternationalString; import org.opengis.metadata.citation.Citation; import org.apache.sis.referencing.ImmutableIdentifier; @@ -27,12 +28,7 @@ import java.util.Optional; /** * An identifier which should not be used anymore. - * This is used mostly for deprecated EPSG codes. - * - * <h2>Implementation note</h2> - * This class opportunistically recycles the {@linkplain #getDescription() description} property into a - * {@linkplain #getRemarks() remarks} property. This is a lazy way to inherit {@link #equals(Object)} and - * {@link #hashCode()} implementations without adding code in this class for taking in account a new field. + * This is used mostly for deprecated <abbr>EPSG</abbr> codes. * * @author Martin Desruisseaux (Geomatys) */ @@ -40,13 +36,19 @@ public final class DeprecatedCode extends ImmutableIdentifier implements Depreca /** * For cross-version compatibility. */ - private static final long serialVersionUID = 357222258307746767L; + private static final long serialVersionUID = 8186104136524932846L; /** * The replacement for the deprecated object, or {@code null} if none. */ public final String replacedBy; + /** + * Comments on or information about why this identifier is deprecated, or {@code null} if none. + */ + @SuppressWarnings("serial") // Most SIS implementations are serializable. + private final InternationalString remarks; + /** * Creates a deprecated identifier. * @@ -54,15 +56,17 @@ public final class DeprecatedCode extends ImmutableIdentifier implements Depreca * @param codeSpace name or identifier of the person or organization responsible for namespace. * @param code identifier code or name, optionally from a controlled list or pattern defined by a code space. * @param version the version of the associated code space or code as specified by the code authority, or {@code null} if none. + * @param description a description associated with the identifier. May be {@code null}. * @param replacedBy the replacement for the deprecated object, or {@code null} if none. * @param remarks comments on or information about why this identifier is deprecated, or {@code null} if none. */ public DeprecatedCode(final Citation authority, final String codeSpace, - final String code, final String version, final String replacedBy, - InternationalString remarks) + final String code, final String version, final InternationalString description, + final String replacedBy, final InternationalString remarks) { super(authority, codeSpace, code, version, remarks); this.replacedBy = replacedBy; + this.remarks = remarks; } /** @@ -84,17 +88,26 @@ public final class DeprecatedCode extends ImmutableIdentifier implements Depreca */ @Override public Optional<InternationalString> getRemarks() { - return Optional.ofNullable(super.getDescription()); + return Optional.ofNullable(remarks); } /** - * Returns {@code null}, since we used the description for the superseded information. - * See <q>Implementation note</q> in class javadoc. - * - * @return {@code null}. + * Returns an hash code value for this identifier. + */ + @Override + public int hashCode() { + return super.hashCode() + 37 * Objects.hash(replacedBy, remarks); + } + + /** + * Tests whether this object is equal to the given object. */ @Override - public InternationalString getDescription() { - return null; + public boolean equals(final Object obj) { + if (super.equals(obj)) { + final var other = (DeprecatedCode) obj; + return Objects.equals(replacedBy, other.replacedBy) && Objects.equals(remarks, other.remarks); + } + return false; } } diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/WKTUtilities.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/WKTUtilities.java index 8bff5af18a..9b4c150fea 100644 --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/WKTUtilities.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/WKTUtilities.java @@ -22,6 +22,11 @@ import java.util.logging.Logger; import javax.measure.Unit; import javax.measure.Quantity; import javax.measure.quantity.Angle; +import org.opengis.metadata.Identifier; +import org.opengis.metadata.extent.Extent; +import org.opengis.metadata.extent.GeographicExtent; +import org.opengis.metadata.extent.GeographicDescription; +import org.opengis.util.InternationalString; import org.opengis.parameter.ParameterValue; import org.opengis.parameter.ParameterValueGroup; import org.opengis.parameter.GeneralParameterValue; @@ -31,6 +36,7 @@ import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.opengis.referencing.cs.CoordinateSystem; import org.opengis.referencing.operation.Matrix; import org.opengis.referencing.operation.MathTransform; +import org.apache.sis.metadata.iso.extent.Extents; import org.apache.sis.referencing.IdentifiedObjects; import org.apache.sis.referencing.crs.AbstractCRS; import org.apache.sis.referencing.datum.DatumOrEnsemble; @@ -44,6 +50,7 @@ import org.apache.sis.io.wkt.FormattableObject; import org.apache.sis.io.wkt.Formatter; import org.apache.sis.measure.Units; import org.apache.sis.util.CharSequences; +import org.apache.sis.util.SimpleInternationalString; import org.apache.sis.util.resources.Vocabulary; import org.apache.sis.util.privy.Constants; import org.apache.sis.util.privy.Numerics; @@ -51,9 +58,6 @@ import org.apache.sis.math.DecimalFunctions; import org.apache.sis.math.Statistics; import org.apache.sis.math.Vector; -// Specific to the geoapi-3.1 and geoapi-4.0 branches: -import org.opengis.metadata.Identifier; - /** * Utility methods for referencing WKT formatting. @@ -467,4 +471,38 @@ fill: for (;;) { } return numbers; } + + /** + * Returns a description of the given extent. If the description is too long, tries to find a shorter one. + * In the particular case of extents created by {@link org.apache.sis.referencing.factory.sql.EPSGDataAccess}, + * a shorter name is available as a geographic identifier in the <abbr>EPSG</abbr> namespace. + * This is a name similar to the name of an {@link IdentifiedObject}, which is also an {@link Identifier}. + * In the latter case, this method checks that the first character is a letter for avoiding to return a + * numerical <abbr>EPSG</abbr> code. + * + * @param extent the extent from which to get a description. + * @return description of medium length for the given extent, or {@code null} if none. + */ + public static InternationalString descriptionOfMediumLength(final Extent extent) { + InternationalString description = extent.getDescription(); + if (description != null && description.length() > 255) { // 255 is the limit recommended by ISO 19162:2019. + if (Extents.isWorld(extent)) { + description = Extents.WORLD.getDescription(); + } else { + for (GeographicExtent element : extent.getGeographicElements()) { + if (element instanceof GeographicDescription) { + Identifier id = ((GeographicDescription) element).getGeographicIdentifier(); + if (id != null && Constants.EPSG.equalsIgnoreCase(id.getCodeSpace())) { + String name = id.getCode(); + if (name != null && !name.isEmpty() && Character.isLetter(name.codePointAt(0))) { + description = new SimpleInternationalString(name); + break; + } + } + } + } + } + } + return description; + } } diff --git a/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/MetadataReader.java b/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/MetadataReader.java index 46f308255b..135d8acb69 100644 --- a/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/MetadataReader.java +++ b/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/MetadataReader.java @@ -748,7 +748,7 @@ split: while ((start = CharSequences.skipLeadingWhitespaces(value, start, lengt * @return {@code true} if at least one numerical value has been added. */ private boolean addExtent() { - addExtent(stringValue(GEOGRAPHIC_IDENTIFIER)); + addExtent(null, stringValue(GEOGRAPHIC_IDENTIFIER)); final double[] extent = new double[4]; /* * If at least one geographic coordinate is available, add a GeographicBoundingBox. diff --git a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/FeatureQuery.java b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/FeatureQuery.java index 4412e94419..83ff095436 100644 --- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/FeatureQuery.java +++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/FeatureQuery.java @@ -267,7 +267,8 @@ public class FeatureQuery extends Query implements Cloneable, Emptiable, Seriali for (int i=0; i<properties.length; i++) { final NamedExpression c = properties[i]; ArgumentChecks.ensureNonNullElement("properties", i, c); - Object key = (c.alias != null) ? c.alias : c.expression; + GenericName alias = c.alias(); + Object key = (alias != null) ? alias : c.expression(); final Integer p = uniques.putIfAbsent(key, i); if (p != null) { if (key instanceof Expression) { @@ -540,7 +541,7 @@ public class FeatureQuery extends Query implements Cloneable, Emptiable, Seriali * @version 1.5 * @since 1.1 */ - public static class NamedExpression implements Serializable { + public static final class NamedExpression implements Serializable { /** * For cross-version compatibility. */ @@ -549,13 +550,19 @@ public class FeatureQuery extends Query implements Cloneable, Emptiable, Seriali /** * The literal, value reference or more complex expression to be retrieved by a {@code Query}. * Never {@code null}. + * + * @deprecated Replaced by {@link #expression()} in preparation for making {@code NamedExpression} a record. */ + @Deprecated(since = "1.5") @SuppressWarnings("serial") public final Expression<? super Feature, ?> expression; /** * The name to assign to the expression result, or {@code null} if unspecified. + * + * @deprecated Replaced by {@link #alias()} in preparation for making {@code NamedExpression} a record. */ + @Deprecated(since = "1.5") @SuppressWarnings("serial") // Most SIS implementations are serializable. public final GenericName alias; @@ -565,8 +572,11 @@ public class FeatureQuery extends Query implements Cloneable, Emptiable, Seriali * a feature {@link Operation}. The latter are commonly called "computed fields" and are equivalent * to SQL {@code GENERATED ALWAYS} keyword for columns. * + * @deprecated Replaced by {@link #type()} in preparation for making {@code NamedExpression} a record. + * * @since 1.4 */ + @Deprecated(since = "1.5") public final ProjectionType type; /** @@ -638,6 +648,37 @@ public class FeatureQuery extends Query implements Cloneable, Emptiable, Seriali return false; } + /** + * The literal, value reference or more complex expression to be retrieved by a {@code Query}. + * Never {@code null}. + * + * @since 1.5 + */ + public Expression<? super Feature, ?> expression() { + return expression; + } + + /** + * The name to assign to the expression result, or {@code null} if unspecified. + * + * @since 1.5 + */ + public GenericName alias() { + return alias; + } + + /** + * Whether the expression result should be stored or evaluated every times that it is requested. + * A stored value will exist as a feature {@link Attribute}, while a virtual value will exist as + * a feature {@link Operation}. The latter are commonly called "computed fields" and are equivalent + * to SQL {@code GENERATED ALWAYS} keyword for columns. + * + * @since 1.5 + */ + public ProjectionType type() { + return type; + } + /** * Returns a hash code value for this column. * @@ -732,7 +773,7 @@ public class FeatureQuery extends Query implements Cloneable, Emptiable, Seriali Set<String> xpaths = ListingPropertyVisitor.xpaths(selection, null); if (projection != null) { for (NamedExpression e : projection) { - xpaths = ListingPropertyVisitor.xpaths(e.expression, xpaths); + xpaths = ListingPropertyVisitor.xpaths(e.expression(), xpaths); } } return xpaths; @@ -763,7 +804,7 @@ public class FeatureQuery extends Query implements Cloneable, Emptiable, Seriali for (int column = 0; column < projection.length; column++) { final NamedExpression item = projection[column]; if (!item.addTo(builder)) { - final var name = item.expression.getFunctionName().toInternationalString(); + final var name = item.expression().getFunctionName().toInternationalString(); throw new InvalidFilterValueException(Resources.forLocale(locale) .getString(Resources.Keys.InvalidExpression_2, column, name)); } diff --git a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/MetadataBuilder.java b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/MetadataBuilder.java index 657102d957..08ecac8507 100644 --- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/MetadataBuilder.java +++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/MetadataBuilder.java @@ -1729,22 +1729,19 @@ public class MetadataBuilder { } /** - * Adds a geographic extent described by an identifier. The given identifier is stored as-is as - * the natural language description, and possibly in a modified form as the geographic identifier. - * See {@link DefaultGeographicDescription#DefaultGeographicDescription(CharSequence)} for details. - * Storage locations are: + * Adds a geographic extent described by an identifier. + * Storage location is: * * <ul> - * <li>{@code metadata/identificationInfo/extent/geographicElement/description}</li> * <li>{@code metadata/identificationInfo/extent/geographicElement/identifier}</li> * </ul> * - * @param identifier identifier or description of spatial and temporal extent, or {@code null} for no-operation. + * @param authority the authority of the identifier code, or {@code null} if none. + * @param code the identifier code used to represent a geographic area, or {@code null} if none. */ - public final void addExtent(final CharSequence identifier) { - final InternationalString i18n = trim(identifier); - if (i18n != null) { - addIfNotPresent(extent().getGeographicElements(), new DefaultGeographicDescription(identifier)); + public final void addExtent(final Citation authority, final String identifier) { + if (authority != null || identifier != null) { + addIfNotPresent(extent().getGeographicElements(), new DefaultGeographicDescription(authority, identifier)); } } diff --git a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/Locales.java b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/Locales.java index f0ca82de40..439fdd0b05 100644 --- a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/Locales.java +++ b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/Locales.java @@ -32,9 +32,8 @@ import static org.apache.sis.util.resources.IndexedResourceBundle.LOGGER; /** - * Static methods working on {@link Locale} instances. While this class is documented as - * providing static methods, a few methods are actually non-static. Those methods need to be - * invoked on the {@link #ALL} or {@link #SIS} instance in order to specify the scope. + * Utility methods working on {@link Locale} instances. + * Non-static methods need to be invoked on the {@link #ALL} or {@link #SIS} instance in order to specify the scope. * Examples: * * {@snippet lang="java" : diff --git a/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/ShapefileStore.java b/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/ShapefileStore.java index 9c2559444b..ccccc16427 100644 --- a/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/ShapefileStore.java +++ b/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/ShapefileStore.java @@ -590,9 +590,9 @@ public final class ShapefileStore extends DataStore implements WritableFeatureSe if (projection != null) { properties = ListingPropertyVisitor.xpaths(selection, properties); for (FeatureQuery.NamedExpression ne : projection) { - properties = ListingPropertyVisitor.xpaths(ne.expression, properties); - simpleSelection &= (ne.alias == null); - simpleSelection &= (ne.expression.getFunctionName().tip().toString().equals(FunctionNames.ValueReference)); + properties = ListingPropertyVisitor.xpaths(ne.expression(), properties); + simpleSelection &= (ne.alias() == null); + simpleSelection &= (ne.expression().getFunctionName().tip().toString().equals(FunctionNames.ValueReference)); } //if link fields are referenced, add target fields diff --git a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/CellFormat.java b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/CellFormat.java index 8d1b562306..8259fc5d69 100644 --- a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/CellFormat.java +++ b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/CellFormat.java @@ -158,10 +158,15 @@ final class CellFormat extends SimpleStringProperty { /** * Tries to reduce the size of the pattern used for formatting the numbers. * This method is invoked when the numbers are too large for the cell width. + * The method should be invoked soon after a call to a {@code format(…)} method, + * because its behavior depends on which value was formatted last time. * * @return whether the pattern has been made smaller. */ final boolean shorterPattern() { + if (Double.isNaN(lastValue)) { + return false; + } int n = cellFormat.getMaximumFractionDigits() - 1; if (n >= 0) { cellFormat.setMaximumFractionDigits(n);
