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

desruisseaux pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git

commit df7a5102a51c591f9ac7a85be55f1b1bd375760b
Author: Martin Desruisseaux <martin.desruisse...@geomatys.com>
AuthorDate: Mon Apr 25 16:05:34 2022 +0200

    Add partial support for color map file (*.clr).
    In current implementation it can apply only to data type byte and unsigned 
short.
    In particular, color map on floating point values is not yet supported.
---
 .../internal/coverage/j2d/ColorModelFactory.java   |   7 +-
 ide-project/NetBeans/build.xml                     |   2 +
 .../sis/internal/storage/esri/RasterStore.java     | 125 +++++++++++++++++++--
 .../sis/internal/storage/esri/package-info.java    |  13 +++
 .../org/apache/sis/internal/storage/esri/BIP.hdr   |   2 +
 .../org/apache/sis/internal/storage/esri/BIP.stx   |   7 ++
 .../org/apache/sis/internal/storage/esri/grid.clr  |  14 +++
 7 files changed, 156 insertions(+), 14 deletions(-)

diff --git 
a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/ColorModelFactory.java
 
b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/ColorModelFactory.java
index 99192128ee..f3a12df41c 100644
--- 
a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/ColorModelFactory.java
+++ 
b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/ColorModelFactory.java
@@ -18,7 +18,6 @@ package org.apache.sis.internal.coverage.j2d;
 
 import java.util.Map;
 import java.util.Arrays;
-import java.util.Collection;
 import java.util.Comparator;
 import java.util.Optional;
 import java.awt.Transparency;
@@ -317,9 +316,9 @@ public final class ColorModelFactory {
      * @see Colorizer
      */
     public static ColorModel createPiecewise(final int dataType, final int 
numBands, final int visibleBand,
-                                             final 
Collection<Map.Entry<NumberRange<?>,Color[]>> colors)
+                                             final Map<NumberRange<?>, 
Color[]> colors)
     {
-        return createPiecewise(dataType, numBands, visibleBand, 
ColorsForRange.list(colors));
+        return createPiecewise(dataType, numBands, visibleBand, 
ColorsForRange.list(colors.entrySet()));
     }
 
     /**
@@ -386,7 +385,7 @@ public final class ColorModelFactory {
 
     /**
      * Returns a color model interpolated for the given range of values. This 
is a convenience method for
-     * {@link #createPiecewise(int, int, int, Collection)} when the collection 
contains only one element.
+     * {@link #createPiecewise(int, int, int, Map)} when the map contains only 
one element.
      *
      * @param  dataType     the color model type.
      * @param  numBands     the number of bands for the color model (usually 
1).
diff --git a/ide-project/NetBeans/build.xml b/ide-project/NetBeans/build.xml
index 557ef9f261..bbb1786f11 100644
--- a/ide-project/NetBeans/build.xml
+++ b/ide-project/NetBeans/build.xml
@@ -304,6 +304,8 @@
         <include name="**/*.txt"/>
         <include name="**/*.asc"/>
         <include name="**/*.hdr"/>
+        <include name="**/*.stx"/>
+        <include name="**/*.clr"/>
         <include name="**/*.raw"/>
         <include name="**/*.png"/>
         <include name="**/*.pgw"/>
diff --git 
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/esri/RasterStore.java
 
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/esri/RasterStore.java
index 6955c061b0..0e588da647 100644
--- 
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/esri/RasterStore.java
+++ 
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/esri/RasterStore.java
@@ -26,6 +26,7 @@ import java.io.FileNotFoundException;
 import java.nio.file.NoSuchFileException;
 import java.nio.file.Path;
 import java.awt.image.ColorModel;
+import java.awt.image.DataBuffer;
 import java.awt.image.SampleModel;
 import java.awt.image.BufferedImage;
 import java.awt.image.WritableRaster;
@@ -53,6 +54,7 @@ import org.apache.sis.internal.util.UnmodifiableArrayList;
 import org.apache.sis.internal.util.Numerics;
 import org.apache.sis.util.resources.Vocabulary;
 import org.apache.sis.util.CharSequences;
+import org.apache.sis.util.ArraysExt;
 import org.apache.sis.math.Statistics;
 
 
@@ -197,6 +199,89 @@ abstract class RasterStore extends PRJDataStore implements 
GridCoverageResource
         metadata = builder.buildAndFreeze();
     }
 
+    /**
+     * Reads the {@code "*.clr"} auxiliary file. Syntax is as below, with one 
line per color:
+     *
+     * <pre>value red green blue</pre>
+     *
+     * The specification said that lines that do not start with a number shall 
be ignored as comment.
+     * Any characters after the fourth number shall also be ignored and can be 
used as comment.
+     *
+     * <h4>Limitations</h4>
+     * Current implementation requires the data type to be {@link 
DataBuffer#TYPE_BYTE} or
+     * {@link DataBuffer#TYPE_SHORT}. A future version could create scaled 
color model for
+     * floating point values as well.
+     *
+     * @param  mapSize   minimal size of index color model map. The actual 
size may be larger.
+     * @param  numBands  number of bands in the sample model. Only one of them 
will be visible.
+     * @return the color model, or {@code null} if the file does not contain 
enough entries.
+     * @throws NoSuchFileException if the auxiliary file has not been found 
(when opened from path).
+     * @throws FileNotFoundException if the auxiliary file has not been found 
(when opened from URL).
+     * @throws IOException if another error occurred while opening the stream.
+     * @throws NumberFormatException if a number can not be parsed.
+     */
+    private ColorModel readColorMap(final int dataType, final int mapSize, 
final int numBands)
+            throws DataStoreException, IOException
+    {
+        final int maxSize;
+        switch (dataType) {
+            case DataBuffer.TYPE_BYTE:   maxSize = 0xFF;   break;
+            case DataBuffer.TYPE_USHORT: maxSize = 0xFFFF; break;
+            default: return null;                           // Limitation 
documented in above javadoc.
+        }
+        int count = 0;
+        long[] indexAndColors = ArraysExt.EMPTY_LONG;       // Index in 
highest 32 bits, ARGB in lowest 32 bits.
+        for (final CharSequence line : 
CharSequences.splitOnEOL(readAuxiliaryFile(CLR))) {
+            final int end   = CharSequences.skipTrailingWhitespaces(line, 0, 
line.length());
+            final int start = CharSequences.skipLeadingWhitespaces(line, 0, 
end);
+            if (start < end && Character.isDigit(Character.codePointAt(line, 
start))) {
+                int column = 0;
+                long code = 0;
+                for (final CharSequence item : 
CharSequences.split(line.subSequence(start, end), ' ')) {
+                    if (item.length() != 0) {
+                        int value = Integer.parseInt(item.toString());
+                        if (column == 0) {
+                            code = ((long) value) << Integer.SIZE;
+                        } else {
+                            value = Math.max(0, Math.min(255, value));
+                            code |= value << ((3 - column) * Byte.SIZE);
+                        }
+                        if (++column >= 4) break;
+                    }
+                }
+                if (count >= indexAndColors.length) {
+                    indexAndColors = Arrays.copyOf(indexAndColors, 
Math.max(count*2, 64));
+                }
+                indexAndColors[count++] = code | 0xFF000000L;
+            }
+        }
+        if (count <= 1) {
+            return null;
+        }
+        /*
+         * Sort the color entries in increasing index order. Because we put 
the value in the highest bits,
+         * we can sort the `long` entries directly. If the file contains more 
entries than what the color
+         * map can contains, the last entries are discarded.
+         */
+        Arrays.sort(indexAndColors, 0, count);
+        int[] ARGB = new int[Math.max(mapSize, 
Math.toIntExact((indexAndColors[count-1] >>> Integer.SIZE) + 1))];
+        final int[] colors = new int[2];
+        for (int i=1; i<count; i++) {
+            final int lower = (int) (indexAndColors[i-1] >>> Integer.SIZE);
+            final int upper = (int) (indexAndColors[i  ] >>> Integer.SIZE);
+            if (upper >= lower) {
+                colors[0] = (int) indexAndColors[i-1];
+                colors[1] = (int) indexAndColors[i  ];
+                ColorModelFactory.expand(colors, ARGB, lower, upper + 1);
+            }
+            if (upper > maxSize) {
+                ARGB = Arrays.copyOf(ARGB, maxSize + 1);
+                break;
+            }
+        }
+        return ColorModelFactory.createIndexColorModel(numBands, VISIBLE_BAND, 
ARGB, true, -1);
+    }
+
     /**
      * Reads the {@code "*.stx"} auxiliary file. Syntax is as below, with one 
line per band.
      * Value between {…} are optional and can be skipped with a # sign in 
place of the number.
@@ -207,17 +292,16 @@ abstract class RasterStore extends PRJDataStore 
implements GridCoverageResource
      *
      * @todo Stretch values are not yet stored.
      *
-     * @param  numBands  length of the array to return.
      * @return statistics for each band. Some elements may be null if not 
specified in the file.
      * @throws NoSuchFileException if the auxiliary file has not been found 
(when opened from path).
      * @throws FileNotFoundException if the auxiliary file has not been found 
(when opened from URL).
      * @throws IOException if another error occurred while opening the stream.
      * @throws NumberFormatException if a number can not be parsed.
      */
-    private Statistics[] readStatistics(final String name, final SampleModel 
sm, final int numBands)
+    private Statistics[] readStatistics(final String name, final SampleModel 
sm)
             throws DataStoreException, IOException
     {
-        final Statistics[] stats = new Statistics[numBands];
+        final Statistics[] stats = new Statistics[sm.getNumBands()];
         for (final CharSequence line : 
CharSequences.splitOnEOL(readAuxiliaryFile(STX))) {
             final int end   = CharSequences.skipTrailingWhitespaces(line, 0, 
line.length());
             final int start = CharSequences.skipLeadingWhitespaces(line, 0, 
end);
@@ -246,7 +330,7 @@ abstract class RasterStore extends PRJDataStore implements 
GridCoverageResource
                 }
                 if (band >= 1 && band <= stats.length) {
                     final int count = Math.multiplyExact(sm.getWidth(), 
sm.getHeight());
-                    stats[band - 1] = new Statistics(name, 0, count, minimum, 
maximum, mean, stdev, true);
+                    stats[band - 1] = new Statistics(name, 0, count, minimum, 
maximum, mean, stdev, false);
                 }
             }
         }
@@ -270,11 +354,9 @@ abstract class RasterStore extends PRJDataStore implements 
GridCoverageResource
          * overwrite them because we need the minimum/maximum values for 
building the sample dimensions.
          */
         try {
-            stats = readStatistics(name, sm, bands.length);
-        } catch (NoSuchFileException | FileNotFoundException e) {
-            listeners.warning(Level.FINE, 
Resources.format(Resources.Keys.CanNotReadAuxiliaryFile_1, STX), e);
+            stats = readStatistics(name, sm);
         } catch (IOException | NumberFormatException e) {
-            throw new 
DataStoreReferencingException(Resources.format(Resources.Keys.CanNotReadAuxiliaryFile_1,
 STX), e);
+            canNotReadAuxiliaryFile(STX, e);
         }
         /*
          * Build the sample dimensions and the color model.
@@ -334,18 +416,41 @@ abstract class RasterStore extends PRJDataStore 
implements GridCoverageResource
             /*
              * Create the color model using the statistics of the band that we 
choose to make visible,
              * or using a RGB color model if the number of bands or the data 
type is compatible.
+             * The color file is optional and will be used if present.
              */
             if (band == VISIBLE_BAND) {
                 if (isRGB) {
                     colorModel = ColorModelFactory.createRGB(sm);
                 } else {
-                    colorModel = ColorModelFactory.createGrayScale(dataType, 
bands.length, band, minimum, maximum);
+                    try {
+                        colorModel = readColorMap(dataType, (int) (maximum + 
1), bands.length);
+                    } catch (IOException | NumberFormatException e) {
+                        canNotReadAuxiliaryFile(CLR, e);
+                    }
+                    if (colorModel == null) {
+                        colorModel = 
ColorModelFactory.createGrayScale(dataType, bands.length, band, minimum, 
maximum);
+                    }
                 }
             }
         }
         sampleDimensions = UnmodifiableArrayList.wrap(bands);
     }
 
+    /**
+     * Sends a warning about a failure to read an optional auxiliary file.
+     * This is used for errors that affect only the rendering, not the 
georeferencing.
+     *
+     * @param  suffix     suffix of the auxiliary file.
+     * @param  exception  error that occurred while reading the auxiliary file.
+     */
+    private void canNotReadAuxiliaryFile(final String suffix, final Exception 
exception) {
+        Level level = Level.WARNING;
+        if (exception instanceof NoSuchFileException || exception instanceof 
FileNotFoundException) {
+            level = Level.FINE;
+        }
+        listeners.warning(level, 
Resources.format(Resources.Keys.CanNotReadAuxiliaryFile_1, suffix), exception);
+    }
+
     /**
      * Default names of bands when the color model is RGB or RGBA.
      */
@@ -380,7 +485,7 @@ abstract class RasterStore extends PRJDataStore implements 
GridCoverageResource
         ColorModel cm = colorModel;
         if (!range.isIdentity()) {
             bands = Arrays.asList(range.select(sampleDimensions));
-            cm = range.select(colorModel).orElse(null);
+            cm = range.select(cm).orElse(null);
             if (cm == null) {
                 final SampleDimension band = bands.get(VISIBLE_BAND);
                 cm = ColorModelFactory.createGrayScale(data.getSampleModel(), 
VISIBLE_BAND, band.getSampleRange().orElse(null));
diff --git 
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/esri/package-info.java
 
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/esri/package-info.java
index e03933fae7..1746e7c191 100644
--- 
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/esri/package-info.java
+++ 
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/esri/package-info.java
@@ -32,6 +32,19 @@
  *       ({@code XDIM}, {@code YDIM}, color file, statistics file, 
<i>etc.</i>).</li>
  * </ul>
  *
+ * <h2>Limitations</h2>
+ * Statistics file ({@code *.stx}) contains {@code band}, {@code minimum}, 
{@code maximum}, {@code mean},
+ * {@code std_deviation}, {@code linear_stretch_min} and {@code 
linear_stretch_max} values.
+ * But in current Apache SIS implementation, the last two values ({@code 
linear_stretch_*}) are ignored.
+ *
+ * <p>Color map file ({@code *.clr}) is read only when the raster does not 
have 3 or 4 bands
+ * (in which case the raster is considered RGB) and when the data type is byte 
or unsigned short.
+ * In all other cases, notably in the case of floating point values, the color 
map is ignored.</p>
+ *
+ * <p>Current implementation of ASCII Grid store loads, caches and returns the 
full image
+ * no matter the subregion or subsampling specified to the {@code read(…)} 
method.
+ * Sub-setting parameters are ignored.</p>
+ *
  * @author  Martin Desruisseaux (Geomatys)
  * @version 1.2
  *
diff --git 
a/storage/sis-storage/src/test/resources/org/apache/sis/internal/storage/esri/BIP.hdr
 
b/storage/sis-storage/src/test/resources/org/apache/sis/internal/storage/esri/BIP.hdr
index 197bf27453..550b24582e 100644
--- 
a/storage/sis-storage/src/test/resources/org/apache/sis/internal/storage/esri/BIP.hdr
+++ 
b/storage/sis-storage/src/test/resources/org/apache/sis/internal/storage/esri/BIP.hdr
@@ -1,4 +1,6 @@
 Header file for the `BIP.raw` test raster.
+The BIP test also contains a `BIP.stx` file
+for testing the loading of statistics data.
 
 NROWS  9
 NCOLS  9
diff --git 
a/storage/sis-storage/src/test/resources/org/apache/sis/internal/storage/esri/BIP.stx
 
b/storage/sis-storage/src/test/resources/org/apache/sis/internal/storage/esri/BIP.stx
new file mode 100644
index 0000000000..8585d48ce5
--- /dev/null
+++ 
b/storage/sis-storage/src/test/resources/org/apache/sis/internal/storage/esri/BIP.stx
@@ -0,0 +1,7 @@
+Pseudo-statistics about the `BIP.raw` file content.
+Format is (where <…> are mandatory and {…} optional):
+
+  <band> <minimum> <maximum> {mean} {std_deviation} {linear_stretch_min} 
{linear_stretch_max}
+
+1 2 250 100 10
+Above values are not real statistics.
diff --git 
a/storage/sis-storage/src/test/resources/org/apache/sis/internal/storage/esri/grid.clr
 
b/storage/sis-storage/src/test/resources/org/apache/sis/internal/storage/esri/grid.clr
new file mode 100644
index 0000000000..8626f5ff73
--- /dev/null
+++ 
b/storage/sis-storage/src/test/resources/org/apache/sis/internal/storage/esri/grid.clr
@@ -0,0 +1,14 @@
+Color map for `grid.asc` file. This auxiliary file is
+actually is actually for BIP/BIL/BSQ raster formats,
+but Apache SIS applies it to ASCII Grid file as well.
+
+-10   0   0 255   (blue)
+  0   0 255 255   (cyan)
+ 10   0 255   0   (green)
+ 20 255 255   0   (yellow)
+ 30 255 165   0   (orange)
+ 40 160  32 240   (purple)
+
+Note: in current Apache SIS implementation this file is ignored
+because ASCII grid data are floating points and color maps are
+restricted to integer types.

Reply via email to