You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by js...@apache.org on 2023/02/13 16:01:18 UTC
[sis] 01/01: feat(Geometry): add parent interface for upcoming geometry API
This is an automated email from the ASF dual-hosted git repository.
jsorel pushed a commit to branch feat/geometry
in repository https://gitbox.apache.org/repos/asf/sis.git
commit f48d2d32b178be6f2ec71040c7331e97f101a7fe
Author: jsorel <jo...@geomatys.com>
AuthorDate: Mon Feb 13 17:00:27 2023 +0100
feat(Geometry): add parent interface for upcoming geometry API
---
.../sis/internal/geometry/AttributeType.java | 92 +++++
.../sis/internal/geometry/AttributesType.java | 58 +++
.../org/apache/sis/internal/geometry/Geometry.java | 158 ++++++++
.../org/apache/sis/internal/math/DataType.java | 430 +++++++++++++++++++++
.../org/apache/sis/internal/math/SampleSystem.java | 152 ++++++++
5 files changed, 890 insertions(+)
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/geometry/AttributeType.java b/core/sis-feature/src/main/java/org/apache/sis/internal/geometry/AttributeType.java
new file mode 100644
index 0000000000..4a5c880af1
--- /dev/null
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/geometry/AttributeType.java
@@ -0,0 +1,92 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.internal.geometry;
+
+import org.apache.sis.internal.math.DataType;
+import org.apache.sis.internal.math.SampleSystem;
+
+/**
+ *
+ * @author Johann Sorel (Geomatys)
+ */
+public interface AttributeType {
+
+ /**
+ * This attribute correspond to SFA coordinates.
+ *
+ * GLTF position attribute.
+ * POSITION,VEC3
+ * Unitless XYZ vertex positions
+ */
+ String ATT_POSITION = "POSITION";
+ /**
+ * GLTF normal attribute.
+ * NORMAL,VEC3
+ * Normalized XYZ vertex normals
+ */
+ String ATT_NORMAL = "NORMAL";
+ /**
+ * GLTF tangent attribute.
+ * TANGENT,VEC4
+ * XYZW vertex tangents where the XYZ portion is normalized,
+ * and the W component is a sign value (-1 or +1) indicating handedness of the tangent basis
+ */
+ String ATT_TANGENT = "TANGENT";
+ /**
+ * GLTF indexed texture coordinate attribute.
+ * TEXCOORD_n,VEC2
+ * ST texture coordinates
+ */
+ String ATT_TEXCOORD = "TEXCOORD";
+ /**
+ * GLTF indexed color attribute.
+ * COLOR_n,VEC3/VEC4
+ * RGB or RGBA vertex color linear multiplier
+ */
+ String ATT_COLOR = "COLOR";
+ /**
+ * GLTF indexed joints attribute.
+ * JOINTS_n,VEC4
+ * Skinned Mesh Attribute
+ */
+ String ATT_JOINTS = "JOINTS";
+ /**
+ * GLTF indexed weights attribute.
+ * JOINTS_n,VEC4
+ * Skinned Mesh Attribute
+ */
+ String ATT_WEIGHTS = "WEIGHTS";
+
+ /**
+ * @return attribute name, not null.
+ */
+ String getName();
+
+ /**
+ * Returns attribute system for given name.
+ *
+ * @return system or null.
+ */
+ SampleSystem getSampleSystem();
+
+ /**
+ * Returns attribute type for given name.
+ *
+ * @return type or null.
+ */
+ DataType getDataType();
+}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/geometry/AttributesType.java b/core/sis-feature/src/main/java/org/apache/sis/internal/geometry/AttributesType.java
new file mode 100644
index 0000000000..6968e30d56
--- /dev/null
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/geometry/AttributesType.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.internal.geometry;
+
+import java.util.Collection;
+import org.opengis.feature.PropertyNotFoundException;
+
+/**
+ * An attributesType is a description of geometry attributes.
+ * <p>
+ * Based on specification :
+ * <ul>
+ * <li>OGC Simple Feature Access - https://www.ogc.org/standards/sfa</li>
+ * <li>Khronos GLTF-2 - https://github.com/KhronosGroup/glTF/tree/main/specification/2.0</li>
+ * </ul>
+ *
+ * <p>
+ * Differences from OGC Simple Feature Access :<br>
+ * In SFA a single attribute is possible, and exist if method isMeasured returns true.<br>
+ * Transposed to the GPU model we obtain two possible attributes : POSITION(2D or 3D) and MEASURE(1D).
+ *
+ * <p>
+ * The GPU model as defined by GLTF is more rich and allows any number of attributes.<br>
+ * Each attribute may itself be composed of 1 to 16 values.
+ *
+ * @author Johann Sorel (Geomatys)
+ */
+public interface AttributesType {
+
+ /**
+ * @param name searched attribute name
+ * @return requested attribute type, never null
+ * @throws PropertyNotFoundException if not found
+ */
+ AttributeType getAttribute(String name) throws PropertyNotFoundException;
+
+ /**
+ * Returns collection of all attributes.
+ *
+ * @return never null, can be empty
+ */
+ Collection<AttributeType> getAttributes();
+
+}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/geometry/Geometry.java b/core/sis-feature/src/main/java/org/apache/sis/internal/geometry/Geometry.java
new file mode 100644
index 0000000000..4b699caaff
--- /dev/null
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/geometry/Geometry.java
@@ -0,0 +1,158 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.internal.geometry;
+
+import java.util.Map;
+import org.opengis.geometry.Envelope;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+
+/**
+ * Parent interface of any geometry.
+ * <p>
+ * Based on specification :
+ * <ul>
+ * <li>OGC Simple Feature Access - https://www.ogc.org/standards/sfa</li>
+ * <li>Khronos GLTF-2 - https://github.com/KhronosGroup/glTF/tree/main/specification/2.0</li>
+ * </ul>
+ *
+ * <p>
+ * Differences from OGC Simple Feature Access :
+ * <ul>
+ * <li>SRID : replaced by getCoordinateReferenceSystem</li>
+ * <li>is3D : look at geometry CoordinateReferenceSystem instead</li>
+ * <li>isMeasured() :
+ * SFA defines only a single measure attribute attached to the geometry.
+ * Khronos/GPU geometries may defined multiple and complex attributes.
+ * Therefor a dedicated interface AttributesType is defined and accessed with {@linkplain #getAttributesType() }.</li>
+ * <li>spatial and relation methods : found on GeometryOperations.</li>
+ * </ul>
+ *
+ * <p>
+ * To be reviewed with upcoming OGC Geometry / ISO-19107.
+ *
+ * @author Johann Sorel (Geomatys)
+ */
+public interface Geometry {
+
+ /**
+ * Get geometry coordinate system.
+ *
+ * @return never null
+ */
+ CoordinateReferenceSystem getCoordinateReferenceSystem();
+
+ /**
+ * Set coordinate system in which the coordinates are declared.
+ * This method does not transform the coordinates.
+ *
+ * @param crs , not null
+ * @Throws IllegalArgumentException if coordinate system is not compatible with geometrie.
+ */
+ void setCoordinateReferenceSystem(CoordinateReferenceSystem crs) throws IllegalArgumentException;
+
+ /**
+ * Get geometry attributes type.
+ *
+ * @return attributes type, never null
+ */
+ AttributesType getAttributesType();
+
+ /**
+ * Get the geometry number of dimensions.<br>
+ * This is the same as coordinate system dimension.
+ *
+ * @return number of dimension
+ */
+ default int getDimension() {
+ return getCoordinateReferenceSystem().getCoordinateSystem().getDimension();
+ }
+
+ /**
+ * Returns the name of the instantiable subtype of Geometry of which this geometric object is an instantiable member.<br>
+ * The name of the subtype of Geometry is returned as a string.
+ *
+ * @see OGC Simple Feature Access 1.2.1 - 6.1.2.2
+ * @return geometry subtype name.
+ */
+ String getGeometryType();
+
+ /**
+ * The minimum bounding box for this Geometry, returned as a Geometry.<br>
+ * The polygon is defined by the corner points of the bounding box [(MINX, MINY), (MAXX, MINY), (MAXX, MAXY), (MINX, MAXY), (MINX, MINY)].<br>
+ * Minimums for Z and M may be added.<br>
+ * The simplest representation of an Envelope is as two direct positions, one containing all the minimums, and another all the maximums.<br>
+ * In some cases, this coordinate will be outside the range of validity for the Spatial Reference System.
+ *
+ * @see OGC Simple Feature Access 1.2.1 - 6.1.2.2
+ * @return Envelope in geometry coordinate reference system.
+ */
+ Envelope getEnvelope();
+
+ /**
+ * Exports this geometric object to a specific Well-known Text Representation of Geometry.
+ *
+ * @see OGC Simple Feature Access 1.2.1 - 6.1.2.2
+ * @return this geometry in Well-known Text
+ */
+ String asText();
+
+ /**
+ * Exports this geometric object to a specific Well-known Binary Representation of Geometry.
+ *
+ * @see OGC Simple Feature Access 1.2.1 - 6.1.2.2
+ * @return this geometry in Well-known Binary
+ */
+ byte[] asBinary();
+
+ /**
+ * Returns TRUE if this geometric object is the empty Geometry.
+ * If true, then this geometric object represents the empty point set ∅ for the coordinate space.
+ *
+ * @see OGC Simple Feature Access 1.2.1 - 6.1.2.2
+ * @return true if empty.
+ */
+ boolean isEmpty();
+
+ /**
+ * Returns TRUE if this geometric object has no anomalous geometric points, such as self intersection or self tangency.
+ * The description of each instantiable geometric class will include the specific conditions that cause an instance
+ * of that class to be classified as not simple.
+ *
+ * @see OGC Simple Feature Access 1.2.1 - 6.1.2.2
+ * @return true if geometry is simple
+ */
+ boolean isSimple();
+
+ /**
+ * Returns the closure of the combinatorial boundary of this geometric object (Reference [1], section 3.12.2).
+ * Because the result of this function is a closure, and hence topologically closed, the resulting boundary can be
+ * represented using representational Geometry primitives (Reference [1], section 3.12.2).
+ *
+ * @see OGC Simple Feature Access 1.2.1 - 6.1.2.2
+ * @return boundary of the geometry
+ */
+ Geometry boundary();
+
+ /**
+ * Map of properties for user needs.
+ * Those informations may be lost in geometry processes.
+ *
+ * @return Map, can be null if the geometry can not store additional informations.
+ */
+ Map<String,Object> userProperties();
+
+}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/math/DataType.java b/core/sis-feature/src/main/java/org/apache/sis/internal/math/DataType.java
new file mode 100644
index 0000000000..ad1b13f3fb
--- /dev/null
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/math/DataType.java
@@ -0,0 +1,430 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.internal.math;
+
+import java.awt.image.RasterFormatException;
+import org.apache.sis.internal.feature.Resources;
+import static org.apache.sis.internal.util.Numerics.MAX_INTEGER_CONVERTIBLE_TO_FLOAT;
+import org.apache.sis.measure.NumberRange;
+import org.apache.sis.util.ArgumentChecks;
+import org.apache.sis.util.Numbers;
+
+
+/**
+ * This class is a clone of Apache SIS org.apache.sis.image.DataType.
+ * But without image type restrictions.
+ *
+ * Normalized values definitions can be found at :
+ * https://www.khronos.org/opengl/wiki/Normalized_Integer
+ * https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_mesh_quantization/README.md
+ *
+ * @author Martin Desruisseaux (Geomatys)
+ * @author Johann Sorel (Geomatys)
+ */
+public enum DataType {
+
+ /**
+ * Signed 8-bits data.
+ */
+ BYTE(0),
+
+ /**
+ * Unsigned 8-bits data.
+ */
+ UBYTE(1),
+
+ /**
+ * Signed 16-bits data.
+ */
+ SHORT(2),
+
+ /**
+ * Unsigned 16-bits data.
+ */
+ USHORT(3),
+
+ /**
+ * Signed 32-bits data.
+ */
+ INT(4),
+
+ /**
+ * Unsigned 32-bits data.
+ */
+ UINT(5),
+
+ /**
+ * Signed 64-bits data.
+ */
+ LONG(6),
+
+ /**
+ * Single precision (32-bits) floating point data.
+ */
+ FLOAT(7),
+
+ /**
+ * Double precision (64-bits) floating point data.
+ */
+ DOUBLE(8),
+
+ /**
+ * Signed 8-bits data interpreted as a decimal in range [-1..1]
+ */
+ NORMALIZED_BYTE(9),
+
+ /**
+ * Unsigned 8-bits data interpreted as a decimal in range [0..1]
+ */
+ NORMALIZED_UBYTE(10),
+
+ /**
+ * Signed 16-bits data interpreted as a decimal in range [-1..1]
+ */
+ NORMALIZED_SHORT(11),
+
+ /**
+ * Unsigned 16-bits data interpreted as a decimal in range [0..1]
+ */
+ NORMALIZED_USHORT(12);
+
+ private final int order;
+
+ /**
+ * Creates a new enumeration.
+ */
+ private DataType(int order) {
+ this.order = order;
+ }
+
+ /**
+ * Returns the smallest data type capable to store the given range of values.
+ * If the given range uses a floating point type, there there is a choice:
+ *
+ * <ul>
+ * <li>If {@code asInteger} is {@code false}, then this method returns
+ * {@link #FLOAT} or {@link #DOUBLE} depending on the range type.</li>
+ * <li>Otherwise this method treats the floating point values as if they
+ * were integers, with minimum value rounded toward negative infinity
+ * and maximum value rounded toward positive infinity.</li>
+ * </ul>
+ *
+ * @param range the range of values.
+ * @param asInteger whether to handle floating point values as integers.
+ * @return smallest data type for the given range of values.
+ */
+ public static DataType forRange(final NumberRange<?> range, final boolean asInteger) {
+ ArgumentChecks.ensureNonNull("range", range);
+ final byte nt = Numbers.getEnumConstant(range.getElementType());
+ if (!asInteger) {
+ if (nt >= Numbers.DOUBLE) return DOUBLE;
+ if (nt >= Numbers.FRACTION) return FLOAT;
+ }
+ final double min = range.getMinDouble();
+ final double max = range.getMaxDouble();
+ if (nt < Numbers.BYTE || nt > Numbers.FLOAT || nt == Numbers.LONG) {
+ /*
+ * Value type is long, double, BigInteger, BigDecimal or unknown type.
+ * If conversions to 32 bits integers would lost integer digits, or if
+ * a bound is NaN, stick to the most conservative data buffer type.
+ */
+ if (!(min >= -MAX_INTEGER_CONVERTIBLE_TO_FLOAT - 0.5 &&
+ max < MAX_INTEGER_CONVERTIBLE_TO_FLOAT + 0.5))
+ {
+ return DOUBLE;
+ }
+ }
+ /*
+ * Check most common types first. If the range could be both signed and unsigned short,
+ * give precedence to unsigned values.
+ * If a bounds is NaN, fallback on TYPE_FLOAT.
+ */
+ final DataType type;
+ if (min >= -0.5 && max < 0xFF + 0.5) {
+ type = UBYTE;
+ } else if (min >= Byte.MIN_VALUE - 0.5 && max < 0xFF + 0.5) {
+ type = BYTE;
+ } else if (min >= -0.5 && max < 0xFFFF + 0.5) {
+ type = USHORT;
+ } else if (min >= Short.MIN_VALUE - 0.5 && max < Short.MAX_VALUE + 0.5) {
+ type = SHORT;
+ } else if (min >= - 0.5 && max < 4294967295L + 0.5) {
+ type = UINT;
+ } else if (min >= Integer.MIN_VALUE - 0.5 && max < Integer.MAX_VALUE + 0.5) {
+ type = INT;
+ } else if (min >= Long.MIN_VALUE - 0.5 && max < Long.MAX_VALUE + 0.5) {
+ type = LONG;
+ } else {
+ type = FLOAT;
+ }
+ return type;
+ }
+
+ /**
+ * Returns the data type for the given primitive type. The given {@code type} should be a primitive
+ * type such as {@link Short#TYPE}, but wrappers class such as {@code Short.class} are also accepted.
+ *
+ * @param type the primitive type or its wrapper class.
+ * @param unsigned whether the type should be considered unsigned.
+ * @return the data type (never {@code null}) for the given primitive type.
+ * @throws RasterFormatException if the given type is not a recognized.
+ */
+ public static DataType forPrimitiveType(final Class<?> type, final boolean unsigned) {
+ switch (Numbers.getEnumConstant(type)) {
+ case Numbers.BYTE: return unsigned ? UBYTE : BYTE;
+ case Numbers.SHORT: return unsigned ? USHORT : SHORT;
+ case Numbers.INTEGER: return unsigned ? UINT : INT;
+ case Numbers.LONG: return LONG;
+ case Numbers.FLOAT: return FLOAT;
+ case Numbers.DOUBLE: return DOUBLE;
+ }
+ throw new RasterFormatException(Resources.format(Resources.Keys.UnknownDataType_1, type));
+ }
+
+ /**
+ * Returns the size in bits of this data type.
+ *
+ * @return size in bits of this data type.
+ */
+ public int size() {
+ switch (this) {
+ case BYTE :
+ case UBYTE :
+ return 8;
+ case SHORT :
+ case USHORT :
+ return 16;
+ case INT :
+ case UINT :
+ case FLOAT :
+ return 32;
+ case LONG :
+ case DOUBLE :
+ return 64;
+ default :
+ throw new IllegalStateException("Unexpected type " + this.name());
+ }
+ }
+
+ /**
+ * Returns whether this type is an unsigned integer type.
+ * Unsigned types are {@link #UBYTE}, {@link #USHORT} and {@link #UINT}.
+ *
+ * @return {@code true} if this type is an unsigned integer type.
+ */
+ public boolean isUnsigned() {
+ switch (this) {
+ case UBYTE :
+ case USHORT :
+ case UINT :
+ return true;
+ default :
+ return false;
+ }
+ }
+
+ /**
+ * Returns whether this type is an integer type, signed or not.
+ *
+ * @return {@code true} if this type is an integer type.
+ */
+ public boolean isInteger() {
+ switch (this) {
+ case BYTE :
+ case UBYTE :
+ case SHORT :
+ case USHORT :
+ case INT :
+ case UINT :
+ case LONG :
+ return true;
+ default :
+ return false;
+ }
+ }
+
+ /**
+ * Returns the smallest floating point type capable to store all values of this type
+ * without precision lost. This method returns:
+ *
+ * <ul>
+ * <li>{@link #DOUBLE} if this data type is {@link #DOUBLE}, {@link #INT}, {@link #UINT} or {@link #LONG}.</li>
+ * <li>{@link #FLOAT} for all other types.</li>
+ * </ul>
+ *
+ * The promotion of integer values to floating point values is sometime necessary
+ * when the image may contain {@link Float#NaN} values.
+ *
+ * @return the smallest of {@link #FLOAT} or {@link #DOUBLE} types
+ * which can store all values of this type without any lost.
+ */
+ public DataType toFloat() {
+ switch (this) {
+ case INT :
+ case UINT :
+ case LONG :
+ case DOUBLE :
+ return DOUBLE;
+ default :
+ return FLOAT;
+ }
+ }
+
+ /**
+ * Get the widest datatype which may contain both types.
+ */
+ public static DataType largest(DataType type1, DataType type2) {
+ if (type1.equals(type2)) {
+ return type1;
+ }
+ if (type1.order > type2.order) {
+ DataType t = type1;
+ type1 = type2;
+ type2 = t;
+ }
+
+ switch (type1) {
+ case BYTE :
+ switch (type2) {
+ case UBYTE : return SHORT;
+ case SHORT : return SHORT;
+ case USHORT : return INT;
+ case INT : return INT;
+ case UINT : return LONG;
+ case LONG : return LONG;
+ case FLOAT : return FLOAT;
+ case DOUBLE : return DOUBLE;
+ case NORMALIZED_BYTE : return FLOAT;
+ case NORMALIZED_UBYTE : return FLOAT;
+ case NORMALIZED_SHORT : return FLOAT;
+ case NORMALIZED_USHORT : return FLOAT;
+ default : throw new IllegalArgumentException("Unexpected types " + type1 + " " + type2);
+ }
+ case UBYTE :
+ switch (type2) {
+ case SHORT : return SHORT;
+ case USHORT : return USHORT;
+ case INT : return INT;
+ case UINT : return UINT;
+ case LONG : return LONG;
+ case FLOAT : return FLOAT;
+ case DOUBLE : return DOUBLE;
+ case NORMALIZED_BYTE : return FLOAT;
+ case NORMALIZED_UBYTE : return FLOAT;
+ case NORMALIZED_SHORT : return FLOAT;
+ case NORMALIZED_USHORT : return FLOAT;
+ default : throw new IllegalArgumentException("Unexpected types " + type1 + " " + type2);
+ }
+ case SHORT :
+ switch (type2) {
+ case USHORT : return INT;
+ case INT : return INT;
+ case UINT : return LONG;
+ case LONG : return LONG;
+ case FLOAT : return FLOAT;
+ case DOUBLE : return DOUBLE;
+ case NORMALIZED_BYTE : return FLOAT;
+ case NORMALIZED_UBYTE : return FLOAT;
+ case NORMALIZED_SHORT : return FLOAT;
+ case NORMALIZED_USHORT : return FLOAT;
+ default : throw new IllegalArgumentException("Unexpected types " + type1 + " " + type2);
+ }
+ case USHORT :
+ switch (type2) {
+ case INT : return INT;
+ case UINT : return UINT;
+ case LONG : return LONG;
+ case FLOAT : return FLOAT;
+ case DOUBLE : return DOUBLE;
+ case NORMALIZED_BYTE : return FLOAT;
+ case NORMALIZED_UBYTE : return FLOAT;
+ case NORMALIZED_SHORT : return FLOAT;
+ case NORMALIZED_USHORT : return FLOAT;
+ default : throw new IllegalArgumentException("Unexpected types " + type1 + " " + type2);
+ }
+ case INT :
+ switch (type2) {
+ case UINT : return LONG;
+ case LONG : return LONG;
+ case FLOAT : return FLOAT;
+ case DOUBLE : return DOUBLE;
+ case NORMALIZED_BYTE : return FLOAT;
+ case NORMALIZED_UBYTE : return FLOAT;
+ case NORMALIZED_SHORT : return FLOAT;
+ case NORMALIZED_USHORT : return FLOAT;
+ default : throw new IllegalArgumentException("Unexpected types " + type1 + " " + type2);
+ }
+ case UINT :
+ switch (type2) {
+ case LONG : return LONG;
+ case FLOAT : return FLOAT;
+ case DOUBLE : return DOUBLE;
+ case NORMALIZED_BYTE : return FLOAT;
+ case NORMALIZED_UBYTE : return FLOAT;
+ case NORMALIZED_SHORT : return FLOAT;
+ case NORMALIZED_USHORT : return FLOAT;
+ default : throw new IllegalArgumentException("Unexpected types " + type1 + " " + type2);
+ }
+ case LONG :
+ switch (type2) {
+ case FLOAT : return DOUBLE;
+ case DOUBLE : return DOUBLE;
+ case NORMALIZED_BYTE : return DOUBLE;
+ case NORMALIZED_UBYTE : return DOUBLE;
+ case NORMALIZED_SHORT : return DOUBLE;
+ case NORMALIZED_USHORT : return DOUBLE;
+ default : throw new IllegalArgumentException("Unexpected types " + type1 + " " + type2);
+ }
+ case FLOAT :
+ switch (type2) {
+ case DOUBLE : return DOUBLE;
+ case NORMALIZED_BYTE : return FLOAT;
+ case NORMALIZED_UBYTE : return FLOAT;
+ case NORMALIZED_SHORT : return FLOAT;
+ case NORMALIZED_USHORT : return FLOAT;
+ default : throw new IllegalArgumentException("Unexpected types " + type1 + " " + type2);
+ }
+ case DOUBLE :
+ switch (type2) {
+ case NORMALIZED_BYTE : return DOUBLE;
+ case NORMALIZED_UBYTE : return DOUBLE;
+ case NORMALIZED_SHORT : return DOUBLE;
+ case NORMALIZED_USHORT : return DOUBLE;
+ default : throw new IllegalArgumentException("Unexpected types " + type1 + " " + type2);
+ }
+ case NORMALIZED_BYTE :
+ switch (type2) {
+ case NORMALIZED_UBYTE : return NORMALIZED_SHORT;
+ case NORMALIZED_SHORT : return NORMALIZED_SHORT;
+ case NORMALIZED_USHORT : return FLOAT;
+ default : throw new IllegalArgumentException("Unexpected types " + type1 + " " + type2);
+ }
+ case NORMALIZED_UBYTE :
+ switch (type2) {
+ case NORMALIZED_SHORT : return NORMALIZED_SHORT;
+ case NORMALIZED_USHORT : return NORMALIZED_USHORT;
+ default : throw new IllegalArgumentException("Unexpected types " + type1 + " " + type2);
+ }
+ case NORMALIZED_SHORT :
+ switch (type2) {
+ case NORMALIZED_USHORT : return FLOAT;
+ default : throw new IllegalArgumentException("Unexpected types " + type1 + " " + type2);
+ }
+ default : throw new IllegalArgumentException("Unexpected types " + type1 + " " + type2);
+ }
+ }
+}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/math/SampleSystem.java b/core/sis-feature/src/main/java/org/apache/sis/internal/math/SampleSystem.java
new file mode 100644
index 0000000000..13e45ae582
--- /dev/null
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/math/SampleSystem.java
@@ -0,0 +1,152 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.internal.math;
+
+import java.util.Arrays;
+import java.util.Objects;
+import org.apache.sis.util.ArgumentChecks;
+import org.apache.sis.util.collection.BackingStoreException;
+import org.apache.sis.util.collection.Cache;
+import org.apache.sis.util.iso.Names;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+import org.opengis.util.GenericName;
+
+/**
+ * Experimental class to store multisamples dimensions.
+ *
+ * This serves an identical purpose as SampleDimension but usable with geometry attributes.
+ *
+ * Waiting for a proper implementation in SIS when reviewing ISO 19123 / 2153.
+ *
+ * @author Johann Sorel (Geomatys)
+ */
+public final class SampleSystem {
+
+ private static final GenericName UNNAMED = Names.createLocalName(null, null, "unnamed");
+ private static final SampleSystem UNDEFINED_1S = new SampleSystem(1);
+ private static final SampleSystem UNDEFINED_2S = new SampleSystem(2);
+ private static final SampleSystem UNDEFINED_3S = new SampleSystem(3);
+ private static final SampleSystem UNDEFINED_4S = new SampleSystem(4);
+ private static SampleSystem[] UNDEFINED = new SampleSystem[0];
+ private static Cache<CoordinateReferenceSystem, SampleSystem> CACHE = new Cache<>();
+
+ private final GenericName name;
+ private final CoordinateReferenceSystem crs;
+ private final int size;
+
+ private SampleSystem(int size) {
+ ArgumentChecks.ensureStrictlyPositive("size", size);
+ this.name = UNNAMED;
+ this.crs = null;
+ this.size = size;
+ }
+
+ private SampleSystem(CoordinateReferenceSystem crs) {
+ this(UNNAMED, crs);
+ }
+
+ private SampleSystem(GenericName name, CoordinateReferenceSystem crs) {
+ ArgumentChecks.ensureNonNull("name", name);
+ ArgumentChecks.ensureNonNull("crs", crs);
+ this.name = name;
+ this.crs = crs;
+ this.size = crs.getCoordinateSystem().getDimension();
+ }
+
+ public static SampleSystem ofSize(int nbDim) {
+ ArgumentChecks.ensureStrictlyPositive("nbDim", nbDim);
+ switch (nbDim) {
+ case 1 : return UNDEFINED_1S;
+ case 2 : return UNDEFINED_2S;
+ case 3 : return UNDEFINED_3S;
+ case 4 : return UNDEFINED_4S;
+ default: {
+ final int idx = nbDim - 4;
+ synchronized (UNNAMED) {
+ if (idx >= UNDEFINED.length) {
+ UNDEFINED = Arrays.copyOf(UNDEFINED, idx+1);
+ }
+ if (UNDEFINED[idx] == null) {
+ UNDEFINED[idx] = new SampleSystem(nbDim);
+ }
+ return UNDEFINED[idx];
+ }
+ }
+ }
+ }
+
+ public static SampleSystem of(CoordinateReferenceSystem crs) {
+ ArgumentChecks.ensureNonNull("crs", crs);
+ try {
+ return CACHE.getOrCreate(crs, () -> new SampleSystem(crs));
+ } catch (Exception ex) {
+ throw new BackingStoreException(ex.getMessage(), ex);
+ }
+ }
+
+ /**
+ * Returns an identification for this dimension. This is typically used as a way to perform a band select
+ * by using human comprehensible descriptions instead of just numbers.
+ *
+ * @return an identification of this system, nerver null.
+ */
+ public GenericName getName() {
+ return name;
+ }
+
+ /**
+ * Returns the coordinate reference system for this record dimension if it is a coordinate system.
+ *
+ * @return can be null.
+ */
+ public CoordinateReferenceSystem getCoordinateReferenceSystem() {
+ return crs;
+ }
+
+ /**
+ * Returns the size in number of samples in this dimension.
+ *
+ * @return dimension size
+ */
+ public int getSize() {
+ return size;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(name, crs, size);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ final SampleSystem other = (SampleSystem) obj;
+ if (!Objects.equals(this.name, other.name)) {
+ return false;
+ }
+ return Objects.equals(this.crs, other.crs);
+ }
+
+}