This is an automated email from the ASF dual-hosted git repository.

asf-gitbox-commits pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/sis.git

commit b8782f10292d86e5b153801e7e37d6d110144663
Merge: 1b04543131 b7f39770a2
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Sat Aug 15 13:37:45 2026 +0200

    Merge branch 'geoapi-3.1'.
    The DGGS module has been temporarily excluded because it depends on the
    geometry module, which is available only in the GeoAPI 3.1/4.0 branches.

 .../apache/sis/buildtools/gradle/Dependency.java   |   2 +-
 .../org/apache/sis/console/HelpCommandTest.java    |   1 +
 .../org.apache.sis.feature/main/module-info.java   |   1 +
 .../sis/coverage/grid/ClippedGridCoverage.java     |   4 +-
 .../apache/sis/coverage/grid/GridCRSBuilder.java   | 156 +++---
 .../org/apache/sis/coverage/grid/GridCoverage.java |  17 +-
 .../apache/sis/coverage/grid/GridCoverage2D.java   | 100 ++--
 .../sis/coverage/grid/GridCoverageBuilder.java     |   6 +-
 .../apache/sis/coverage/grid/GridDerivation.java   |   2 +-
 .../org/apache/sis/coverage/grid/GridExtent.java   | 136 ++---
 .../org/apache/sis/coverage/grid/GridGeometry.java |  43 +-
 .../apache/sis/coverage/grid/ImageRenderer.java    | 121 +----
 .../apache/sis/coverage/grid/SliceGeometry.java    |   3 +-
 .../org/apache/sis/feature/AbstractFeature.java    |   7 +-
 .../org/apache/sis/feature/internal/Resources.java |  15 +
 .../sis/feature/internal/Resources.properties      |   3 +
 .../sis/feature/internal/Resources_fr.properties   |   3 +
 .../internal/shared/AttributeConvention.java       |   6 +-
 .../main/org/apache/sis/image/BandSelectImage.java |   4 +-
 .../apache/sis/image/BandedSampleConverter.java    |   6 +-
 .../main/org/apache/sis/image/ComputedImage.java   |  12 +-
 .../main/org/apache/sis/image/ImageLayout.java     |  38 +-
 .../main/org/apache/sis/image/ImageOverlay.java    |   2 +-
 .../org/apache/sis/image/MultiSourcePrefetch.java  |   5 +-
 .../main/org/apache/sis/image/PlanarImage.java     | 128 ++---
 .../main/org/apache/sis/image/PrefetchedImage.java |   4 +-
 .../org/apache/sis/image/SourceAlignedImage.java   |   2 +-
 .../apache/sis/image/WritableComputedImage.java    |   8 +-
 .../org/apache/sis/image/internal/Summarizer.java  | 275 +++++++++++
 .../apache/sis/image/internal}/package-info.java   |  11 +-
 .../image/internal/shared/BatchComputedImage.java  |  27 +-
 .../image/internal/shared/ColorModelFactory.java   |  54 +-
 .../image/internal/shared/DeferredProperty.java    |  47 +-
 .../sis/image/internal/shared/ImageUtilities.java  |  36 +-
 .../sis/image/internal/shared/RasterFactory.java   |  21 +-
 .../sis/image/internal/shared/ReshapedImage.java   | 115 ++++-
 .../image/internal/shared/ScaledColorSpace.java    |  13 +-
 .../sis/image/internal/shared/TileOpExecutor.java  |   7 +-
 .../sis/image/internal/shared/TiledImage.java      |   6 +-
 .../image/internal/shared/WritableTiledImage.java  |  16 +-
 ...ervableImage.java => WritableUntiledImage.java} | 272 ++++++++--
 .../apache/sis/coverage/grid/GridExtentTest.java   |  57 ++-
 .../coverage/grid/ResampledGridCoverageTest.java   |   6 +-
 .../test/org/apache/sis/image/ImageLayoutTest.java |   9 +
 .../test/org/apache/sis/image/TiledImageMock.java  |  71 ++-
 .../image/internal/shared/ReshapedImageTest.java   |   5 +-
 .../org/apache/sis/metadata/AbstractMetadata.java  |   6 +-
 .../main/org/apache/sis/metadata/CacheKey.java     |   9 +-
 .../org/apache/sis/metadata/MetadataCopier.java    |   2 +-
 .../org/apache/sis/metadata/MetadataStandard.java  | 393 ++++++++++-----
 .../org/apache/sis/metadata/MetadataVisitor.java   |  15 +-
 .../apache/sis/metadata/ModifiableMetadata.java    |  46 +-
 .../org/apache/sis/metadata/PropertyAccessor.java  |   3 +-
 .../main/org/apache/sis/metadata/Pruner.java       |   4 +-
 .../main/org/apache/sis/metadata/StateChanger.java |   2 +-
 .../main/org/apache/sis/metadata/TreeNode.java     |  10 +-
 .../apache/sis/metadata/UnresolvedTypePolicy.java} |  40 +-
 .../apache/sis/metadata/internal/Resources.java    |   5 +
 .../sis/metadata/internal/Resources.properties     |   1 +
 .../sis/metadata/internal/Resources_fr.properties  |   1 +
 .../sis/metadata/internal/shared/Merger.java       |  88 +++-
 .../iso/extent/DefaultGeographicBoundingBox.java   |   2 +-
 .../main/org/apache/sis/metadata/package-info.java |   2 +-
 .../sis/metadata/sql/internal/shared/Dialect.java  |  43 +-
 .../metadata/sql/internal/shared/SQLBuilder.java   |  11 +-
 .../sis/metadata/sql/internal/shared/Supports.java |   7 +-
 .../org/apache/sis/util/iso/DefaultRecordType.java |   2 +-
 .../apache/sis/xml/bind/cat/CodeListAdapter.java   |   2 +-
 .../test/org/apache/sis/metadata/HashCodeTest.java |   4 +-
 .../apache/sis/metadata/InformationMapTest.java    |   1 +
 .../apache/sis/metadata/MetadataStandardTest.java  |  28 +-
 .../test/org/apache/sis/metadata/NameMapTest.java  |   1 +
 .../test/org/apache/sis/metadata/PrunerTest.java   |   1 +
 .../org/apache/sis/metadata/SpecialCasesTest.java  |   1 +
 .../apache/sis/metadata/TreeNodeChildrenTest.java  |   2 +-
 .../test/org/apache/sis/metadata/TypeMapTest.java  |   1 +
 .../sis/metadata/internal/shared/MergerTest.java   |  24 +-
 .../org/apache/sis/map/coverage/RenderingData.java |  24 +-
 .../apache/sis/map/coverage/RenderingDataTest.java | 256 ++++++++++
 .../main/org/apache/sis/geometry/Envelopes.java    |  51 +-
 .../org/apache/sis/parameter/MatrixParameters.java |   2 +-
 .../sis/referencing/datum/DatumOrEnsemble.java     |   2 +-
 .../operation/CoordinateOperationContext.java      |   6 +-
 .../operation/CoordinateOperationFinder.java       |  32 +-
 .../referencing/operation/SubOperationInfo.java    |  40 +-
 .../operation/provider/DatumShiftMethod.java       |   6 +-
 .../transform/AbstractLinearTransform.java         |   7 +-
 .../operation/transform/PassThroughTransform.java  |   6 +-
 .../operation/transform/TransformJoiner.java       |  11 +-
 .../org/apache/sis/storage/geotiff/DataSubset.java |   2 +-
 .../org/apache/sis/storage/netcdf/base/Axis.java   |  23 +-
 .../apache/sis/storage/netcdf/base/Variable.java   |   7 +
 .../sis/storage/netcdf/ucar/VariableWrapper.java   |  22 +-
 .../sis/storage/sql/feature/SelectionClause.java   |  11 +-
 .../storage/sql/feature/SelectionClauseWriter.java |   6 +-
 .../sis/storage/sql/postgis/RasterReader.java      |   4 +-
 .../org/apache/sis/storage/sql/SQLStoreTest.java   |   2 +-
 .../org/apache/sis/storage/gpx/MetadataTest.java   |   8 +-
 .../org/apache/sis/storage/gpx/ReaderTest.java     |  85 +++-
 .../org.apache.sis.storage/main/module-info.java   |   6 +-
 .../main/org/apache/sis/storage/DataStore.java     |   4 +-
 .../main/org/apache/sis/storage/FeatureQuery.java  |   4 +-
 .../main/org/apache/sis/storage/Resource.java      |   4 +-
 .../org/apache/sis/storage/esri/RasterStore.java   |  13 +-
 .../sis/storage/event/CascadedStoreEvent.java      |   2 +-
 .../apache/sis/storage/event/StoreListener.java    |   6 +-
 .../apache/sis/storage/event/StoreListeners.java   |  16 +-
 .../sis/storage/metadata/MetadataBuilder.java      |   2 +-
 .../sis/storage/tiling/ArtificiallyTiledImage.java | 261 ++++++++++
 .../apache/sis/storage/tiling/ImageTileMatrix.java |   2 +-
 .../apache/sis/storage/tiling/TileReadEvent.java   |  63 ++-
 .../sis/storage/tiling/TiledDeferredImage.java     |   2 +-
 .../sis/storage/tiling/TiledGridCoverage.java      |  10 +-
 .../storage/tiling/TiledGridCoverageResource.java  | 547 +++++++++++++++++----
 .../org/apache/sis/storage/DataStoreTestCase.java  |   4 +-
 .../sis/storage/event/StoreListenersTest.java      |   6 +-
 .../main/org/apache/sis/math/Statistics.java       |   3 +
 .../main/org/apache/sis/util/Classes.java          |   9 +-
 .../sis/util/collection/WeakValueHashMap.java      |   4 +-
 .../apache/sis/util/internal/shared/Numerics.java  |   2 +-
 .../main/org/apache/sis/util/resources/Errors.java |   5 +
 .../apache/sis/util/resources/Errors.properties    |   1 +
 .../apache/sis/util/resources/Errors_fr.properties |   1 +
 .../sis/util/resources/IndexedResourceBundle.java  |  14 +-
 .../org/apache/sis/util/resources/Messages.java    |   5 +
 .../apache/sis/util/resources/Messages.properties  |   1 +
 .../sis/util/resources/Messages_fr.properties      |   3 +-
 .../org/apache/sis/util/resources/Vocabulary.java  |  25 +-
 .../sis/util/resources/Vocabulary.properties       |   5 +-
 .../sis/util/resources/Vocabulary_fr.properties    |   7 +-
 .../apache/sis/util/collection/RangeSetTest.java   |   4 +-
 incubator/build.gradle.kts                         |  19 +-
 .../coveragejson/binding/CategoryEncoding.java     |  82 ---
 .../storage/coveragejson/binding/Dictionary.java   |  73 ---
 .../sis/storage/coveragejson/binding/I18N.java     | 125 -----
 .../storage/coveragejson/binding/Parameters.java   |  78 ---
 .../sis/storage/coveragejson/binding/Ranges.java   |  74 ---
 .../org.apache.sis.storage.DataStoreProvider       |   0
 .../main/module-info.java                          |  27 +-
 .../storage/coveragejson/CoverageJsonStore.java    |  17 +-
 .../coveragejson/CoverageJsonStoreProvider.java    |   0
 .../sis/storage/coveragejson/CoverageResource.java |  18 +-
 .../sis/storage/coveragejson/binding/Axe.java      |   9 +-
 .../sis/storage/coveragejson/binding/Axes.java     |  16 +-
 .../sis/storage/coveragejson/binding/Category.java |  10 +-
 .../coveragejson/binding/CategoryEncoding.java}    |  16 +-
 .../sis/storage/coveragejson/binding/Coverage.java |  47 +-
 .../coveragejson/binding/CoverageCollection.java   |  10 +-
 .../coveragejson/binding/CoverageJsonObject.java   |  37 +-
 .../sis/storage/coveragejson/binding/Domain.java   |   7 +-
 .../coveragejson/binding/GeographicCRS.java        |   7 +-
 .../sis/storage/coveragejson/binding/I18N.java     |  77 +++
 .../storage/coveragejson/binding/IdentifierRS.java |  10 +-
 .../sis/storage/coveragejson/binding/NdArray.java  |   7 +-
 .../coveragejson/binding/ObservedProperty.java     |   9 +-
 .../storage/coveragejson/binding/Parameter.java    |   7 +-
 .../coveragejson/binding/ParameterGroup.java       |   7 +-
 .../storage/coveragejson/binding/ProjectedCRS.java |   7 +-
 .../binding/ReferenceSystemConnection.java         |   9 +-
 .../sis/storage/coveragejson/binding/Symbol.java   |   9 +-
 .../coveragejson/binding/TargetConcept.java        |   9 +-
 .../storage/coveragejson/binding/TemporalRS.java   |   7 +-
 .../sis/storage/coveragejson/binding/TileSet.java  |   9 +-
 .../storage/coveragejson/binding/TiledNdArray.java |   9 +-
 .../sis/storage/coveragejson/binding/Unit.java     |   9 +-
 .../storage/coveragejson/binding/VerticalCRS.java  |   7 +-
 .../storage/coveragejson/binding/package-info.java |   0
 .../sis/storage/coveragejson/package-info.java     |   0
 .../org/apache/sis/storage/geojson/GeoJSON.java    | 165 +++++++
 .../apache/sis/storage/geojson/GeoJSONMapper.java  | 479 ++++++++++++++++++
 .../storage/geojson/binding/GeoJSONFeature.java    | 318 ++++++++++++
 .../geojson/binding/GeoJSONFeatureCollection.java  | 322 ++++++++++++
 .../storage/geojson/binding/GeoJSONGeometry.java   | 134 +++++
 .../geojson/binding/GeoJSONGeometryCollection.java |  84 ++++
 .../storage/geojson/binding/GeoJSONLineString.java |  85 ++++
 .../geojson/binding/GeoJSONMultiLineString.java    |  85 ++++
 .../storage/geojson/binding/GeoJSONMultiPoint.java |  85 ++++
 .../geojson/binding/GeoJSONMultiPolygon.java       |  85 ++++
 .../sis/storage/geojson/binding/GeoJSONObject.java | 104 ++++
 .../sis/storage/geojson/binding/GeoJSONPoint.java  |  88 ++++
 .../storage/geojson/binding/GeoJSONPolygon.java    |  85 ++++
 .../geojson/binding/JSONFGCircularString.java      |  88 ++++
 .../geojson/binding/JSONFGCompoundCurve.java       |  88 ++++
 .../storage/geojson/binding/JSONFGCoordRefSys.java | 131 +++++
 .../geojson/binding/JSONFGCurvePolygon.java        |  89 ++++
 .../storage/geojson/binding/JSONFGMeasures.java    | 123 +++++
 .../storage/geojson/binding/JSONFGMultiCurve.java  |  89 ++++
 .../geojson/binding/JSONFGMultiPolyhedron.java     |  85 ++++
 .../storage/geojson/binding/JSONFGMultiPrism.java  |  85 ++++
 .../geojson/binding/JSONFGMultiSurface.java        |  88 ++++
 .../storage/geojson/binding/JSONFGPolyhedron.java  |  86 ++++
 .../sis/storage/geojson/binding/JSONFGPrism.java   | 127 +++++
 .../sis/storage/geojson/binding/JSONFGTime.java    | 124 +++++
 .../sis/storage/geojson}/binding/package-info.java |   8 +-
 .../sis/storage/json/DataTransferObject.java       | 100 ++++
 .../movingfeature/binding/MFCoordRefSys.java       | 110 +++++
 .../movingfeature/binding/MFMovingFeature.java     | 277 +++++++++++
 .../binding/MFMovingFeatureCollection.java         | 270 ++++++++++
 .../binding/MFMovingGeometryBase.java              | 101 ++++
 .../binding/MFMovingGeometryCollection.java        |  93 ++++
 .../movingfeature/binding/MFMovingLineString.java  |  87 ++++
 .../movingfeature/binding/MFMovingPoint.java       |  86 ++++
 .../movingfeature/binding/MFMovingPointCloud.java  |  89 ++++
 .../movingfeature/binding/MFMovingPolygon.java     |  88 ++++
 .../movingfeature/binding/MFOrientation.java       | 103 ++++
 .../movingfeature/binding/MFTemporalGeometry.java  | 128 +++++
 .../binding/MFTemporalPrimitiveGeometry.java       | 152 ++++++
 .../binding/MFTemporalPropertyGroup.java           |  94 ++++
 .../binding/MFTemporalPropertyValue.java           | 186 +++++++
 .../movingfeature}/binding/package-info.java       |   7 +-
 .../main/org/apache/sis/storage/ogcjson/Link.java  | 280 +++++++++++
 .../apache/sis/storage/ogcjson}/package-info.java  |   6 +-
 .../coveragejson/CoverageJsonStoreTest.java        |   0
 .../storage/coveragejson/binding/BindingTest.java  |  69 +--
 .../sis/storage/coveragejson/binding/LICENSE.md    |   0
 .../storage/coveragejson/binding/axe_bounds.json   |   0
 .../storage/coveragejson/binding/axe_polygon.json  |   0
 .../storage/coveragejson/binding/axe_regular.json  |   0
 .../storage/coveragejson/binding/axe_tuples.json   |   0
 .../binding/coverage_vertical_profile.json         |   0
 .../binding/coverage_vertical_profile_nocs.json    |   0
 .../coveragejson/binding/coveragecollection.json   |   0
 .../storage/coveragejson/binding/domain_grid.json  |   0
 .../coveragejson/binding/domain_trajectory.json    |   0
 .../coveragejson/binding/domaintype_grid.json      |   0
 .../binding/domaintype_multipoint.json             |   0
 .../binding/domaintype_multipointseries.json       |   0
 .../binding/domaintype_multipolygon.json           |   0
 .../binding/domaintype_multipolygonseries.json     |   0
 .../coveragejson/binding/domaintype_point.json     |   0
 .../binding/domaintype_pointseries.json            |   0
 .../coveragejson/binding/domaintype_polygon.json   |   0
 .../binding/domaintype_polygonseries.json          |   0
 .../coveragejson/binding/domaintype_section.json   |   0
 .../binding/domaintype_trajectory.json             |   0
 .../binding/domaintype_vertical_profile.json       |   0
 .../binding/geographiccrs_longlat.json             |   0
 .../binding/geographiccrs_longlatheight.json       |   0
 .../sis/storage/coveragejson/binding/ndarray.json  |   0
 .../binding/parameter_categoricaldata.json         |   0
 .../binding/parameter_continuousdata.json          |   0
 .../binding/parametergroup_uncertainty.json        |   0
 .../binding/parametergroup_vectorquantity.json     |   0
 .../binding/projectedcrs_britishnationalgrid.json  |   0
 .../binding/reference_system_connection.json       |   0
 .../storage/coveragejson/binding/temporalrs.json   |   0
 .../storage/coveragejson/binding/tiledndarray.json |   0
 .../coveragejson/binding/verticalcrs_navd88.json   |   0
 .../sis/storage/coveragejson/coverage_xyzt.json    |   0
 .../sis/storage/geojson/binding/BindingTest.java}  |  15 +-
 .../storage/geojson/binding/geojson_feature.json   |  10 +
 .../geojson/binding/geojson_featurecollection.json |  48 ++
 .../binding/geojson_geometrycollection.json        |  13 +
 .../geojson/binding/geojson_linestring.json        |   7 +
 .../geojson/binding/geojson_multilinestring.json   |  13 +
 .../geojson/binding/geojson_multipoint.json        |   7 +
 .../geojson/binding/geojson_multipolygon.json      |  30 ++
 .../sis/storage/geojson/binding/geojson_point.json |   4 +
 .../storage/geojson/binding/geojson_polygon.json   |  12 +
 .../binding/jsonfg_circularstring_measures.json    |  13 +
 .../binding/jsonfg_circularstring_no_measures.json |   8 +
 .../binding/jsonfg_compoundcurve_measures.json     |  25 +
 .../binding/jsonfg_compoundcurve_no_measures.json  |  20 +
 .../geojson/binding/jsonfg_coordrefsys.json        |  18 +
 .../binding/jsonfg_curvepolygon_measures.json      |  20 +
 .../binding/jsonfg_curvepolygon_no_measures.json   |  15 +
 .../storage/geojson/binding/jsonfg_feature.json    |  49 ++
 .../jsonfg_geometrycollection_measures.json        |  21 +
 .../jsonfg_geometrycollection_no_measures.json     |  16 +
 .../binding/jsonfg_linestring_measures.json        |  12 +
 .../binding/jsonfg_linestring_no_measures.json     |   7 +
 .../binding/jsonfg_multicurve_measures.json        |  25 +
 .../binding/jsonfg_multicurve_no_measures.json     |  20 +
 .../binding/jsonfg_multilinestring_measures.json   |  18 +
 .../jsonfg_multilinestring_no_measures.json        |  13 +
 .../binding/jsonfg_multipoint_measures.json        |  12 +
 .../binding/jsonfg_multipoint_no_measures.json     |   7 +
 .../binding/jsonfg_multipolygon_measures.json      |  35 ++
 .../binding/jsonfg_multipolygon_no_measures.json   |  30 ++
 .../binding/jsonfg_multipolyhedron_measures.json   |  23 +
 .../jsonfg_multipolyhedron_no_measures.json        |  18 +
 .../binding/jsonfg_multiprism_measures.json        |  27 +
 .../binding/jsonfg_multiprism_no_measures.json     |  22 +
 .../binding/jsonfg_multisurface_measures.json      |  37 ++
 .../binding/jsonfg_multisurface_no_measures.json   |  32 ++
 .../geojson/binding/jsonfg_point_measures.json     |   9 +
 .../geojson/binding/jsonfg_point_no_measures.json  |   4 +
 .../geojson/binding/jsonfg_polygon_measures.json   |  17 +
 .../binding/jsonfg_polygon_no_measures.json        |  12 +
 .../binding/jsonfg_polyhedron_measures.json        |  21 +
 .../binding/jsonfg_polyhedron_no_measures.json     |  16 +
 .../geojson/binding/jsonfg_prism_measures.json     |  22 +
 .../geojson/binding/jsonfg_prism_no_measures.json  |  17 +
 .../storage/geojson/binding/jsonfg_time_date.json  |  15 +
 .../geojson/binding/jsonfg_time_interval.json      |  18 +
 .../binding/jsonfg_time_interval_open_end.json     |  18 +
 .../binding/jsonfg_time_interval_open_start.json   |  18 +
 .../geojson/binding/jsonfg_time_timestamp.json     |  15 +
 .../sis/storage/json/AbstractBindingTest.java      |  76 +++
 .../movingfeature/binding/BindingTest.java}        |  15 +-
 netbeans-project/ivy.xml                           |   3 +
 netbeans-project/nbproject/project.properties      |   2 +-
 .../apache/sis/gui/coverage/CoverageCanvas.java    | 211 ++------
 .../apache/sis/gui/coverage/CoverageControls.java  |   2 +-
 .../apache/sis/gui/coverage/CoverageExplorer.java  |   2 +-
 .../org/apache/sis/gui/coverage/GridControls.java  |   2 +-
 .../main/org/apache/sis/gui/coverage/GridView.java |   2 +-
 .../apache/sis/gui/coverage/RectangleMerger.java   | 111 +++++
 .../sis/gui/coverage/StyledRenderingData.java      |   5 +
 .../apache/sis/gui/coverage/TileReadListener.java  | 327 ++++++++++++
 .../org/apache/sis/gui/dataset/WindowHandler.java  |   2 +-
 .../org/apache/sis/gui/internal/LogHandler.java    |   2 +-
 .../apache/sis/gui/internal/ShapeConverter.java    |   5 +-
 .../org/apache/sis/gui/map/EvanescentPane.java     |   1 +
 .../main/org/apache/sis/gui/map/MapCanvas.java     |  40 +-
 .../main/org/apache/sis/gui/map/MultiCanvas.java   |   2 +-
 .../org/apache/sis/gui/map/ValuesFormatter.java    |   7 +-
 .../org/apache/sis/gui/map/ValuesUnderCursor.java  |   2 +-
 .../org/apache/sis/storage/gdal/TiledResource.java |  16 +-
 319 files changed, 10574 insertions(+), 1979 deletions(-)

diff --cc 
buildSrc/src/main/java/org/apache/sis/buildtools/gradle/Dependency.java
index 3eb4604a65,4bcec1da8c..5a668b537e
--- a/buildSrc/src/main/java/org/apache/sis/buildtools/gradle/Dependency.java
+++ b/buildSrc/src/main/java/org/apache/sis/buildtools/gradle/Dependency.java
@@@ -76,7 -78,8 +76,7 @@@ public final class Dependency 
          Map.entry("storage.gsf",              "storage:sis-gsf"),
          Map.entry("storage.gdal",             "storage:sis-gdal"),
          Map.entry("storage.geopackage",       "storage:sis-geopackage"),
-         Map.entry("storage.coveragejson",     "storage:sis-coveragejson"),
+         Map.entry("storage.json",             "storage:sis-json"),
 -        Map.entry("portrayal.map",            "core:sis-portrayal-map"),
          Map.entry("webapp",                   "application:sis-webapp")
      );
  
diff --cc endorsed/src/org.apache.sis.feature/main/module-info.java
index ef37ed8ccb,3ba6d33f15..87168cb542
--- a/endorsed/src/org.apache.sis.feature/main/module-info.java
+++ b/endorsed/src/org.apache.sis.feature/main/module-info.java
@@@ -69,8 -68,11 +69,9 @@@ module org.apache.sis.feature 
              org.apache.sis.storage.sql,
              org.apache.sis.storage.netcdf,
              org.apache.sis.storage.shapefile,       // In the "incubator" 
sub-project.
+             org.apache.sis.storage.json,            // In the "incubator" 
sub-project.
 -            org.apache.sis.referencing.dggs,        // In the "incubator" 
sub-project.
              org.apache.sis.storage.gdal,            // In the "optional" 
sub-project.
              org.apache.sis.portrayal,
 -            org.apache.sis.portrayal.map,           // In the "incubator" 
sub-project.
              org.apache.sis.gui;                     // In the "optional" 
sub-project.
  
      exports org.apache.sis.geometry.wrapper to
diff --cc 
endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridCRSBuilder.java
index 0425941c15,7ebf7e57a3..4217928a76
--- 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridCRSBuilder.java
+++ 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridCRSBuilder.java
@@@ -67,11 -66,14 +66,14 @@@ import org.apache.sis.util.logging.Logg
  import org.apache.sis.util.resources.Vocabulary;
  import org.apache.sis.util.collection.Containers;
  import org.apache.sis.util.collection.BackingStoreException;
+ import org.apache.sis.util.internal.shared.Numerics;
  import org.apache.sis.util.iso.Types;
  import org.apache.sis.measure.Units;
+ import org.apache.sis.util.resources.Errors;
  
 -// Specific to the geoapi-3.1 and geoapi-4.0 branches:
 -import org.opengis.referencing.ObjectDomain;
 -import org.opengis.coordinate.MismatchedDimensionException;
 +// Specific to the main branch:
 +import org.opengis.referencing.datum.Datum;
++import org.opengis.geometry.MismatchedDimensionException;
  
  
  /**
diff --cc 
endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/AbstractFeature.java
index 31b84f7e15,17408ae4a8..c41c7b7a0c
--- 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/AbstractFeature.java
+++ 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/AbstractFeature.java
@@@ -569,13 -531,13 +564,13 @@@ public abstract class AbstractFeature i
       * @param  property        name of the property for which to get a 
characteristic.
       * @param  characteristic  name of the characteristic of the property of 
the given name.
       * @return default value of the specified characteristic on the specified 
property.
--     * @throws PropertyNotFoundException if the {@code property} argument is 
not the name of a property.
++     * @throws IllegalArgumentException if the {@code property} argument is 
not the name of a property.
       */
      final Optional<?> getDefaultCharacteristicValue(final String property, 
final String characteristic) {
 -        final PropertyType p = type.getProperty(property);
 -        if (p instanceof AttributeType<?>) {
 -            return Optional.ofNullable(((AttributeType<?>) 
p).characteristics().get(characteristic))
 -                    .map(AttributeType::getDefaultValue);
 +        final AbstractIdentifiedType p = type.getProperty(property);
 +        if (p instanceof DefaultAttributeType<?>) {
 +            return Optional.ofNullable(((DefaultAttributeType<?>) 
p).characteristics().get(characteristic))
 +                    .map(DefaultAttributeType::getDefaultValue);
          }
          return Optional.empty();
      }
diff --cc 
endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/internal/shared/AttributeConvention.java
index f6131f6227,395db909dc..ef66b1d810
--- 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/internal/shared/AttributeConvention.java
+++ 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/internal/shared/AttributeConvention.java
@@@ -284,7 -290,7 +284,7 @@@ public final class AttributeConvention 
       * @param  feature   the feature instance from which to get the 
<abbr>CRS</abbr> of an attribute.
       * @param  property  name of the property for which to get the 
<abbr>CRS</abbr>.
       * @return the Coordinate Reference System characteristic of the 
specified property, or {@code null} if none.
--     * @throws PropertyNotFoundException if the {@code property} argument is 
not the name of a property of the given feature.
++     * @throws IllegalArgumentException if the {@code property} argument is 
not the name of a property of the given feature.
       * @throws ClassCastException if {@link #CRS_CHARACTERISTIC} has been 
found but is associated
       *         to an object which is not a {@link CoordinateReferenceSystem} 
instance.
       *
@@@ -332,7 -338,7 +332,7 @@@
       * @param  feature   the feature instance from which to get the maximal 
length of an attribute.
       * @param  property  the name of the property for which to get the 
maximal length.
       * @return the maximal length characteristic of the specified property, 
or {@code null} if none.
--     * @throws PropertyNotFoundException if the {@code property} argument is 
not the name of a property of the given feature.
++     * @throws IllegalArgumentException if the {@code property} argument is 
not the name of a property of the given feature.
       * @throws ClassCastException if {@link #MAXIMAL_LENGTH_CHARACTERISTIC} 
has been found but is associated
       *         to an object which is not an {@link Integer} instance.
       *
@@@ -390,10 -396,27 +390,10 @@@
       * @param  property        name of the property for which to get the 
characteristic.
       * @param  characteristic  name of the characteristic from which to get 
the value or default value.
       * @return the value or default value of the specified characteristic in 
the specified property, or {@code null} if none.
--     * @throws PropertyNotFoundException if the {@code property} argument is 
not the name of a property of the given feature.
++     * @throws IllegalArgumentException if the {@code property} argument is 
not the name of a property of the given feature.
       */
 -    private static Object getCharacteristic(final Feature feature, final 
String property, final String characteristic) {
 -        if (feature instanceof AbstractFeature) {
 -            return ((AbstractFeature) 
feature).getCharacteristicValue(property, characteristic).orElse(null);
 -        }
 -        final Property attribute = feature.getProperty(property);
 -        if (attribute instanceof Attribute<?>) {
 -            final Attribute<?> at = ((Attribute<?>) 
attribute).characteristics().get(characteristic);
 -            if (at != null) {
 -                final Object value = at.getValue();
 -                if (value != null) {
 -                    return value;
 -                }
 -            }
 -            final AttributeType<?> type = ((Attribute<?>) 
attribute).getType().characteristics().get(characteristic);
 -            if (type != null) {
 -                return type.getDefaultValue();
 -            }
 -        }
 -        return null;
 +    private static Object getCharacteristic(final AbstractFeature feature, 
final String property, final String characteristic) {
 +        return feature.getCharacteristicValue(property, 
characteristic).orElse(null);
      }
  
      /**
diff --cc 
endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/internal/shared/MergerTest.java
index d419b2d3e2,9c79d7677b..09c66d15eb
--- 
a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/internal/shared/MergerTest.java
+++ 
b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/internal/shared/MergerTest.java
@@@ -111,10 -114,10 +111,10 @@@ public final class MergerTest extends T
          assertSetEquals(List.of(StandardCharsets.UTF_16, 
StandardCharsets.UTF_8),
                          target.getLocalesAndCharsets().values());
  
-         final Iterator<ContentInformation> it       = 
target.getContentInfo().iterator();
-         final ImageDescription             image    = (ImageDescription)      
      it.next();
-         final FeatureCatalogueDescription  features = 
(FeatureCatalogueDescription) it.next();
-         final DefaultCoverageDescription   coverage = 
(DefaultCoverageDescription)  it.next();
+         final Iterator<ContentInformation> it = 
target.getContentInfo().iterator();
+         final var  image    = assertInstanceOf(ImageDescription.class,        
    it.next());
+         final var  features = 
assertInstanceOf(FeatureCatalogueDescription.class, it.next());
 -        final var  coverage = assertInstanceOf(CoverageDescription.class,     
    it.next());
++        final var  coverage = 
assertInstanceOf(DefaultCoverageDescription.class,  it.next());
          assertFalse(it.hasNext());
  
          assertEquals(ImagingCondition.CLOUD, image   .getImagingCondition());
diff --cc 
endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/SubOperationInfo.java
index 69c93aedb1,c692d2cbca..62e545cbc5
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/SubOperationInfo.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/SubOperationInfo.java
@@@ -31,10 -31,10 +31,13 @@@ import org.apache.sis.referencing.inter
  import org.apache.sis.referencing.operation.matrix.Matrices;
  import org.apache.sis.referencing.operation.matrix.MatrixSIS;
  import org.apache.sis.referencing.operation.transform.MathTransforms;
+ import org.apache.sis.measure.NumberRange;
+ import org.apache.sis.util.Classes;
+ import org.apache.sis.util.internal.shared.Strings;
  
 +// Specific to the main branch:
 +import org.apache.sis.referencing.crs.DefaultParametricCRS;
 +
  
  /**
   * Information about the operation from a source component to a target 
component in {@code CompoundCRS} instances.
diff --cc 
endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/SelectionClause.java
index 8a1b2ad0d2,fb0355910e..e1299735df
--- 
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/SelectionClause.java
+++ 
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/SelectionClause.java
@@@ -181,10 -190,17 +181,17 @@@ public final class SelectionClause exte
      }
  
      /**
-      * Writes a literal value, or marks this SQL as invalid if the value 
cannot be formatted.
+      * Writes a literal value, or marks this <abbr>SQL</abbr> as invalid if 
the value cannot be formatted.
+      * The value can be {@code null}, a character string, a Boolean, a 
number, a temporal object,
+      * a geographic bounding box or an envelope. If the given object cannot 
be formatted,
+      * then this clause is {@linkplain #invalidate() marked as invalid}.
       */
      final void appendLiteral(final Object value) {
-         if (value instanceof GeographicBoundingBox) {
+         if (value == null) {
+             append(NULL);
+         } else if (value instanceof CharSequence) {
 -            appendLiteralOrPattern(value.toString());
++            appendValue(value.toString());
+         } else if (value instanceof GeographicBoundingBox) {
              appendGeometry(null, new GeneralEnvelope((GeographicBoundingBox) 
value));
          } else if (value instanceof Envelope) {
              appendGeometry(null, (Envelope) value);
diff --cc 
endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/SelectionClauseWriter.java
index 386016294c,114a2fff87..52c88c92b4
--- 
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/SelectionClauseWriter.java
+++ 
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/SelectionClauseWriter.java
@@@ -83,8 -87,8 +83,8 @@@ public class SelectionClauseWriter exte
          
setFilterHandler(ComparisonOperatorName.PROPERTY_IS_GREATER_THAN_OR_EQUAL_TO, 
new Comparison(" >= "));
          setFilterHandler(ComparisonOperatorName.PROPERTY_IS_LESS_THAN,        
        new Comparison(" < "));
          
setFilterHandler(ComparisonOperatorName.PROPERTY_IS_LESS_THAN_OR_EQUAL_TO,    
new Comparison(" <= "));
-         setFilterHandler(ComparisonOperatorName.PROPERTY_IS_BETWEEN, (f,sql) 
-> {
 -        
setFilterHandler(ComparisonOperatorName.valueOf(FunctionNames.PROPERTY_IS_BETWEEN),
 (f, sql) -> {
 -            final var filter = (BetweenComparisonOperator<Feature>) f;
++        setFilterHandler(ComparisonOperatorName.PROPERTY_IS_BETWEEN, (f, sql) 
-> {
 +            final var filter = (BetweenComparisonOperator<AbstractFeature>) f;
              /* Nothing to append */  if (write(sql, filter.getExpression()))  
  return;
              sql.append(" BETWEEN "); if (write(sql, 
filter.getLowerBoundary())) return;
              sql.append(" AND ");         write(sql, 
filter.getUpperBoundary());
diff --cc 
endorsed/src/org.apache.sis.storage.sql/test/org/apache/sis/storage/sql/SQLStoreTest.java
index d7741d7460,e0f04f8e94..c090a2ac23
--- 
a/endorsed/src/org.apache.sis.storage.sql/test/org/apache/sis/storage/sql/SQLStoreTest.java
+++ 
b/endorsed/src/org.apache.sis.storage.sql/test/org/apache/sis/storage/sql/SQLStoreTest.java
@@@ -446,10 -451,10 +446,10 @@@ public final class SQLStoreTest extend
           */
          final Object[] result;
          final FeatureSet cities = dataset.findResource("Cities");
 -        try (Stream<Feature> features = cities.subset(query).features(false)) 
{
 +        try (Stream<AbstractFeature> features = 
cities.subset(query).features(false)) {
              result = features.map(f -> 
f.getPropertyValue("native_name")).toArray();
          }
-         assertSetEquals(Arrays.asList("Montréal", "Québec"), 
Arrays.asList(result));
+         assertSetEquals(List.of("Montréal", "Québec"), Arrays.asList(result));
      }
  
      /**
diff --cc 
endorsed/src/org.apache.sis.storage.xml/test/org/apache/sis/storage/gpx/ReaderTest.java
index ffedca3a5e,56fcd3e904..0e44121b2e
--- 
a/endorsed/src/org.apache.sis.storage.xml/test/org/apache/sis/storage/gpx/ReaderTest.java
+++ 
b/endorsed/src/org.apache.sis.storage.xml/test/org/apache/sis/storage/gpx/ReaderTest.java
@@@ -95,8 -105,9 +105,9 @@@ public final class ReaderTest extends T
       */
      private static void assertEnvelopeEquals(final double xmin, final double 
xmax,
                                               final double ymin, final double 
ymax,
-                                              final Envelope actual)
 -                                             final Feature f)
++                                             final AbstractFeature f)
      {
+         final Envelope actual = assertInstanceOf(Envelope.class, 
f.getPropertyValue("sis:envelope"));
          assertEquals(2, actual.getDimension(), "dimension");
          assertEquals(actual.getMinimum(0), xmin, "xmin");
          assertEquals(actual.getMaximum(0), xmax, "xmax");
@@@ -207,10 -255,10 +255,10 @@@
      @Test
      public void testWayPoint100() throws DataStoreException {
          try (Store reader = create(TestData.V1_0, TestData.WAYPOINT)) {
-             verifyAlmostEmptyMetadata((Metadata) reader.getMetadata());
+             verifyAlmostEmptyMetadata(getMetadata(reader));
              assertEquals(StoreProvider.V1_0, reader.getVersion());
 -            try (Stream<Feature> features = reader.features(false)) {
 -                final Iterator<Feature> it = features.iterator();
 +            try (Stream<AbstractFeature> features = reader.features(false)) {
 +                final Iterator<AbstractFeature> it = features.iterator();
                  verifyPoint(it.next(), 0, false);
                  verifyPoint(it.next(), 1, false);
                  verifyPoint(it.next(), 2, false);
@@@ -227,10 -275,10 +275,10 @@@
      @Test
      public void testWayPoint110() throws DataStoreException {
          try (Store reader = create(TestData.V1_1, TestData.WAYPOINT)) {
-             verifyAlmostEmptyMetadata((Metadata) reader.getMetadata());
+             verifyAlmostEmptyMetadata(getMetadata(reader));
              assertEquals(StoreProvider.V1_1, reader.getVersion());
 -            try (Stream<Feature> features = reader.features(false)) {
 -                final Iterator<Feature> it = features.iterator();
 +            try (Stream<AbstractFeature> features = reader.features(false)) {
 +                final Iterator<AbstractFeature> it = features.iterator();
                  verifyPoint(it.next(), 0, true);
                  verifyPoint(it.next(), 1, true);
                  verifyPoint(it.next(), 2, true);
@@@ -247,10 -295,10 +295,10 @@@
      @Test
      public void testRoute100() throws DataStoreException {
          try (Store reader = create(TestData.V1_0, TestData.ROUTE)) {
-             verifyAlmostEmptyMetadata((Metadata) reader.getMetadata());
+             verifyAlmostEmptyMetadata(getMetadata(reader));
              assertEquals(StoreProvider.V1_0, reader.getVersion());
 -            try (Stream<Feature> features = reader.features(false)) {
 -                final Iterator<Feature> it = features.iterator();
 +            try (Stream<AbstractFeature> features = reader.features(false)) {
 +                final Iterator<AbstractFeature> it = features.iterator();
                  verifyRoute(it.next(), false, 1);
                  verifyEmpty(it.next(), "rtept");
                  assertFalse(it.hasNext());
@@@ -352,10 -400,10 +400,10 @@@
      @Test
      public void testTrack100() throws DataStoreException {
          try (Store reader = create(TestData.V1_0, TestData.TRACK)) {
-             verifyAlmostEmptyMetadata((Metadata) reader.getMetadata());
+             verifyAlmostEmptyMetadata(getMetadata(reader));
              assertEquals(StoreProvider.V1_0, reader.getVersion());
 -            try (Stream<Feature> features = reader.features(false)) {
 -                final Iterator<Feature> it = features.iterator();
 +            try (Stream<AbstractFeature> features = reader.features(false)) {
 +                final Iterator<AbstractFeature> it = features.iterator();
                  verifyTrack(it.next(), false, 1);
                  verifyEmpty(it.next(), "trkseg");
                  assertFalse(it.hasNext());
@@@ -371,10 -419,10 +419,10 @@@
      @Test
      public void testTrack110() throws DataStoreException {
          try (Store reader = create(TestData.V1_1, TestData.TRACK)) {
-             verifyAlmostEmptyMetadata((Metadata) reader.getMetadata());
+             verifyAlmostEmptyMetadata(getMetadata(reader));
              assertEquals(StoreProvider.V1_1, reader.getVersion());
 -            try (Stream<Feature> features = reader.features(false)) {
 -                final Iterator<Feature> it = features.iterator();
 +            try (Stream<AbstractFeature> features = reader.features(false)) {
 +                final Iterator<AbstractFeature> it = features.iterator();
                  verifyTrack(it.next(), true, 3);
                  verifyEmpty(it.next(), "trkseg");
                  assertFalse(it.hasNext());
@@@ -586,16 -634,17 +634,17 @@@
       * @throws DataStoreException if reader failed to be created or failed at 
reading.
       */
      @Test
+     @SuppressWarnings("ConvertToTryWithResources")
      public void testConcurrentReads() throws DataStoreException {
          try (Store reader = createFromURL()) {
 -            final Stream<Feature>   f1 = reader.features(false);
 -            final Iterator<Feature> i1 = f1.iterator();
 +            final Stream<AbstractFeature>   f1 = reader.features(false);
 +            final Iterator<AbstractFeature> i1 = f1.iterator();
              verifyRoute(i1.next(), true, 3);
 -            final Stream<Feature>   f2 = reader.features(false);
 -            final Iterator<Feature> i2 = f2.iterator();
 +            final Stream<AbstractFeature>   f2 = reader.features(false);
 +            final Iterator<AbstractFeature> i2 = f2.iterator();
              verifyEmpty(i1.next(), "rtept");
 -            final Stream<Feature>   f3 = reader.features(false);
 -            final Iterator<Feature> i3 = f3.iterator();
 +            final Stream<AbstractFeature>   f3 = reader.features(false);
 +            final Iterator<AbstractFeature> i3 = f3.iterator();
              verifyRoute(i2.next(), true, 3);
              verifyRoute(i3.next(), true, 3);
              verifyEmpty(i3.next(), "rtept");
diff --cc 
endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/FeatureQuery.java
index 63b6b1b81f,b780a79674..9c5845162a
--- 
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
@@@ -622,7 -621,7 +622,7 @@@ public class FeatureQuery extends Quer
           * @param  builder  the builder where to add the property.
           * @return whether the property has been successfully added.
           * @throws InvalidFilterValueException if {@linkplain #expression} is 
invalid.
--         * @throws PropertyNotFoundException if the property was not found in 
{@code builder.source()}.
++         * @throws IllegalArgumentException if the property was not found in 
{@code builder.source()}.
           * @throws UnconvertibleObjectException if the property default value 
cannot be converted to the expected type.
           */
          final boolean addTo(final FeatureProjectionBuilder builder) {
@@@ -788,7 -787,7 +788,7 @@@
       * @param  sourceType  the feature type to project.
       * @param  locale      locale for error messages, or {@code null} for the 
default locale.
       * @throws InvalidFilterValueException if an {@linkplain 
NamedExpression#expression expression} is invalid.
--     * @throws PropertyNotFoundException if a property referenced by an 
expression was not found in {@code sourceType}.
++     * @throws IllegalArgumentException if a property referenced by an 
expression was not found in {@code sourceType}.
       * @throws UnconvertibleObjectException if a property default value 
cannot be converted to the expected type.
       * @throws UnsupportedOperationException if there is an attempt to rename 
a property which is used by an operation.
       */
diff --cc 
incubator/src/org.apache.sis.storage.json/main/org/apache/sis/storage/geojson/GeoJSON.java
index 0000000000,d805dfbcef..ee2b202e19
mode 000000,100644..100644
--- 
a/incubator/src/org.apache.sis.storage.json/main/org/apache/sis/storage/geojson/GeoJSON.java
+++ 
b/incubator/src/org.apache.sis.storage.json/main/org/apache/sis/storage/geojson/GeoJSON.java
@@@ -1,0 -1,165 +1,165 @@@
+ /*
+  * 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.geojson;
+ 
+ import java.util.List;
+ import java.util.Map.Entry;
++import org.apache.sis.feature.AbstractFeature;
++import org.apache.sis.feature.DefaultFeatureType;
+ import org.apache.sis.feature.builder.AttributeRole;
+ import org.apache.sis.feature.builder.FeatureTypeBuilder;
+ import org.apache.sis.feature.internal.shared.AttributeConvention;
+ import org.apache.sis.storage.DataStoreException;
+ import org.apache.sis.storage.geojson.binding.GeoJSONFeature;
+ import org.apache.sis.storage.geojson.binding.GeoJSONGeometry;
+ import org.apache.sis.storage.geojson.binding.GeoJSONGeometryCollection;
+ import org.apache.sis.storage.geojson.binding.GeoJSONLineString;
+ import org.apache.sis.storage.geojson.binding.GeoJSONMultiLineString;
+ import org.apache.sis.storage.geojson.binding.GeoJSONMultiPoint;
+ import org.apache.sis.storage.geojson.binding.GeoJSONMultiPolygon;
+ import org.apache.sis.storage.geojson.binding.GeoJSONPoint;
+ import org.apache.sis.storage.geojson.binding.GeoJSONPolygon;
+ import org.locationtech.jts.geom.Coordinate;
+ import org.locationtech.jts.geom.CoordinateXY;
+ import org.locationtech.jts.geom.CoordinateXYZM;
+ import org.locationtech.jts.geom.Geometry;
+ import org.locationtech.jts.geom.GeometryFactory;
+ import org.locationtech.jts.geom.LineString;
+ import org.locationtech.jts.geom.LinearRing;
+ import org.locationtech.jts.geom.Point;
+ import org.locationtech.jts.geom.Polygon;
 -import org.opengis.feature.Feature;
 -import org.opengis.feature.FeatureType;
+ 
+ /**
+  *
+  * @author Johann Sorel (Geomatys)
+  */
+ public final class GeoJSON {
+ 
+     private static final GeometryFactory GF = new GeometryFactory();
+ 
+     private GeoJSON() {
+ 
+     }
+ 
 -    public static Feature fromGeoJSON(GeoJSONFeature json, FeatureType type) 
throws DataStoreException {
++    public static AbstractFeature fromGeoJSON(GeoJSONFeature json, 
DefaultFeatureType type) throws DataStoreException {
+ 
+         final GeoJSONGeometry jsongeom = json.getGeometry();
+         Geometry geom = null;
+         if (jsongeom != null) {
+             geom = fromGeoJSON(jsongeom);
+         }
+ 
+         if (type == null) {
+             final FeatureTypeBuilder ftb = new FeatureTypeBuilder();
+             ftb.setName("geojson");
+ 
+             if (json.getId() != null) {
+                 
ftb.addAttribute(json.getId().getClass()).setName(AttributeConvention.IDENTIFIER_PROPERTY).addRole(AttributeRole.IDENTIFIER_COMPONENT);
+             }
+             if (geom != null) {
+                 
ftb.addAttribute(geom.getClass()).setName(AttributeConvention.GEOMETRY_PROPERTY).addRole(AttributeRole.DEFAULT_GEOMETRY);
+             }
+             for (Entry<String,Object> entry : 
json.getProperties().entrySet()) {
+                 
ftb.addAttribute(entry.getValue().getClass()).setName(entry.getKey());
+             }
+             type = ftb.build();
+         }
+ 
 -        final Feature feature = type.newInstance();
++        final AbstractFeature feature = type.newInstance();
+ 
+         if (json.getId() != null) {
+             feature.setPropertyValue(AttributeConvention.IDENTIFIER, 
json.getId());
+         }
+ 
+         if (geom != null) {
+             feature.setPropertyValue(AttributeConvention.GEOMETRY, geom);
+         }
+ 
+         for (Entry<String,Object> entry : json.getProperties().entrySet()) {
+             feature.setPropertyValue(entry.getKey(), entry.getValue());
+         }
+         return feature;
+     }
+ 
+     public static Geometry fromGeoJSON(GeoJSONGeometry geom) throws 
DataStoreException {
+         if (geom instanceof GeoJSONPoint json) {
+             return GF.createPoint(toCoordinate(json.getCoordinates()));
+         } else if (geom instanceof GeoJSONLineString json) {
+             return GF.createLineString(toCoordinates(json.getCoordinates()));
+         } else if (geom instanceof GeoJSONPolygon json) {
+             return toPolygon(json.getCoordinates());
+         } else if (geom instanceof GeoJSONMultiPoint json) {
+             final List<List<Double>> coordinates = json.getCoordinates();
+             final Point[] points = new Point[coordinates.size()];
+             for (int i = 0; i < points.length; i++) {
+                 points[i] = GF.createPoint(toCoordinate(coordinates.get(i)));
+             }
+             return GF.createMultiPoint(points);
+         } else if (geom instanceof GeoJSONMultiLineString json) {
+             final List<List<List<Double>>> coordinates = 
json.getCoordinates();
+             final LineString[] lines = new LineString[coordinates.size()];
+             for (int i = 0; i < lines.length; i++) {
+                 lines[i] = 
GF.createLineString(toCoordinates(coordinates.get(i)));
+             }
+             return GF.createMultiLineString(lines);
+         } else if (geom instanceof GeoJSONMultiPolygon json) {
+             final List<List<List<List<Double>>>> coordinates = 
json.getCoordinates();
+             final Polygon[] polygons = new Polygon[coordinates.size()];
+             for (int i = 0; i < polygons.length; i++) {
+                 polygons[i] = toPolygon(coordinates.get(i));
+             }
+             return GF.createMultiPolygon(polygons);
+         } else if (geom instanceof GeoJSONGeometryCollection json) {
+             final List<GeoJSONGeometry> geometries = json.getGeometries();
+             final Geometry[] children = new Geometry[geometries.size()];
+             for (int i = 0; i < children.length; i++) {
+                 children[i] = fromGeoJSON(geometries.get(i));
+             }
+             return GF.createGeometryCollection(children);
+         } else {
+             throw new DataStoreException("Geometry not supported " + geom);
+         }
+     }
+ 
+     private static Polygon toPolygon(List<List<List<Double>>> rings) throws 
DataStoreException {
+         final LinearRing shell = 
GF.createLinearRing(toCoordinates(rings.get(0)));
+         final LinearRing[] holes = new LinearRing[rings.size() - 1];
+         for (int i = 0; i < holes.length; i++) {
+             holes[i] = GF.createLinearRing(toCoordinates(rings.get(i + 1)));
+         }
+         return GF.createPolygon(shell, holes);
+     }
+ 
+     private static Coordinate[] toCoordinates(List<List<Double>> positions) 
throws DataStoreException {
+         final Coordinate[] coords = new Coordinate[positions.size()];
+         for (int i = 0; i < coords.length; i++) {
+             coords[i] = toCoordinate(positions.get(i));
+         }
+         return coords;
+     }
+ 
+     private static Coordinate toCoordinate(List<Double> coordinates) throws 
DataStoreException {
+         final int size = coordinates.size();
+         switch (size) {
+             case 2 : return new CoordinateXY(coordinates.get(0), 
coordinates.get(1));
+             case 3 : return new Coordinate(coordinates.get(0), 
coordinates.get(1), coordinates.get(2));
+             case 4 : return new CoordinateXYZM(coordinates.get(0), 
coordinates.get(1), coordinates.get(2), coordinates.get(3));
+             default : throw new DataStoreException("Geometry " + size + "not 
supported");
+         }
+     }
+ 
+ }
diff --cc 
incubator/src/org.apache.sis.storage.json/main/org/apache/sis/storage/geojson/GeoJSONMapper.java
index 0000000000,f480f04d6e..65bc1df0f6
mode 000000,100644..100644
--- 
a/incubator/src/org.apache.sis.storage.json/main/org/apache/sis/storage/geojson/GeoJSONMapper.java
+++ 
b/incubator/src/org.apache.sis.storage.json/main/org/apache/sis/storage/geojson/GeoJSONMapper.java
@@@ -1,0 -1,481 +1,479 @@@
+ /*
+  * 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.geojson;
+ 
+ import java.util.ArrayList;
+ import java.util.Iterator;
+ import java.util.LinkedHashMap;
+ import java.util.List;
+ import java.util.Map;
+ import java.util.function.Predicate;
+ import java.util.stream.Collectors;
+ import java.util.stream.Stream;
++import org.apache.sis.feature.AbstractFeature;
++import org.apache.sis.feature.AbstractIdentifiedType;
++import org.apache.sis.feature.AbstractOperation;
++import org.apache.sis.feature.DefaultAttributeType;
++import org.apache.sis.feature.DefaultFeatureType;
+ import org.apache.sis.feature.internal.shared.AttributeConvention;
+ import org.apache.sis.referencing.IdentifiedObjects;
+ import org.apache.sis.storage.DataStoreException;
+ import org.apache.sis.storage.FeatureSet;
+ import org.apache.sis.storage.geojson.binding.GeoJSONFeature;
+ import org.apache.sis.storage.geojson.binding.GeoJSONFeatureCollection;
+ import org.apache.sis.storage.geojson.binding.GeoJSONGeometry;
+ import org.apache.sis.storage.geojson.binding.GeoJSONGeometryCollection;
+ import org.apache.sis.storage.geojson.binding.GeoJSONLineString;
+ import org.apache.sis.storage.geojson.binding.GeoJSONMultiLineString;
+ import org.apache.sis.storage.geojson.binding.GeoJSONMultiPoint;
+ import org.apache.sis.storage.geojson.binding.GeoJSONMultiPolygon;
+ import org.apache.sis.storage.geojson.binding.GeoJSONPoint;
+ import org.apache.sis.storage.geojson.binding.GeoJSONPolygon;
+ import org.locationtech.jts.geom.CoordinateSequence;
+ import org.locationtech.jts.geom.Envelope;
+ import org.locationtech.jts.geom.Geometry;
+ import org.locationtech.jts.geom.GeometryCollection;
+ import org.locationtech.jts.geom.LineString;
+ import org.locationtech.jts.geom.MultiLineString;
+ import org.locationtech.jts.geom.MultiPoint;
+ import org.locationtech.jts.geom.MultiPolygon;
+ import org.locationtech.jts.geom.Point;
+ import org.locationtech.jts.geom.Polygon;
 -import org.opengis.feature.AttributeType;
 -import org.opengis.feature.Feature;
 -import org.opengis.feature.FeatureType;
 -import org.opengis.feature.IdentifiedType;
 -import org.opengis.feature.Operation;
 -import org.opengis.feature.PropertyNotFoundException;
 -import org.opengis.feature.PropertyType;
+ import org.opengis.referencing.crs.CoordinateReferenceSystem;
+ 
+ /**
+  *
+  * @author Johann Sorel (Geomatys)
+  */
+ public final class GeoJSONMapper {
+ 
+     private boolean bboxOnCollection = false;
+     private boolean bboxOnFeature = false;
+     private boolean bboxOnGeometry = false;
+     private boolean includeFeatureId = true;
+     private boolean includeTypeOnFeature = false;
+     private boolean includeTypeOnCollection = false;
+     private boolean includeCoordRefSysOnCollection = false;
+     private boolean includeCoordRefSysOnFeature = false;
+     private boolean includeCoordRefSysOnGeometry = false;
+ 
+     public void setBboxOnCollection(boolean bboxOnCollection) {
+         this.bboxOnCollection = bboxOnCollection;
+     }
+ 
+     public boolean isBboxOnCollection() {
+         return bboxOnCollection;
+     }
+ 
+     public void setBboxOnFeature(boolean bboxOnFeature) {
+         this.bboxOnFeature = bboxOnFeature;
+     }
+ 
+     public boolean isBboxOnFeature() {
+         return bboxOnFeature;
+     }
+ 
+     public void setBboxOnGeometry(boolean bboxOnGeometry) {
+         this.bboxOnGeometry = bboxOnGeometry;
+     }
+ 
+     public boolean isBboxOnGeometry() {
+         return bboxOnGeometry;
+     }
+ 
+     public void setIncludeFeatureId(boolean includeFeatureId) {
+         this.includeFeatureId = includeFeatureId;
+     }
+ 
+     public boolean isIncludeFeatureId() {
+         return includeFeatureId;
+     }
+ 
+     public void setIncludeTypeOnFeature(boolean includeTypeOnFeature) {
+         this.includeTypeOnFeature = includeTypeOnFeature;
+     }
+ 
+     public boolean isIncludeTypeOnFeature() {
+         return includeTypeOnFeature;
+     }
+ 
+     public void setIncludeTypeOnCollection(boolean includeTypeOnCollection) {
+         this.includeTypeOnCollection = includeTypeOnCollection;
+     }
+ 
+     public boolean isIncludeTypeOnCollection() {
+         return includeTypeOnCollection;
+     }
+ 
+     public void setIncludeCoordRefSysOnCollection(boolean 
includeCoordRefSysOnCollection) {
+         this.includeCoordRefSysOnCollection = includeCoordRefSysOnCollection;
+     }
+ 
+     public boolean isIncludeCoordRefSysOnCollection() {
+         return includeCoordRefSysOnCollection;
+     }
+ 
+     public void setIncludeCoordRefSysOnFeature(boolean 
includeCoordRefSysOnFeature) {
+         this.includeCoordRefSysOnFeature = includeCoordRefSysOnFeature;
+     }
+ 
+     public boolean isIncludeCoordRefSysOnFeature() {
+         return includeCoordRefSysOnFeature;
+     }
+ 
+     public void setIncludeCoordRefSysOnGeometry(boolean 
includeCoordRefSysOnGeometry) {
+         this.includeCoordRefSysOnGeometry = includeCoordRefSysOnGeometry;
+     }
+ 
+     public boolean isIncludeCoordRefSysOnGeometry() {
+         return includeCoordRefSysOnGeometry;
+     }
+ 
 -    public GeoJSONFeature transform(Feature feature) throws 
DataStoreException {
 -        final FeatureType type = feature.getType();
++    public GeoJSONFeature transform(AbstractFeature feature) throws 
DataStoreException {
++        final DefaultFeatureType type = feature.getType();
+ 
+         final GeoJSONFeature gf = new GeoJSONFeature();
+ 
+         if (includeTypeOnFeature) {
+             gf.setFeatureType(getTypeName(type));
+         }
+ 
+         if (includeFeatureId && 
feature.getType().hasProperty(AttributeConvention.IDENTIFIER)) {    // TODO: 
should be determined in advance.
+             Object id = 
feature.getPropertyValue(AttributeConvention.IDENTIFIER);
+             if (id != null) {
+                 gf.setId(id);
+             }
+         }
+         if (includeCoordRefSysOnFeature) {
+             gf.setCoordRefSys(getCoordRefSys(type));
+         }
+ 
+         if (feature.getType().hasProperty(AttributeConvention.GEOMETRY)) {  
// TODO: should be determined in advance.
+             Object geom = 
feature.getPropertyValue(AttributeConvention.GEOMETRY);
+             if (geom instanceof Geometry g) {
+                 GeoJSONGeometry json = transform(g);
+                 gf.setGeometry(json);
+ 
+                 if (bboxOnFeature) {
+                     Envelope env = g.getEnvelopeInternal();
+                     if (env != null && !env.isNull()) {
+                         final List<Double> bbox = new ArrayList<>();
+                         bbox.add(env.getMinX());
+                         bbox.add(env.getMinY());
+                         bbox.add(env.getMaxX());
+                         bbox.add(env.getMaxY());
+                         gf.setBbox(bbox);
+                     }
+                 }
+             }
+         }
+ 
+         final Map<String,Object> properties = new LinkedHashMap();
+         gf.setProperties(properties);
 -        for (PropertyType pt : type.getProperties(true)) {
++        for (AbstractIdentifiedType pt : type.getProperties(true)) {
+             if (AttributeConvention.contains(pt.getName())) continue;
 -            if (pt instanceof AttributeType) {
++            if (pt instanceof DefaultAttributeType) {
+                 final String name = pt.getName().toString();
+                 final Object value = feature.getPropertyValue(name);
+                 properties.put(name, value);
+             }
+         }
+ 
+         return gf;
+     }
+ 
+     public GeoJSONFeatureCollection transform(FeatureSet features) throws 
DataStoreException {
 -        final FeatureType type = features.getType();
++        final DefaultFeatureType type = features.getType();
+ 
+         final GeoJSONFeatureCollection cl = new GeoJSONFeatureCollection();
+         final List<GeoJSONFeature> lst = new ArrayList<>();
 -        try (Stream<Feature> stream = features.features(false)) {
 -            final Iterator<Feature> ite = stream.iterator();
++        try (Stream<AbstractFeature> stream = features.features(false)) {
++            final Iterator<AbstractFeature> ite = stream.iterator();
+             while (ite.hasNext()) {
+                 lst.add(transform(ite.next()));
+             }
+         }
+ 
+         cl.setFeatures(lst);
+ 
+         if (includeTypeOnCollection) {
+             cl.setFeatureType(getTypeName(type));
+         }
+         if (includeCoordRefSysOnCollection) {
+             cl.setCoordRefSys(getCoordRefSys(type));
+         }
+         if (bboxOnCollection) {
+             org.opengis.geometry.Envelope env = 
features.getEnvelope().orElse(null);
+             if (env != null) {
+                 final List<Double> bbox = new ArrayList<>();
+                 bbox.add(env.getMinimum(0));
+                 bbox.add(env.getMinimum(1));
+                 bbox.add(env.getMaximum(0));
+                 bbox.add(env.getMaximum(1));
+                 cl.setBbox(bbox);
+             }
+         }
+ 
+         return cl;
+     }
+ 
+     public GeoJSONGeometry transform(Geometry geom) throws DataStoreException 
{
+ 
+         final GeoJSONGeometry res;
+         if (geom instanceof Point cdt) {
+             final GeoJSONPoint json = new GeoJSONPoint();
+             CoordinateSequence cs = cdt.getCoordinateSequence();
+             json.setCoordinates(toList(cs).get(0));
+             res = json;
+         } else if (geom instanceof LineString cdt) {
+             final GeoJSONLineString json = new GeoJSONLineString();
+             CoordinateSequence cs = cdt.getCoordinateSequence();
+             json.setCoordinates(toList(cs));
+             res = json;
+         } else if (geom instanceof Polygon cdt) {
+             final GeoJSONPolygon json = new GeoJSONPolygon();
+             final List<List<List<Double>>> lst = new ArrayList<>();
+             lst.add(toList(cdt.getExteriorRing().getCoordinateSequence()));
+             for (int i = 0, n = cdt.getNumInteriorRing(); i < n; i++) {
+                 
lst.add(toList(cdt.getInteriorRingN(i).getCoordinateSequence()));
+             }
+             json.setCoordinates(lst);
+             res = json;
+         } else if (geom instanceof MultiPoint cdt) {
+             final GeoJSONMultiPoint json = new GeoJSONMultiPoint();
+             final List<List<Double>> lst = new ArrayList<>();
+             for (int i = 0, n = cdt.getNumGeometries(); i < n; i++) {
+                 
lst.add(toList(((Point)cdt.getGeometryN(i)).getCoordinateSequence()).get(0));
+             }
+             json.setCoordinates(lst);
+             res = json;
+         } else if (geom instanceof MultiLineString cdt) {
+             final GeoJSONMultiLineString json = new GeoJSONMultiLineString();
+             final List<List<List<Double>>> lst = new ArrayList<>();
+             for (int i = 0, n = cdt.getNumGeometries(); i < n; i++) {
+                 
lst.add(toList(((LineString)cdt.getGeometryN(i)).getCoordinateSequence()));
+             }
+             json.setCoordinates(lst);
+             res = json;
+         } else if (geom instanceof MultiPolygon cdt) {
+             final GeoJSONMultiPolygon json = new GeoJSONMultiPolygon();
+             final List<List<List<List<Double>>>> lst = new ArrayList<>();
+             for (int i = 0, n = cdt.getNumGeometries(); i < n; i++) {
+                 final Polygon pl = (Polygon) cdt.getGeometryN(i);
+                 final List<List<List<Double>>> sublst = new ArrayList<>();
+                 
sublst.add(toList(pl.getExteriorRing().getCoordinateSequence()));
+                 for (int j = 0, k = pl.getNumInteriorRing(); j < k; j++) {
+                     
sublst.add(toList(pl.getInteriorRingN(j).getCoordinateSequence()));
+                 }
+                 lst.add(sublst);
+             }
+             json.setCoordinates(lst);
+             res = json;
+         } else if (geom instanceof GeometryCollection cdt) {
+             final GeoJSONGeometryCollection json = new 
GeoJSONGeometryCollection();
+             final List<GeoJSONGeometry> geometries = new ArrayList<>();
+             for (int i = 0, n = cdt.getNumGeometries(); i < n; i++) {
+                 final Geometry pl = (Geometry) cdt.getGeometryN(i);
+                 geometries.add(transform(pl));
+             }
+             json.setGeometries(geometries);
+             res = json;
+         } else {
+             throw new DataStoreException("Geometry not supported yet " + 
geom);
+         }
+ 
+         if (bboxOnGeometry) {
+             Envelope env = geom.getEnvelopeInternal();
+             if (env != null && !env.isNull()) {
+                 final List<Double> bbox = new ArrayList<>();
+                 bbox.add(env.getMinX());
+                 bbox.add(env.getMinY());
+                 bbox.add(env.getMaxX());
+                 bbox.add(env.getMaxY());
+                 res.setBbox(bbox);
+             }
+         }
+ 
+         if (includeCoordRefSysOnGeometry) {
+             Object userData = geom.getUserData();
+             if (userData instanceof CoordinateReferenceSystem crs) {
+                 res.setCoordRefSys(getCoordRefSys(crs));
+             }
+         }
+ 
+         return res;
+     }
+ 
+     private List<List<Double>> toList(CoordinateSequence cs) throws 
DataStoreException {
+         final int dim = cs.getDimension();
+         final int size = cs.size();
+         final List<List<Double>> lst = new ArrayList<>(size);
+         switch (dim) {
+             case 2 : {
+                 for (int i = 0; i < size; i++) {
+                     lst.add(List.of(cs.getOrdinate(i, 0), cs.getOrdinate(i, 
1)));
+                 }
+             } break;
+             case 3 : {
+                 for (int i = 0; i < size; i++) {
+                     lst.add(List.of(cs.getOrdinate(i, 0), cs.getOrdinate(i, 
1), cs.getOrdinate(i, 2)));
+                 }
+             } break;
+             case 4 : {
+                 for (int i = 0; i < size; i++) {
+                     lst.add(List.of(cs.getOrdinate(i, 0), cs.getOrdinate(i, 
1), cs.getOrdinate(i, 2), cs.getOrdinate(i, 3)));
+                 }
+             } break;
+             default: throw new DataStoreException("Unexpected coordinate 
sequence dimension " + dim);
+         }
+         return lst;
+     }
+ 
 -    private static String getTypeName(FeatureType type) {
++    private static String getTypeName(DefaultFeatureType type) {
+         return type.getName().toString();
+     }
+ 
+     /**
+      * Returns the 'coordRefSys' value to use for the given feature type: a 
plain CRS
+      * identifier URI {@link String}, per the common encoding used by real 
JSON-FG producers
+      * (see JSON-FG version 1.0.0, {@code coordrefsys.json}).
+      */
 -    private static Object getCoordRefSys(FeatureType ft) {
++    private static Object getCoordRefSys(DefaultFeatureType ft) {
+         final CoordinateReferenceSystem crs = getCRS(ft);
+         return getCoordRefSys(crs);
+     }
+ 
+     private static Object getCoordRefSys(CoordinateReferenceSystem crs) {
+         if (crs == null) return null;
+         return IdentifiedObjects.getIdentifierOrName(crs);
+     }
+ 
+ 
+     /**
+      * Extract the coordinate reference system associated to the primary 
geometry
+      * of input data type.
+      *
+      * @implNote
 -     * Primary geometry is determined using {@link 
#getDefaultGeometry(org.opengis.feature.FeatureType) }.
++     * Primary geometry is determined using {@link 
#getDefaultGeometry(DefaultFeatureType)}.
+      *
+      * @param type The data type to extract reference system from.
+      * @return The CRS associated to the default geometry of this data type, 
or
+      * a null value if we cannot determine what is the primary geometry of the
+      * data type. Note that a null value is also returned if a geometry 
property
+      * is found, but no CRS characteristics is associated with it.
+      */
 -    private static CoordinateReferenceSystem getCRS(FeatureType type){
++    private static CoordinateReferenceSystem getCRS(DefaultFeatureType type){
+         try {
+             return getCRS(getDefaultGeometry(type));
+         } catch (IllegalArgumentException|IllegalStateException ex) {
+             //no default geometry property
+             return null;
+         }
+     }
+ 
+     /**
+      * Extract CRS characteristic if it exist.
+      *
+      * @param type
+      * @return CoordinateReferenceSystem or null
+      */
 -    private static CoordinateReferenceSystem getCRS(PropertyType type){
++    private static CoordinateReferenceSystem getCRS(AbstractIdentifiedType 
type){
+         return getCharacteristicValue(type, AttributeConvention.CRS, null);
+     }
+ 
+     /**
+      * Extract characteristic value if it exist.
+      *
+      * @param <T> expected value class
+      * @param type base type to search in
+      * @param charName characteristic name
+      * @param defaulValue default value if characteristic is missing or null.
+      * @return characteristic value or default value is not found
+      */
 -    private static <T> T getCharacteristicValue(PropertyType type, String 
charName, T defaulValue){
 -        while(type instanceof Operation){
 -            type = (PropertyType) ((Operation)type).getResult();
++    private static <T> T getCharacteristicValue(AbstractIdentifiedType type, 
String charName, T defaulValue){
++        while(type instanceof AbstractOperation){
++            type = (AbstractIdentifiedType) 
((AbstractOperation)type).getResult();
+         }
 -        if(type instanceof AttributeType){
 -            final AttributeType at = (AttributeType) 
((AttributeType)type).characteristics().get(charName);
++        if(type instanceof DefaultAttributeType){
++            final DefaultAttributeType at = (DefaultAttributeType) 
((DefaultAttributeType)type).characteristics().get(charName);
+             if(at!=null){
+                 T val = (T) at.getDefaultValue();
+                 return val==null ? defaulValue : val;
+             }
+         }
+         return defaulValue;
+     }
+ 
+     /**
+      * Search for the main geometric property in the given type. We'll search
+      * for an SIS convention first (see
+      * {@link AttributeConvention#GEOMETRY_PROPERTY}. If no convention is set 
on
+      * the input type, we'll check if it contains a single geometric property.
+      * If it's the case, we return it. Otherwise (no or multiple geometries), 
we
+      * throw an exception.
+      *
+      * @param type The data type to search into.
+      * @return The main geometric property we've found.
 -     * @throws PropertyNotFoundException If no geometric property is available
++     * @throws IllegalArgumentException If no geometric property is available
+      * in the given type.
+      * @throws IllegalStateException If no convention is set (see
+      * {@link AttributeConvention#GEOMETRY_PROPERTY}), and we've found more 
than
+      * one geometry.
+      */
 -    private static PropertyType getDefaultGeometry(final FeatureType type) 
throws PropertyNotFoundException, IllegalStateException {
 -        PropertyType geometry;
++    private static AbstractIdentifiedType getDefaultGeometry(final 
DefaultFeatureType type) throws IllegalArgumentException, IllegalStateException 
{
++        AbstractIdentifiedType geometry;
+         try {
+             geometry = type.getProperty(AttributeConvention.GEOMETRY);
 -        } catch (PropertyNotFoundException e) {
++        } catch (IllegalArgumentException e) {
+             try {
+                 geometry = searchForGeometry(type);
+             } catch (RuntimeException e2) {
+                 e2.addSuppressed(e);
+                 throw e2;
+             }
+         }
+         return geometry;
+     }
+ 
+     /**
+      * Search for a geometric attribute outside SIS conventions. More 
accurately,
+      * we expect the given type to have a single geometry attribute. If many 
are
+      * found, an exception is thrown.
+      *
+      * @param type The data type to search into.
+      * @return The only geometric property we've found.
 -     * @throws PropertyNotFoundException If no geometric property is 
available in
++     * @throws IllegalArgumentException If no geometric property is available 
in
+      * the given type.
+      * @throws IllegalStateException If we've found more than one geometry.
+      */
 -    private static PropertyType searchForGeometry(final FeatureType type) 
throws PropertyNotFoundException, IllegalStateException {
 -        final Predicate<IdentifiedType> isNotConvention = p -> 
!AttributeConvention.contains(p.getName());
 -        final List<? extends PropertyType> geometries = 
type.getProperties(true).stream()
++    private static AbstractIdentifiedType searchForGeometry(final 
DefaultFeatureType type) throws IllegalArgumentException, IllegalStateException 
{
++        final Predicate<AbstractIdentifiedType> isNotConvention = p -> 
!AttributeConvention.contains(p.getName());
++        final List<? extends AbstractIdentifiedType> geometries = 
type.getProperties(true).stream()
+                 .filter(isNotConvention)
+                 .filter(AttributeConvention::isGeometryAttribute)
+                 .collect(Collectors.toList());
+ 
+         if (geometries.size() < 1) {
 -            throw new PropertyNotFoundException("No geometric property can be 
found outside of sis convention.");
++            throw new IllegalArgumentException("No geometric property can be 
found outside of sis convention.");
+         } else if (geometries.size() > 1) {
+             throw new IllegalStateException("Multiple geometries found. We 
don't know which one to select.");
+         } else {
+             return geometries.get(0);
+         }
+     }
+ 
+ }
diff --cc netbeans-project/ivy.xml
index 9e591bf630,4e265323ba..9be4d52407
--- a/netbeans-project/ivy.xml
+++ b/netbeans-project/ivy.xml
@@@ -39,5 -38,10 +39,8 @@@
          <dependency org="gov.nist.math"          name="jama"                  
  rev="1.0.3"/>
          <dependency org="net.sf.geographiclib"   name="GeographicLib-Java"    
  rev="2.0"/>
          <dependency org="org.xerial"             name="sqlite-jdbc"           
  rev="3.45.1.0"/>
 -        <dependency org="org.xerial"             name="sqlite-jdbc"           
  rev="3.45.1.0"/>
 -        <dependency org="org.xerial"             name="sqlite-jdbc"           
  rev="3.45.1.0"/>
+         <dependency org="com.fasterxml.jackson.core" name="jackson-core"      
  rev="2.22.1"/>
+         <dependency org="com.fasterxml.jackson.core" 
name="jackson-annotations" rev="2.22"/>
+         <dependency org="com.fasterxml.jackson.core" name="jackson-databind"  
  rev="2.22.1"/>
      </dependencies>
  </ivy-module>

Reply via email to