james-willis commented on code in PR #3114:
URL: https://github.com/apache/sedona/pull/3114#discussion_r3668252706


##########
common/src/main/java/org/apache/sedona/common/utils/RasterUtils.java:
##########
@@ -647,6 +647,114 @@ public static boolean isDataTypeIntegral(int 
dataTypeCode) {
     }
   }
 
+  /**
+   * Verifies that {@code noDataValue} can be stored in the pixel type named 
by {@code pixelType}
+   * without silent coercion, returning it unchanged when it can. Delegates to 
{@link
+   * #assertRepresentable(double, String, String)} with the argument name 
{@code "noDataValue"} so
+   * the nodata path keeps its existing error messages.
+   *
+   * @param noDataValue the candidate nodata / background value
+   * @param pixelType a Sedona pixel type string accepted by {@link 
#getDataTypeCode(String)} (for
+   *     example {@code "B"}, {@code "I"}, {@code "D"})
+   * @return {@code noDataValue}, unchanged, when it is representable in the 
pixel type
+   * @throws IllegalArgumentException when {@code noDataValue} cannot be 
represented in the pixel
+   *     type
+   */
+  public static double assertNoDataValueRepresentable(double noDataValue, 
String pixelType) {
+    return assertRepresentable(noDataValue, pixelType, "noDataValue");
+  }
+
+  /**
+   * Verifies that {@code value} (a nodata / background value, or a burn 
value) can be stored in the
+   * pixel type named by {@code pixelType} without silent coercion, returning 
it unchanged when it
+   * can. Writing a value that is out of the pixel type's range, or fractional 
for an integer pixel
+   * type, coerces the stored sample to a different number than the value the 
caller asked for (for
+   * example -1.0 or 300.0 wraps to 255 or 44 in an unsigned 8-bit band), so 
the sample would read
+   * back as a different number than requested. For float / double pixel types 
a value that does not
+   * round-trip exactly through 32-bit float storage, and any non-finite value 
(NaN or +/-Infinity),
+   * are rejected for the same reason. Such a value is a correctness 
violation, not a per-row data
+   * condition, so this rejects it with an {@link IllegalArgumentException} 
instead of coercing.
+   *
+   * @param value the candidate value
+   * @param pixelType a Sedona pixel type string accepted by {@link 
#getDataTypeCode(String)} (for
+   *     example {@code "B"}, {@code "I"}, {@code "D"})
+   * @param argName the name of the argument being validated (for example 
{@code "noDataValue"} or
+   *     {@code "value"}), used in the exception message
+   * @return {@code value}, unchanged, when it is representable in the pixel 
type
+   * @throws IllegalArgumentException when {@code value} cannot be represented 
in the pixel type
+   */
+  public static double assertRepresentable(double value, String pixelType, 
String argName) {
+    int dataTypeCode = getDataTypeCode(pixelType);
+    long min;
+    long max;
+    String description;
+    switch (dataTypeCode) {
+      case DataBuffer.TYPE_BYTE:
+        min = 0;
+        max = 255;
+        description = "unsigned 8-bit";
+        break;
+      case DataBuffer.TYPE_USHORT:
+        min = 0;
+        max = 65535;
+        description = "unsigned 16-bit";
+        break;
+      case DataBuffer.TYPE_SHORT:
+        min = Short.MIN_VALUE;
+        max = Short.MAX_VALUE;
+        description = "signed 16-bit";
+        break;
+      case DataBuffer.TYPE_INT:
+        min = Integer.MIN_VALUE;
+        max = Integer.MAX_VALUE;
+        description = "signed 32-bit";
+        break;
+      case DataBuffer.TYPE_FLOAT:
+        // NaN and infinite values cannot be used on a float/double band: NaN 
is the codebase's
+        // internal "no nodata" sentinel, so it cannot be stored as a distinct 
nodata value and
+        // would be silently dropped; an infinite value is not a valid 
GeoTools category bound and
+        // crashes deep in GeoTools with "Range [Infinity .. Infinity] is not 
valid". Reject both
+        // up front with a clear error (fail loud) rather than dropping them 
or crashing later.
+        if (!Double.isFinite(value)) {
+          throw new IllegalArgumentException(
+              String.format(
+                  "%s %s is not supported for pixel type '%s' (NaN and 
infinite values are not supported); use a finite value",
+                  argName, value, pixelType));
+        }
+        // A nodata value is a sentinel, so it must round-trip exactly through 
the band's
+        // 32-bit float storage — otherwise the stored sentinel differs from 
the value the
+        // caller set (e.g. 0.1 would be stored as 0.10000000149...), and 
comparisons against
+        // the caller's value would miss it.
+        if ((double) (float) value != value) {
+          throw new IllegalArgumentException(
+              String.format(
+                  "%s %s is not exactly representable in pixel type '%s' 
(32-bit float)",
+                  argName, value, pixelType));
+        }
+        return value;
+      case DataBuffer.TYPE_DOUBLE:
+      default:
+        // 64-bit float represents every finite double value; only NaN and 
infinities are rejected
+        // (see the float arm for why).
+        if (!Double.isFinite(value)) {
+          throw new IllegalArgumentException(
+              String.format(
+                  "%s %s is not supported for pixel type '%s' (NaN and 
infinite values are not supported); use a finite value",
+                  argName, value, pixelType));
+        }
+        return value;
+    }
+
+    // Integer pixel types: reject out-of-range and fractional values 
(Math.rint also rejects NaN).
+    if (value < min || value > max || value != Math.rint(value)) {

Review Comment:
   I did this. not sure what it matters. Haven't bothered to think about what 
-0 means



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to