This is an automated email from the ASF dual-hosted git repository. desruisseaux pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/sis.git
commit a833e1da47405df451049d96a020a4ca4036af79 Merge: 7f0548fd16 cccbaee7b7 Author: Martin Desruisseaux <martin.desruisse...@geomatys.com> AuthorDate: Wed Dec 4 15:11:34 2024 +0100 Merge branch 'geoapi-3.1' .../org.apache.sis.feature/main/module-info.java | 2 + .../org/apache/sis/feature/FeatureOperations.java | 7 +- .../sis/feature/GroupAsPolylineOperation.java | 4 +- .../apache/sis/feature/StringJoinOperation.java | 14 +- .../sis/feature/builder/FeatureTypeBuilder.java | 13 +- .../org/apache/sis/feature/internal/Resources.java | 2 +- .../sis/feature/internal/Resources.properties | 2 +- .../sis/filter/sqlmm/GeometryConstructor.java | 8 +- .../apache/sis/geometry/wrapper/Capability.java | 44 ++ .../apache/sis/geometry/wrapper/Dimensions.java | 100 ++++ .../apache/sis/geometry/wrapper/Geometries.java | 216 ++++--- .../apache/sis/geometry/wrapper/GeometryType.java | 116 +++- .../sis/geometry/wrapper/StandardGeometries.java | 53 +- .../apache/sis/geometry/wrapper/esri/Factory.java | 211 +++++-- .../apache/sis/geometry/wrapper/esri/Wrapper.java | 2 +- .../apache/sis/geometry/wrapper/j2d/Factory.java | 114 ++-- .../apache/sis/geometry/wrapper/jts/Factory.java | 449 +++++++++------ .../apache/sis/geometry/wrapper/jts/Wrapper.java | 78 ++- .../sis/filter/BinarySpatialFilterTestCase.java | 21 +- .../apache/sis/filter/sqlmm/RegistryTestCase.java | 24 +- .../sis/geometry/wrapper/GeometriesTestCase.java | 32 +- .../sis/geometry/wrapper/esri/FactoryTest.java | 34 ++ .../sis/geometry/wrapper/jts/WrapperTest.java | 78 +++ .../org/apache/sis/metadata/sql/privy/Syntax.java | 2 +- .../main/org/apache/sis/io/wkt/doc-files/ESRI.txt | 2 +- .../apache/sis/referencing/cs/AxesConvention.java | 13 + .../org/apache/sis/referencing/cs/AxisFilter.java | 2 +- .../sis/referencing/privy/AxisDirections.java | 47 +- .../test/org/apache/sis/io/wkt/ExtraCRS.txt | 2 +- .../test/org/apache/sis/io/wkt/Malformed.txt | 2 +- .../apache/sis/storage/netcdf/base/FeatureSet.java | 24 +- .../apache/sis/storage/sql/feature/Analyzer.java | 9 +- .../apache/sis/storage/sql/feature/Database.java | 1 - .../sis/storage/sql/feature/FeatureAdapter.java | 11 +- .../apache/sis/storage/sql/feature/PrimaryKey.java | 12 + .../sis/storage/sql/feature/QueryAnalyzer.java | 6 +- .../apache/sis/storage/sql/feature/Relation.java | 17 +- .../org/apache/sis/storage/sql/feature/Table.java | 4 +- .../sis/storage/sql/feature/TableAnalyzer.java | 10 +- .../main/org/apache/sis/storage/gpx/Reader.java | 4 +- .../sis/storage/DataStoreContentException.java | 2 +- .../sis/storage/csv/MovingFeatureBuilder.java | 7 +- .../main/org/apache/sis/storage/csv/Store.java | 3 +- .../apache/sis/storage/image/WorldFileStore.java | 2 +- .../src/org.apache.sis.util/main/module-info.java | 11 + .../apache/sis/converter/ConverterRegistry.java | 12 +- .../org/apache/sis/converter/DateConverter.java | 51 +- .../org/apache/sis/converter/InstantConverter.java | 83 +++ .../org/apache/sis/converter/StringConverter.java | 82 +++ .../main/org/apache/sis/math/FunctionProperty.java | 4 +- .../main/org/apache/sis/math/Vector.java | 2 + .../main/org/apache/sis/util/ArraysExt.java | 17 +- .../org/apache/sis/storage/gimi/GimiProvider.java | 2 +- .../main/org/apache/sis/storage/gimi/Group.java | 3 +- .../org/apache/sis/storage/gimi/ResourceGrid.java | 30 +- .../storage/gimi/ResourceImageUncompressed.java | 6 +- .../apache/sis/storage/gimi/ResourcePyramid.java | 12 +- .../apache/sis/storage/gimi/ResourceUnknown.java | 2 +- .../storage/gimi/internal/MatrixGridRessource.java | 28 +- .../isobmff/gimi/ModelTransformationProperty.java | 2 +- .../isobmff/gimi/TiledImageConfigurationBox.java | 1 - .../gimi/isobmff/gimi/WellKnownText2Property.java | 4 +- .../org/apache/sis/storage/gsf/StructClass.java | 10 + .../org/apache/sis/storage/gsf/SwathBathyPing.java | 8 +- optional/build.gradle.kts | 2 + .../org.apache.sis.storage.DataStoreProvider | 2 +- .../org/apache/sis/storage/gdal/ErrorHandler.java | 8 +- .../apache/sis/storage/gdal/FeatureIterator.java | 493 ++++++++++++++++ .../org/apache/sis/storage/gdal/FeatureLayer.java | 309 ++++++++++ .../org/apache/sis/storage/gdal/FieldAccessor.java | 628 +++++++++++++++++++++ .../main/org/apache/sis/storage/gdal/GDAL.java | 31 +- .../org/apache/sis/storage/gdal/GDALStore.java | 103 +++- .../main/org/apache/sis/storage/gdal/OGR.java | 341 +++++++++++ .../main/org/apache/sis/storage/gdal/Opener.java | 38 +- .../org/apache/sis/storage/gdal/SpatialRef.java | 138 ++++- .../org/apache/sis/storage/gdal/TiledResource.java | 8 +- .../org/apache/sis/storage/gdal/package-info.java | 13 +- .../apache/sis/storage/panama/NativeFunctions.java | 14 +- .../org/apache/sis/storage/panama/Resources.java | 5 + .../apache/sis/storage/panama/Resources.properties | 1 + .../sis/storage/panama/Resources_fr.properties | 1 + .../org/apache/sis/storage/gdal/GDALStoreTest.java | 2 +- 82 files changed, 3726 insertions(+), 587 deletions(-) diff --cc endorsed/src/org.apache.sis.feature/main/module-info.java index 2dfebdce96,3a65be6345..d6907a8a0b --- a/endorsed/src/org.apache.sis.feature/main/module-info.java +++ b/endorsed/src/org.apache.sis.feature/main/module-info.java @@@ -58,6 -54,8 +58,7 @@@ module org.apache.sis.feature org.apache.sis.storage.netcdf, org.apache.sis.storage.shapefile, // In the "incubator" sub-project. org.apache.sis.portrayal, - org.apache.sis.portrayal.map, // In the "incubator" sub-project. + org.apache.sis.storage.gdal, // In the "optional" sub-project. org.apache.sis.gui; // In the "optional" sub-project. exports org.apache.sis.geometry.wrapper to @@@ -65,7 -63,10 +66,8 @@@ org.apache.sis.storage.xml, org.apache.sis.storage.sql, org.apache.sis.storage.netcdf, + org.apache.sis.storage.gdal, // In the "optional" sub-project. - org.apache.sis.storage.shapefile, // In the "incubator" sub-project. - org.apache.sis.portrayal.map, // In the "incubator" sub-project. - org.apache.sis.cql; // In the "incubator" sub-project. + org.apache.sis.storage.shapefile; // In the "incubator" sub-project. exports org.apache.sis.geometry.wrapper.j2d to org.apache.sis.gui; // In the "optional" sub-project. diff --cc endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/FeatureOperations.java index ef10b003fa,4ab41f8df7..2dc4406318 --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/FeatureOperations.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/FeatureOperations.java @@@ -210,9 -205,8 +208,8 @@@ public final class FeatureOperations ex * * @see <a href="https://en.wikipedia.org/wiki/Compound_key">Compound key on Wikipedia</a> */ - public static Operation compound(final Map<String,?> identification, final String delimiter, - final String prefix, final String suffix, final PropertyType... singleAttributes) + public static AbstractOperation compound(final Map<String,?> identification, final String delimiter, + final String prefix, final String suffix, final AbstractIdentifiedType... singleAttributes) - throws UnconvertibleObjectException { ArgumentChecks.ensureNonEmpty("delimiter", delimiter); if (delimiter.indexOf(StringJoinOperation.ESCAPE) >= 0) { diff --cc endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/StringJoinOperation.java index 77d239cb1f,b702dc10e9..0fce5a316d --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/StringJoinOperation.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/StringJoinOperation.java @@@ -163,12 -176,14 +163,14 @@@ final class StringJoinOperation extend * It is caller's responsibility to ensure that {@code delimiter} and {@code singleAttributes} are not null. * This private constructor does not verify that condition on the assumption that the public API did. * + * @throws UnconvertibleObjectException if at least one attributes is not convertible from a string. + * @throws IllegalArgumentException if the operation failed for another reason. + * - * @see FeatureOperations#compound(Map, String, String, String, PropertyType...) + * @see FeatureOperations#compound(Map, String, String, String, AbstractIdentifiedType...) */ @SuppressWarnings({"rawtypes", "unchecked"}) // Generic array creation. StringJoinOperation(final Map<String,?> identification, final String delimiter, - final String prefix, final String suffix, final PropertyType[] singleAttributes) + final String prefix, final String suffix, final AbstractIdentifiedType[] singleAttributes) - throws UnconvertibleObjectException { super(identification); attributeNames = new String[singleAttributes.length]; @@@ -221,8 -236,14 +223,14 @@@ * We need only their names and how to convert from String to their values. */ attributeNames[i] = name.toString(); - ObjectConverter<? super String, ?> converter = ObjectConverters.find( - String.class, ((DefaultAttributeType<?>) propertyType).getValueClass()); - final Class<?> valueClass = ((AttributeType<?>) propertyType).getValueClass(); ++ final Class<?> valueClass = ((DefaultAttributeType<?>) propertyType).getValueClass(); + ObjectConverter<? super String, ?> converter; + try { + converter = ObjectConverters.find(String.class, valueClass); + } catch (UnconvertibleObjectException e) { + throw new UnconvertibleObjectException(Resources.forProperties(identification) + .getString(Resources.Keys.IllegalPropertyType_2, name, valueClass), e); + } if (isAssociation) { converter = new ForFeature(converter); } diff --cc endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/builder/FeatureTypeBuilder.java index 75fd60b37e,ba638a793e..b44ee0c7d6 --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/builder/FeatureTypeBuilder.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/builder/FeatureTypeBuilder.java @@@ -854,14 -829,14 +849,14 @@@ public class FeatureTypeBuilder extend * @see #properties() * @see #getProperty(String) */ - public PropertyTypeBuilder addProperty(final PropertyType template) { + public PropertyTypeBuilder addProperty(final AbstractIdentifiedType template) { ensureNonNull("template", template); - if (template instanceof AttributeType<?>) { - return addAttribute((AttributeType<?>) template); - } else if (template instanceof FeatureAssociationRole) { - return addAssociation((FeatureAssociationRole) template); + if (template instanceof DefaultAttributeType<?>) { + return addAttribute((DefaultAttributeType<?>) template); + } else if (template instanceof DefaultAssociationRole) { + return addAssociation((DefaultAssociationRole) template); } else { - final PropertyTypeBuilder property = new OperationWrapper(this, template); + final var property = new OperationWrapper(this, template); properties.add(property); clearCache(); return property; diff --cc endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/GeometryConstructor.java index 0528807e7e,299fe553f8..8792bcc895 --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/GeometryConstructor.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/GeometryConstructor.java @@@ -120,11 -121,11 +120,11 @@@ class GeometryConstructor<R,G> extends } else { result = library.createFromComponents(operation.getGeometryType().get(), value); } - final Object geometry = library.getGeometry(result); + final Object geomImpl = library.getGeometry(result); final Class<?> expected = operation.getReturnType(library); - if (!expected.isInstance(geometry)) { + if (!expected.isInstance(geomImpl)) { - throw new InvalidFilterValueException(Errors.format( + throw new IllegalArgumentException(Errors.format( - Errors.Keys.IllegalArgumentClass_3, "geom", expected, Classes.getClass(geometry))); + Errors.Keys.IllegalArgumentClass_3, "geom", expected, Classes.getClass(geomImpl))); } if (srid != null) { final CoordinateReferenceSystem crs = getTargetCRS(input); diff --cc endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/Geometries.java index d935240840,facdd123be..fae0cd3aee --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/Geometries.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/Geometries.java @@@ -329,9 -330,11 +330,11 @@@ public abstract class Geometries<G> imp final Object geometry; final int n = point.getDimension(); switch (n) { - case 2: geometry = createPoint(point.getOrdinate(0), point.getOrdinate(1)); break; - case 3: geometry = createPoint(point.getOrdinate(0), point.getOrdinate(1), point.getOrdinate(2)); break; - default: throw new MismatchedDimensionException(Errors.format(Errors.Keys.MismatchedDimension_3, "point", (n <= 2) ? 2 : 3, n)); - case BIDIMENSIONAL: geometry = createPoint(point.getCoordinate(0), point.getCoordinate(1)); break; - case TRIDIMENSIONAL: geometry = createPoint(point.getCoordinate(0), point.getCoordinate(1), point.getCoordinate(2)); break; ++ case BIDIMENSIONAL: geometry = createPoint(point.getOrdinate(0), point.getOrdinate(1)); break; ++ case TRIDIMENSIONAL: geometry = createPoint(point.getOrdinate(0), point.getOrdinate(1), point.getOrdinate(2)); break; + default: throw new MismatchedDimensionException( + Errors.format(Errors.Keys.MismatchedDimension_3, "point", + (n <= BIDIMENSIONAL) ? BIDIMENSIONAL : TRIDIMENSIONAL, n)); } final GeometryWrapper wrapper = castOrWrap(geometry); if (point.getCoordinateReferenceSystem() != null) { diff --cc endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/jts/Wrapper.java index f6a92ba94d,c8734bea9d..7964c1e535 --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/jts/Wrapper.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/jts/Wrapper.java @@@ -61,10 -61,11 +61,11 @@@ import org.apache.sis.util.Debug import org.apache.sis.util.collection.BackingStoreException; import org.apache.sis.util.resources.Errors; import org.apache.sis.filter.sqlmm.SQLMM; + import static org.apache.sis.geometry.wrapper.GeometryType.POINT; -// Specific to the geoapi-3.1 and geoapi-4.0 branches: -import org.opengis.filter.SpatialOperatorName; -import org.opengis.filter.DistanceOperatorName; +// Specific to the main branch: +import org.apache.sis.pending.geoapi.filter.SpatialOperatorName; +import org.apache.sis.pending.geoapi.filter.DistanceOperatorName; /** diff --cc endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/BinarySpatialFilterTestCase.java index 8cf9ae7c93,38fbe42ea0..51cb616902 --- a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/BinarySpatialFilterTestCase.java +++ b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/BinarySpatialFilterTestCase.java @@@ -86,21 -88,20 +87,21 @@@ public abstract class BinarySpatialFilt /** * Creates the polygon identified by the given enumeration value. */ - private Literal<Feature,G> literal(final Polygon p) { + private Literal<AbstractFeature,G> literal(final Polygon p) { - final byte[] coordinates; + final double[] coordinates; boolean polygon = true; switch (p) { - case RIGHT: coordinates = new byte[] {5, 5, 5, 10, 10, 10, 10, 5, 5, 5}; break; - case DISTANCE_1: coordinates = new byte[] {5, +1, 10, +1, 10, 4, 5, 4, 5, +1}; break; - case DISTANCE_3: coordinates = new byte[] {5, -1, 10, -1, 10, 2, 5, 2, 5, -1}; break; - case INTERSECT: coordinates = new byte[] {7, 3, 9, 3, 9, 6, 7, 6, 7, 3}; break; - case CONTAINS: coordinates = new byte[] {1, 1, 11, 1, 11, 20, 1, 20, 1, 1}; break; - case CROSSES: coordinates = new byte[] {4, 6, 7, 8, 12, 9}; polygon = false; break; - case TOUCHES: coordinates = new byte[] {4, 2, 7, 5, 9, 3}; polygon = false; break; + case RIGHT: coordinates = new double[] {5, 5, 5, 10, 10, 10, 10, 5, 5, 5}; break; + case DISTANCE_1: coordinates = new double[] {5, +1, 10, +1, 10, 4, 5, 4, 5, +1}; break; + case DISTANCE_3: coordinates = new double[] {5, -1, 10, -1, 10, 2, 5, 2, 5, -1}; break; + case INTERSECT: coordinates = new double[] {7, 3, 9, 3, 9, 6, 7, 6, 7, 3}; break; + case CONTAINS: coordinates = new double[] {1, 1, 11, 1, 11, 20, 1, 20, 1, 1}; break; + case CROSSES: coordinates = new double[] {4, 6, 7, 8, 12, 9}; polygon = false; break; + case TOUCHES: coordinates = new double[] {4, 2, 7, 5, 9, 3}; polygon = false; break; default: throw new AssertionError(p); } - return factory.literal(library.createPolyline(polygon, false, Dimensions.XY, DoubleBuffer.wrap(coordinates))); + return (Literal<AbstractFeature,G>) - factory.literal(library.createPolyline(polygon, 2, Vector.create(coordinates, false))); ++ factory.literal(library.createPolyline(polygon, false, Dimensions.XY, DoubleBuffer.wrap(coordinates))); } /** diff --cc endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/Table.java index 07abaeee41,7bb776d5cc..e2157aaafb --- 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 @@@ -227,9 -228,7 +227,7 @@@ final class Table extends AbstractFeatu * have been set to association names. If `ClassCastException` occurs here, it is a bug * in our object constructions. */ - final DefaultAssociationRole association = - (DefaultAssociationRole) featureType.getProperty(relation.propertyName); - - final var association = (FeatureAssociationRole) featureType.getProperty(relation.propertyName); ++ final var association = (DefaultAssociationRole) featureType.getProperty(relation.propertyName); final Table table = tables.get(association.getValueType().getName()); if (table == null) { throw new InternalDataStoreException(association.toString()); diff --cc endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/csv/MovingFeatureBuilder.java index 94b8d61d4b,fd3f4fadf7..ec137d47af --- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/csv/MovingFeatureBuilder.java +++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/csv/MovingFeatureBuilder.java @@@ -193,10 -194,10 +194,10 @@@ final class MovingFeatureBuilder extend * source method name and logger name, then forward to a {@code WarningListener}. */ public final <G> void storeGeometry(final String featureName, final int index, final int dimension, - final Geometries<G> factory, final Attribute<G> dest, final Consumer<LogRecord> warningListener) + final Geometries<G> factory, final AbstractAttribute<G> dest, final Consumer<LogRecord> warningListener) { int n = count[index]; - final Vector[] vectors = new Vector[n]; + final var vectors = new Vector[n]; for (Period p = properties[index]; p != null; p = p.previous) { vectors[--n] = Vector.create(p.value, false); } diff --cc endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/csv/Store.java index 17a3e7e588,9f9668999a..2022049dcd --- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/csv/Store.java +++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/csv/Store.java @@@ -553,8 -556,8 +554,8 @@@ final class Store extends URIDataStore if (dissociate) { type = double[].class; } else { - type = geometries.polylineClass; + type = geometries.getGeometryClass(GeometryType.LINESTRING); - characteristics = new AttributeType[] {MovingFeatureBuilder.TIME_AS_INSTANTS}; + characteristics = new DefaultAttributeType[] {MovingFeatureBuilder.TIME_AS_INSTANTS}; } minOccurrence = 1; maxOccurrence = 1; diff --cc optional/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/FeatureIterator.java index 0000000000,dba0932520..feb2ef7082 mode 000000,100644..100644 --- a/optional/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/FeatureIterator.java +++ b/optional/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/FeatureIterator.java @@@ -1,0 -1,493 +1,493 @@@ + /* + * 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.storage.gdal; + + import java.util.Set; + import java.util.HashSet; + import java.util.Spliterator; + import java.util.function.Consumer; + import java.util.logging.Level; + import java.util.logging.LogRecord; + import java.nio.ByteOrder; + import java.nio.DoubleBuffer; + import java.time.LocalDate; + import java.time.LocalTime; + import java.time.ZoneOffset; + import java.lang.foreign.Arena; + import java.lang.foreign.MemorySegment; + import java.lang.foreign.ValueLayout; + import java.lang.reflect.Array; + import org.apache.sis.util.privy.Numerics; + import org.apache.sis.util.privy.Constants; + import org.apache.sis.util.resources.Errors; + import org.apache.sis.storage.DataStoreException; + import org.apache.sis.storage.ConcurrentReadException; + import org.apache.sis.geometry.wrapper.Capability; + import org.apache.sis.geometry.wrapper.Dimensions; + import org.apache.sis.geometry.wrapper.Geometries; + import org.apache.sis.geometry.wrapper.GeometryType; + import org.apache.sis.storage.panama.Resources; + -// Specific to the geoapi-3.1 and geoapi-4.0 branches: -import org.opengis.feature.Feature; ++// Specific to the main branch: ++import org.apache.sis.feature.AbstractFeature; + + + /** + * Iterator over the features returned by <abbr>OGR</abbr>. + * The current implementation supports only sequential accesses. + * All usages of this iterator must be done in the same thread. + * + * @author Johann Sorel (Geomatys) + * @author Martin Desruisseaux (Geomatys) + */ -final class FeatureIterator implements Spliterator<Feature>, Runnable { ++final class FeatureIterator implements Spliterator<AbstractFeature>, Runnable { + /** + * {@code OGRwkbGeometryType} enumeration value specific to <abbr>GDAL</abbr>. + */ + private static final int LINEAR_RING = 101; + + /** + * The source of features. This object provides the feature type, + * the fields to read, the synchronization lock and the native functions. + */ + private final FeatureLayer layer; + + /** + * The number of elements, or -1 if too expansive to compute. + */ + private final long count; + + /** + * The arena used for allocating the buffers in this class, or {@code null} if none (because not needed). + */ + private Arena arena; + + /** + * A buffer where some <abbr>GDAL</abbr> functions will write their outputs, or {@code null} if not needed. + */ + private MemorySegment buffer; + + /** + * Buffers for extracting the date components of a field, or {@code null} if no date is expected. + * There is no {@code year} component because {@link #buffer} can be used directly for the years. + * + * <h4>Mapping to geometry coordinates</h4> + * The same buffer and the same slices are opportunistically used for geometry coordinates as below. + * This mapping relies on the fact that {@link Double#BYTES} is twice the value of {@link Integer#BYTES}. + * Therefore, we start with the buffer, skip one slice, take the next slice, skip one slice, <i>etc.</i> + * + * <ol> + * <li><var>x</var>: {@link #buffer}</li> + * <li><var>y</var>: {@link #day}</li> + * <li><var>z</var>: {@link #minute}</li> + * <li><var>m</var>: {@link #timezone}</li> + * </ol> + * + * @see FieldAccessor#NUM_DATE_COMPONENTS + */ + private MemorySegment month, day, hour, minute, second, timezone; + + /** + * Unsupported types encountered during the iteration. + * Used for avoiding to emit the same warning more than once. + */ + private final Set<String> unsupportedTypes; + + /** + * Whether the iterator has been initialized. After initialization, no other iteration + * can be executed on the same {@link FeatureLayer} until {@link #run()} is executed. + */ + private boolean initialized; + + /** + * Creates a new iterator for the given layer. + * + * @param layer the source of features. + * @throws Throwable if an error occurred while estimating the number of features. + */ + FeatureIterator(final FeatureLayer layer) throws DataStoreException { + this.layer = layer; + final OGR ogr = layer.OGR(); + try { + count = (long) ogr.getFeatureCount.invokeExact(layer.handle, 0); + } catch (Throwable e) { + throw GDAL.propagate(e); + } + unsupportedTypes = new HashSet<>(); + } + + /** + * Allocates the buffer if needed (if at least one field is a date/time, an array or a geometry). + * Tries to allocate only the required amount of memory. In the case of geometries, that amount is a guess. + * This method shall be invoked after construction. It is not part of the constructor for allowing the call + * to {@link #run()} if an exception it thrown. + * + * <p>This method must be invoked in a block synchronized on {@code layer.store}.</p> + * + * @throws ConcurrentReadException if another iteration is already in progress. + * @throws Throwable if an error occurred while reseting the stream. + */ + private void initialize(final OGR ogr) throws Throwable { + assert Thread.holdsLock(layer.store); + if (layer.iterationInProgress != null) { + throw new ConcurrentReadException(Resources.forLocale(layer.store.getLocale()) + .getString(Resources.Keys.UnsupportedConcurrentIteration)); + } + initialized = true; // Set now for making sure that this method is not executed twice. + int bufferSize = 0; + for (FieldAccessor<?> field : layer.fields) { + bufferSize = Math.max(bufferSize, field.bufferSize()); + } + if (bufferSize >= Integer.BYTES) { + arena = layer.store.changeArena(arena, true); + buffer = arena.allocate(bufferSize); + sliceBuffers(); + } + layer.iterationInProgress = this; + ogr.resetReading.invoke(layer.handle); // Must be done after `getFeatureCount`. + } + + /** + * Creates the memory segments for the coordinates and date components. + * This method needs to be invoked every times that {@link #buffer} changed. + */ + private void sliceBuffers() { + if (buffer.byteSize() > Integer.BYTES) { + for (int i=1; ; i++) { + final MemorySegment s = buffer.asSlice(i * Integer.BYTES, 1); + switch (i) { + default: throw new AssertionError(i); + case 1: month = s; break; + case 2: day = s; break; // Also used for Y coordinate values. + case 3: hour = s; break; + case 4: minute = s; break; // Also used for Z coordinate values. + case 5: second = s; break; + case 6: timezone = s; return; // Also used for M coordinate values. + } + } + } + } + + /** + * Do not split the iterator, because this implementation requires sequential accesses. + */ + @Override - public Spliterator<Feature> trySplit() { ++ public Spliterator<AbstractFeature> trySplit() { + return null; + } + + /** + * Returns the number of features if known, or {@link Long#MAX_VALUE} otherwise. + */ + @Override + public long estimateSize() { + return (count >= 0) ? count : Long.MAX_VALUE; + } + + /** + * Returns the number of features if known, or {@code -1} otherwise. + */ + @Override + public long getExactSizeIfKnown() { + return count; + } + + /** + * Specifies that this iterator never returns null elements. + * May also specify that the number of features is known. + */ + @Override + public int characteristics() { + return (count >= 0) ? SIZED | NONNULL : NONNULL; + } + + /** + * Fetches the next feature instance. + * + * @param action the action to perform with the next feature. + * @return whether a feature instance has been found. + */ + @Override - public boolean tryAdvance(final Consumer<? super Feature> action) { ++ public boolean tryAdvance(final Consumer<? super AbstractFeature> action) { + try { + synchronized (layer.store) { + final OGR ogr = layer.OGR(); + if (!initialized) { + initialize(ogr); + } + return advance(ogr, action); + } + } catch (Throwable e) { + throw GDAL.propagate(e); + } + } + + /** + * Executes the given action on all remaining feature instances. + * + * @param action the action to perform on all remaining features. + */ + @Override + @SuppressWarnings("empty-statement") - public void forEachRemaining(final Consumer<? super Feature> action) { ++ public void forEachRemaining(final Consumer<? super AbstractFeature> action) { + try { + synchronized (layer.store) { + final OGR ogr = layer.OGR(); + if (!initialized) { + initialize(ogr); + } + while (advance(ogr, action)); + } + } catch (Throwable e) { + throw GDAL.propagate(e); + } + } + + /** + * Executes the given action on the next feature instance. + * + * @param ogr pointers to native functions. + * @param action the action to perform with the next feature. + * @return whether a feature instance has been found. + * @throws Throwable if a call to a native method failed. + */ - private boolean advance(final OGR ogr, final Consumer<? super Feature> action) throws Throwable { ++ private boolean advance(final OGR ogr, final Consumer<? super AbstractFeature> action) throws Throwable { + final MemorySegment handle = (MemorySegment) ogr.getNextFeature.invokeExact(layer.handle); + if (GDAL.isNull(handle)) { + return false; + } - final Feature feature; ++ final AbstractFeature feature; + try { + feature = layer.type.newInstance(); + for (FieldAccessor<?> field : layer.fields) { + feature.setPropertyValue(field.name, field.getValue(this, ogr, handle)); + } + } finally { + ogr.destroyFeature.invokeExact(handle); + } + action.accept(feature); + return true; + } + + /** + * Returns a buffer where some <abbr>GDAL</abbr> functions will write their outputs. + * This method shall be invoked only from instances where {@link FieldAccessor#bufferSize()} + * returned a non-zero value. + */ + final MemorySegment buffer() { + return buffer; + } + + /** + * Returns whether the field in the given feature has a date. + * If true, the date components are stored in the temporal buffers of this layer. + * This method must be invoked in a block synchronized on {@link FeatureLayer#store}. + * + * @param ogr pointers to native functions. + * @param feature pointer to the {@code OGRFeatureH} instance. + * @return whether the field has date components in the given feature instance. + * @throws Throwable if an error occurred during the native method call. + */ + final boolean hasDate(final OGR ogr, final MemorySegment feature, final int index) throws Throwable { + return ((int) ogr.getFieldAsDateTime.invokeExact(feature, index, + buffer, month, day, hour, minute, second, timezone)) != 0; + } + + /** + * Creates a local date from the current field values. + * The {@link #hasDate(OGR, MemorySegment, int)} method must have been invoked before this method. + * + * @return the local date of the field checked by the last call to {@link #hasDate(OGR, MemorySegment, int)}. Never null. + * @throws NullPointerException if the temporal buffers are null (no dates where expected for the feature type). + */ + final LocalDate date() { + return LocalDate.of(buffer.get(ValueLayout.JAVA_INT, 0), + month .get(ValueLayout.JAVA_INT, 0), + day .get(ValueLayout.JAVA_INT, 0)); + } + + /** + * Creates a local time from the current field values. + * The {@link #hasDate(OGR, MemorySegment, int)} method must have been invoked before this method. + * + * @return the local time of the field checked by the last call to {@link #hasDate(OGR, MemorySegment, int)}. Never null. + * @throws NullPointerException if the temporal buffers are null (no dates where expected for the feature type). + */ + final LocalTime time() { + int sec; + float withMillis = second.get(ValueLayout.JAVA_FLOAT, 0); + return LocalTime.of(hour .get(ValueLayout.JAVA_INT, 0), + minute.get(ValueLayout.JAVA_INT, 0), + sec = (int) withMillis, + Math.round((withMillis - sec) * Constants.NANOS_PER_SECOND)); + } + + /** + * Returns the timezone of current field values, or {@code null} if local or unknown. + * The {@link #hasDate(OGR, MemorySegment, int)} method must have been invoked before this method. + * + * @return the timezone of the field checked by the last call to {@link #hasDate(OGR, MemorySegment, int)}, or null if local. + * @throws NullPointerException if the temporal buffers are null (no dates where expected for the feature type). + */ + final ZoneOffset timezone() { + return timezone.get(ValueLayout.JAVA_INT, 0) == 100 ? ZoneOffset.UTC : null; + } + + /** + * Creates the Java geometry object from the given {@code OGRGeometryH}. + * + * @param ogr pointers to native functions. + * @param geom pointer to the {@code OGRGeometryH} instance. + * @return the Java geometry object, or {@code null} if none or not supported. + * @throws ArrayStoreException if the geometry is some kind of collection but contains a child of unexpected type. + * @throws Throwable if an error occurred during a native method call or Java geometry construction. + */ + final Object geometry(final OGR ogr, final MemorySegment geom) throws Throwable { + assert Thread.holdsLock(layer.store); + final Geometries<?> library = layer.library; + final int code = (int) ogr.getGeometryType.invokeExact(geom); + final var type = (code == LINEAR_RING) ? GeometryType.LINESTRING : GeometryType.forBinaryType(code); + final boolean isPolygon = (type == GeometryType.POLYGON); + if (isPolygon || type.isCollection) { + /* + * If the geometry is some kind of collection (with polygon considered as a collection of linear rings), + * invoke this method recursively for all child components. Note that `LinearRing` is not a standard type, + * so we replace it by the `LineString` parent type. + */ + final GeometryType componentType; + if (isPolygon) { + componentType = GeometryType.LINESTRING; + } else { + componentType = type.component(); + if (componentType == null) { + unsupportedType(type.name); + return null; + } + } + final Class<?> componentClass = library.getGeometryClass(componentType); + final int n = (int) ogr.getGeometryCount.invokeExact(geom); + if (n <= 0) { + return null; + } + final Object[] children = (Object[]) Array.newInstance(componentClass, n); + for (int i=0; i<n; i++) { + var child = (MemorySegment) ogr.getGeometryRef.invokeExact(geom, i); + children[i] = geometry(ogr, child); // May throw ArrayStoreException. + } + return library.getGeometry(library.createFromComponents(type, children)); + } + /* + * Case of Point and LineString/LinearRing. The buffer capacity must be verified. + * If the capacity is not sufficient, create a new buffer with the lifetime of this method call only. + * We do not use the iterator lifetime for avoiding memory leaks, because buffers may be created often + * if the geometry sizes are increasing in each iteration (buffers are released only in `Arena.close()`). + */ + int pointCount = (int) ogr.getPointCount.invokeExact(geom); + if (pointCount <= 0) { // `OGR_G_GetPointCount` returns 0 if not Point or LineString/LinearRing. + return null; + } + final boolean hasZ = GeometryType.hasZ(code) && library.supports(Capability.Z_COORDINATE); + final boolean hasM = GeometryType.hasM(code) && library.supports(Capability.M_COORDINATE); + final var dimensions = Dimensions.forZorM(hasZ, hasM); + final int stride = dimensions.count * Double.BYTES; + final long capacity = Math.multiplyFull(pointCount, stride); + if (buffer.byteSize() < capacity) { + arena = layer.store.changeArena(arena, true); + buffer = arena.allocate(capacity); + } + /* + * (cx, cy, cz, cm) are pointers to the same buffer with an offset of `sizeof(double)` between each. + * We opportunistically reuse the slices already prepared for dates, by choosing those having the right offset. + */ + final MemorySegment cx = buffer; + final MemorySegment cy = day; // See field javadoc. + final MemorySegment cz = hasZ ? minute : MemorySegment.NULL; + final MemorySegment cm = hasM ? (hasZ ? timezone : minute) : MemorySegment.NULL; + pointCount = (int) ogr.getPoints.invokeExact(geom, cx, stride, cy, stride, cz, stride, cm, stride); + assert Math.multiplyFull(pointCount, stride) <= cx.byteSize() : pointCount; + if (pointCount <= 0) { + return null; + } + if (pointCount == 1) { + double x = cx.get(ValueLayout.JAVA_DOUBLE, 0); + double y = cy.get(ValueLayout.JAVA_DOUBLE, 0); + if (hasZ) { + double z = cz.get(ValueLayout.JAVA_DOUBLE, 0); + return library.createPoint(x, y, z); + } else { + return library.createPoint(x, y); + } + } + /* + * Case of string line having more than 1 point. The `DoubleBuffer` wraps directly the memory segment. + * The caller method shall copy the coordinate values, because the buffer will not be valid anymore + * shortly after this methd call. + */ + @SuppressWarnings("restricted") + final MemorySegment coords = cx.reinterpret(Math.multiplyFull(pointCount, stride)); + final DoubleBuffer coordinates = coords.asByteBuffer().order(ByteOrder.nativeOrder()).asDoubleBuffer(); + DoubleBuffer closingPoint = null; + if (code == LINEAR_RING && pointCount >= 3) { + /* + * Polylines are automatically recognized as linear rings if the last point is equal to + * the first point. If this is not the case while a linear ring was expected, copy that + * point in a separated vector. The two vectors will be concatenated by `createPolyline`. + */ + final int last = pointCount - dimensions.count; + final double[] point = new double[dimensions.count]; + for (int i=0; i<point.length; i++) { + point[i] = coordinates.get(i); + if (closingPoint == null && !Numerics.equalsIgnoreZeroSign(point[i], coordinates.get(last + i))) { + closingPoint = DoubleBuffer.wrap(point); // Okay even if we didn't finished to fill the array. + } + } + } + switch (type) { + case POINT: return library.createPoint(false, dimensions, coordinates); + case MULTIPOINT: return library.createMultiPoint(false, dimensions, coordinates); + default: return library.createPolyline(false, false, dimensions, coordinates, closingPoint); + } + } + + /** + * Logs a warning saying that the given type is unsupported. + * Each type is warned only once per iteration. + */ + private void unsupportedType(final String type) { + if (unsupportedTypes.add(type)) { + layer.store.warning(FeatureLayer.class, "features", + new LogRecord(Level.WARNING, layer.errors().getString(Errors.Keys.UnsupportedType_1, type))); + } + } + + /** + * Releases the native memory allocated for this iterator and marks the iteration as completed. + * Call to this method should be registered with {@link java.util.stream.Stream#onClose(Runnable)}. + * + * <h4>Design note</h4> + * We could register this method for invocation by the garbage collector when the users forgot to invoke + * {@code Stream.close()}. But we don't in order to throw {@code ConcurrentReadException} systematically, + * for letting users know that they have a bug. + */ + @Override + public void run() { + synchronized (layer.store) { + layer.iterationInProgress = null; + arena = layer.store.changeArena(arena, false); + } + } + } diff --cc optional/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/FeatureLayer.java index 0000000000,c0c1b4b5c7..8b256c5307 mode 000000,100644..100644 --- a/optional/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/FeatureLayer.java +++ b/optional/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/FeatureLayer.java @@@ -1,0 -1,307 +1,309 @@@ + /* + * 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.storage.gdal; + + import java.util.Locale; + import java.util.Optional; + import java.util.stream.Stream; + import java.util.stream.StreamSupport; + import java.lang.foreign.Arena; + import java.lang.foreign.AddressLayout; + import java.lang.foreign.MemorySegment; -import org.opengis.feature.Feature; -import org.opengis.feature.FeatureType; + import org.opengis.geometry.Envelope; + import org.opengis.referencing.crs.CoordinateReferenceSystem; + import org.apache.sis.feature.builder.AttributeRole; + import org.apache.sis.feature.builder.AttributeTypeBuilder; + import org.apache.sis.feature.builder.FeatureTypeBuilder; + import org.apache.sis.feature.privy.AttributeConvention; + import org.apache.sis.geometry.GeneralEnvelope; + import org.apache.sis.geometry.wrapper.Geometries; + import org.apache.sis.geometry.wrapper.GeometryType; + import org.apache.sis.referencing.IdentifiedObjects; + import org.apache.sis.storage.AbstractFeatureSet; + import org.apache.sis.storage.DataStoreException; + import org.apache.sis.util.ArraysExt; + import org.apache.sis.util.resources.Errors; + import org.apache.sis.util.privy.Strings; + ++// Specific to the main branch: ++import org.apache.sis.feature.AbstractFeature; ++import org.apache.sis.feature.DefaultFeatureType; ++ + + /** + * Information about a feature set in <abbr>OGR</abbr>. + * This is a wrapper around {@code OGRLayerH} in the C/C++ <abbr>API</abbr>. + * + * @author Johann Sorel (Geomatys) + * @author Martin Desruisseaux (Geomatys) + */ + final class FeatureLayer extends AbstractFeatureSet { + /** + * The data store which owns this feature layer. + * All operations must be synchronized on this store, because <abbr>GDAL</abbr> is not thread-safe. + */ + final GDALStore store; + + /** + * Pointer to the <abbr>OGR</abbr> object in native memory. + * This is a {@code OGRLayerH} in the C/C++ <abbr>API</abbr>. + */ + final MemorySegment handle; + + /** + * Description (list of fields) of all feature instances in this layer. + */ - final FeatureType type; ++ final DefaultFeatureType type; + + /** + * Description of the fields to read with GDAL API. Each {@code FieldAccessor} contains the property name + * and the code to execute for fetching the field value from an {@code OGRFeatureH} with <abbr>OGR</abbr>, + */ + final FieldAccessor<?>[] fields; + + /** + * The coordinate reference system of the default geometry field (the first one), or {@code null} if unknown. + * Note that each geometry field can also have its own CRS, not necessarily equals to this one. + */ + private final CoordinateReferenceSystem defaultCRS; + + /** + * The library to use for building geometry objects. + */ + final Geometries<?> library; + + /** + * Non-null if an iteration is in progress. Each {@code FeatureLayer} can have only one iteration in progress + * at a given time, because <abbr>GDAL</abbr> {@code OGRLayerH} C/C++ <abbr>API</abbr> provides only one cursor. + */ + FeatureIterator iterationInProgress; + + /** + * Wraps a <abbr>GDAL</abbr> {@code OGRLayerH} and builds the feature type. + * + * @param store the data store which owns this feature layer. + * @param gdal set of native methods to use. + * @param handle the {@code OGRLayerH} to wrap. + * @throws Throwable if an error occurred during a call to a native method or in Java code. + */ + private FeatureLayer(final GDALStore store, final GDAL gdal, final OGR ogr, final MemorySegment handle) throws Throwable { + super(store); + this.store = store; + this.handle = handle; + this.library = Geometries.factory(store.library); + final var builder = new FeatureTypeBuilder(); + final var definition = (MemorySegment) ogr.getLayerDefinition.invokeExact(handle); + final int fieldCount = (int) ogr.getFieldCount.invokeExact(definition); + final int geomCount = (int) ogr.getGeomFieldCount.invokeExact(definition); + /* + * Fetch the non-geometry fields. + */ + @SuppressWarnings("LocalVariableHidesMemberVariable") + final var fields = new FieldAccessor<?>[FieldAccessor.NUM_ADDITIONAL_FIELDS + fieldCount + geomCount]; + int fieldIndex = 0; + fields[fieldIndex++] = addAttribute(builder, FieldAccessor.Identifier.INSTANCE); + for (int i=0; i<fieldCount; i++) { + fields[fieldIndex++] = addAttribute(builder, FieldAccessor.create(ogr, definition, i)); + } + /* + * Fetch the geometry fields with the first one taken as the default geometry. + * Each geometry field can have its own coordinate reference system (optional). + * The declared geometry type may be conservatively generalized to `GEOMETRY` + * if the information provided by the driver is not reliable. + */ + final String driverName = store.getDriverName(gdal); + final boolean generalize = (driverName != null) && driverName.toLowerCase(Locale.US).contains("shapefile"); + + @SuppressWarnings("LocalVariableHidesMemberVariable") + CoordinateReferenceSystem defaultCRS = null; + + boolean isFirstGeometry = true; + for (int i=0; i<geomCount; i++) { + final var geomField = (MemorySegment) ogr.getGeomFieldDefinition.invokeExact(definition, i); + if (GDAL.isNull(geomField)) continue; // Paranoiac check, but should never happen. + boolean setAsDefault = isFirstGeometry; + final int geomType = (int) ogr.getGeomFieldType.invokeExact(geomField); + String name = GDAL.toString((MemorySegment) ogr.getGeomFieldName.invokeExact(geomField)); + if (name == null || name.isBlank()) { + name = setAsDefault ? AttributeConvention.GEOMETRY : "geometry" + i; + setAsDefault = false; // Because already has the default name. + } + /* + * OGR Hack: ShapeFile geometry type is not correctly detected because the OGR driver does + * not distinguish betwen polygon and multi-polygon. The same issue applies to line strings. + * However, points are okay (ShapeFile has distinct Point and MultiPoint types). + * https://code.djangoproject.com/ticket/7218 + */ + GeometryType gt = GeometryType.forBinaryType(geomType); + if (generalize && (gt == GeometryType.POLYGON || gt == GeometryType.LINESTRING)) { + gt = GeometryType.GEOMETRY; + } + Class<?> geomClass = library.getGeometryClass(gt); + /* + * Add the geometry attribute together with the corresponding `FieldAccessor` for fetching geometries. + * Each field may have its own CRS, but the first one will be taken as the CRS for default geometries. + */ + final CoordinateReferenceSystem crs = SpatialRef.parseCRS(store, gdal, + (MemorySegment) ogr.getGeomFieldSpatialRef.invokeExact(geomField)); + + final AttributeTypeBuilder<?> attribute = builder.addAttribute(geomClass).setName(name).setCRS(crs); + if (setAsDefault) { // First geometry as default. + attribute.addRole(AttributeRole.DEFAULT_GEOMETRY); + } + fields[fieldIndex++] = new FieldAccessor.Geometry<>(name, i, geomClass, crs); + if (isFirstGeometry) { + isFirstGeometry = false; + defaultCRS = crs; // May still be null. + } + } + this.type = builder.setName(GDAL.toString((MemorySegment) ogr.getLayerName.invokeExact(handle))).build(); + this.fields = ArraysExt.resize(fields, fieldIndex); + this.defaultCRS = defaultCRS; + } + + /** + * Adds a non-geometry attribute to a feature type. This is a helper method for the constructor. + * The returned {@link FieldAccessor} instance encapsulates the code for fetching the attribute value. + */ + private static FieldAccessor<?> addAttribute(final FeatureTypeBuilder builder, final FieldAccessor<?> field) { + final AttributeTypeBuilder<?> attribute; + Class<?> ft = field.getElementClass(); + if (ft != null) { + attribute = builder.addAttribute(ft).setMaximumOccurs(Integer.MAX_VALUE); + } else { + attribute = builder.addAttribute(field.getJavaClass()); + } + attribute.setName(field.name); + return field; + } + + /** + * Returns all feature layers found in the data store. + * + * @param parent wrapper for the {@code GDALDatasetH} of <abbr>GDAL</abbr> C/C++ <abbr>API</abbr>. + * @param gdal set of <abbr>GDAL</abbr> native functions. + * @return all layers found, or an empty array if none. + * @throws DataStoreException if an error occurred. + */ + static FeatureLayer[] listLayers(final GDALStore parent, final GDAL gdal) throws DataStoreException { + final MemorySegment dataset = parent.handle(); + try { + final int n = (int) gdal.getLayerCount.invokeExact(dataset); + final var layers = new FeatureLayer[n]; + if (n != 0) { + final OGR ogr = gdal.ogr(); // Initialize only if we have at least one layer. + for (int i=0; i<n; i++) { + final var layer = (MemorySegment) ogr.getLayer.invokeExact(dataset, i); + layers[i] = new FeatureLayer(parent, gdal, ogr, layer); + } + } + return layers; + } catch (Throwable e) { + throw GDAL.propagate(e); + } + } + + /** + * Returns the feature type of this feature set. + */ + @Override - public final FeatureType getType() { ++ public final DefaultFeatureType getType() { + return type; + } + + /** + * Returns the set of <abbr>OGR</abbr> native functions. + * + * @return the set of native functions. + * @throws DataStoreException if the native library is not available. + */ + final OGR OGR() throws DataStoreException { + return store.getProvider().GDAL().ogr(); + } + + /** + * Returns the layer bounding box if its computation is not too expansive. + * + * @return the layer bounding box, or empty if none or too expensive to compute. + * @throws DataStoreException if GDAL raised an error. + */ + @Override + public Optional<Envelope> getEnvelope() throws DataStoreException { + final OGR ogr = OGR(); + try (Arena arena = Arena.ofConfined()) { + final MemorySegment extent = arena.allocate(Double.BYTES * 4); // minX, maxX, minY, maxY. + int error; + synchronized (store) { + try { + error = (int) ogr.getLayerExtent.invokeExact(handle, extent, 0); + } catch (Throwable e) { + throw GDAL.propagate(e); + } + } + if (error != 0) { + // GDAL documentation said that `OGRERR_FAILURE` can simply means that the extent is not known. + return Optional.empty(); + } + final GeneralEnvelope env; + if (defaultCRS != null) { + env = new GeneralEnvelope(defaultCRS); + } else { + env = new GeneralEnvelope(SpatialRef.BIDIMENSIONAL); + } + final var t = AddressLayout.JAVA_DOUBLE; + env.setRange(0, extent.getAtIndex(t, 0), extent.getAtIndex(t, 1)); + env.setRange(1, extent.getAtIndex(t, 2), extent.getAtIndex(t, 3)); + return Optional.of(env); + } finally { + ErrorHandler.throwOnFailure(store, "getEnvelope"); + } + } + + /** + * Returns a stream of feature instances to be read from this layer. + * Only one stream can be executed at a time. + * + * @param parallel ignored since <abbr>GDAL</abbr> is not thread-safe. + */ + @Override - public Stream<Feature> features(boolean parallel) throws DataStoreException { ++ public Stream<AbstractFeature> features(boolean parallel) throws DataStoreException { + try { + synchronized (store) { + // Ignore the `parallel` argument, as it is not supported by the iterator. + final var it = new FeatureIterator(this); + return StreamSupport.stream(it, false).onClose(it); + } + } finally { + ErrorHandler.throwOnFailure(store, "features"); + } + } + + /** + * Returns the locale-dependent resources for error messages. + */ + final Errors errors() { + return Errors.forLocale(store.getLocale()); + } + + /** + * Returns a string representation of this layer for debugging purposes. + */ + @Override + public String toString() { + return Strings.toString(getClass(), null, type.getName(), "crs", IdentifiedObjects.getDisplayName(defaultCRS)); + } + }