You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by de...@apache.org on 2022/04/04 15:26:32 UTC
[sis] branch geoapi-4.0 updated (1b0c4e2c6e -> c3ca073d5e)
This is an automated email from the ASF dual-hosted git repository.
desruisseaux pushed a change to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git
from 1b0c4e2c6e Make a better effort for saying in which object a property was missing. Minor formatting in comments.
new 796ddbbf04 Search for auxiliary files using Path in addition or URI. Those two kinds of object does not always open the same streams.
new c3ca073d5e Bug fix: colors applied to ASCII Grid images were wrongly applied to other kind of images if they shared the same `ImageProcessor`.
The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
Summary of changes:
.../org/apache/sis/coverage/grid/GridCoverage.java | 4 +-
.../sis/coverage/grid/GridCoverageBuilder.java | 2 +-
.../apache/sis/coverage/grid/ImageRenderer.java | 3 +-
.../java/org/apache/sis/image/Visualization.java | 4 +-
.../internal/coverage/j2d/ColorModelFactory.java | 2 +-
.../sis/internal/coverage/j2d/Colorizer.java | 74 ++++++++++++++------
.../sis/internal/coverage/j2d/ColorsForRange.java | 61 +++++++++--------
.../sis/internal/coverage/j2d/ColorizerTest.java | 4 +-
.../apache/sis/internal/storage/PRJDataStore.java | 79 ++++++++++++++--------
.../apache/sis/internal/storage/URIDataStore.java | 59 +++++++++++++---
10 files changed, 194 insertions(+), 98 deletions(-)
[sis] 01/02: Search for auxiliary files using Path in addition or URI. Those two kinds of object does not always open the same streams.
Posted by de...@apache.org.
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 796ddbbf0407444efed6d9f32b2882ac218e047a
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Mon Apr 4 11:22:58 2022 +0200
Search for auxiliary files using Path in addition or URI.
Those two kinds of object does not always open the same streams.
---
.../apache/sis/internal/storage/PRJDataStore.java | 79 ++++++++++++++--------
.../apache/sis/internal/storage/URIDataStore.java | 59 +++++++++++++---
2 files changed, 97 insertions(+), 41 deletions(-)
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/PRJDataStore.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/PRJDataStore.java
index 0a5a54846a..4c04ec072a 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/PRJDataStore.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/PRJDataStore.java
@@ -18,13 +18,13 @@ package org.apache.sis.internal.storage;
import java.net.URL;
import java.io.IOException;
+import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.FileNotFoundException;
import java.nio.charset.Charset;
-import java.nio.file.FileSystemNotFoundException;
import java.nio.file.Files;
import java.nio.file.Path;
-import java.nio.file.Paths;
+import java.nio.file.NoSuchFileException;
import java.text.ParseException;
import java.util.Arrays;
import java.util.Locale;
@@ -137,7 +137,7 @@ public abstract class PRJDataStore extends URIDataStore {
crs = (CoordinateReferenceSystem) format.parseObject(wkt);
format.validate(crs);
}
- } catch (FileNotFoundException e) {
+ } catch (NoSuchFileException | FileNotFoundException e) {
listeners.warning(Resources.format(Resources.Keys.CanNotReadAuxiliaryFile_1, PRJ), e);
} catch (IOException | ParseException | ClassCastException e) {
throw new DataStoreException(Resources.format(Resources.Keys.CanNotReadAuxiliaryFile_1, PRJ), e);
@@ -153,18 +153,37 @@ public abstract class PRJDataStore extends URIDataStore {
* @param extension the filename extension of the auxiliary file to open.
* @param encoding the encoding to use for reading the file content, or {@code null} for default.
* @return a stream opened on the specified file, or {@code null} if the file is not found.
- * @throws FileNotFoundException if the auxiliary file has not been found.
+ * @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.
*/
protected final String readAuxiliaryFile(final String extension, Charset encoding) throws IOException {
- final URL url = IOUtilities.toAuxiliaryURL(location, extension);
- if (url == null) {
- return null;
- }
if (encoding == null) {
encoding = Charset.defaultCharset();
}
- try (InputStreamReader reader = new InputStreamReader(url.openStream(), encoding)) {
+ /*
+ * Try to open the stream using the storage type (Path or URL) closest to the type
+ * given at construction time. We do that because those two types can not open the
+ * same streams. For example Path does not open HTTP or FTP connections by default,
+ * and URL does not open S3 files in current implementation.
+ */
+ final InputStream stream;
+ Path path = getSpecifiedPath();
+ if (path != null) {
+ final String base = getBaseFilename(path);
+ path = path.resolveSibling(base.concat(PRJ));
+ stream = Files.newInputStream(path);
+ } else {
+ final URL url = IOUtilities.toAuxiliaryURL(location, extension);
+ if (url == null) {
+ return null;
+ }
+ stream = url.openStream();
+ }
+ /*
+ * Reads the auxiliary file fully.
+ */
+ try (InputStreamReader reader = new InputStreamReader(stream, encoding)) {
char[] buffer = new char[1024];
int offset = 0, count;
while ((count = reader.read(buffer, offset, buffer.length - offset)) >= 0) {
@@ -202,29 +221,29 @@ public abstract class PRJDataStore extends URIDataStore {
* @throws DataStoreException if the URI can not be converted to a {@link Path}.
*/
protected final Path[] listComponentFiles(final String... auxiliaries) throws DataStoreException {
- final Path path;
- if (location == null) {
- return new Path[0];
- } else try {
- path = Paths.get(location);
- } catch (IllegalArgumentException | FileSystemNotFoundException e) {
- throw new DataStoreException(e);
- }
- String base = path.getFileName().toString();
- final int s = base.lastIndexOf('.');
- if (s >= 0) {
- base = base.substring(0, s+1);
- }
- final Path[] paths = new Path[auxiliaries.length + 1];
- paths[0] = path;
- int count = 1;
- for (final String extension : auxiliaries) {
- final Path p = path.resolveSibling(base.concat(extension));
- if (Files.isRegularFile(p)) {
- paths[count++] = p;
+ Path[] paths = super.getComponentFiles();
+ int count = paths.length;
+ if (count != 0) {
+ final Path path = paths[0];
+ final String base = getBaseFilename(path);
+ for (final String extension : auxiliaries) {
+ final Path p = path.resolveSibling(base.concat(extension));
+ if (Files.isRegularFile(p)) {
+ if (count >= paths.length) {
+ paths = Arrays.copyOf(paths, count + auxiliaries.length);
+ }
+ paths[count++] = p;
+ }
}
+ paths = ArraysExt.resize(paths, count);
}
- return ArraysExt.resize(paths, count);
+ return paths;
+ }
+
+ private static String getBaseFilename(final Path path) {
+ final String base = path.getFileName().toString();
+ final int s = base.lastIndexOf('.');
+ return (s >= 0) ? base.substring(0, s+1) : base + '.';
}
/**
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/URIDataStore.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/URIDataStore.java
index 181f44b4ee..ce4faf81d0 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/URIDataStore.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/URIDataStore.java
@@ -16,6 +16,7 @@
*/
package org.apache.sis.internal.storage;
+import java.io.File;
import java.net.URI;
import java.util.Optional;
import java.nio.file.Path;
@@ -54,6 +55,25 @@ public abstract class URIDataStore extends DataStore implements StoreResource, R
*/
protected final URI location;
+ /**
+ * The {@link #location} as a path, computed when first needed.
+ * If the storage given at construction time was a {@link Path} or a {@link File} instance,
+ * then this field is initialized in the constructor in order to avoid a "path → URI → path" roundtrip
+ * (such roundtrip transforms relative paths into {@linkplain Path#toAbsolutePath() absolute paths}).
+ *
+ * @see #getSpecifiedPath()
+ * @see #getComponentFiles()
+ */
+ private volatile Path locationAsPath;
+
+ /**
+ * Whether {@link #locationAsPath} was initialized at construction time ({@code true})
+ * of inferred from the {@link #location} URI at a later time ({@code false}).
+ *
+ * @see #getSpecifiedPath()
+ */
+ private final boolean locationIsPath;
+
/**
* Creates a new data store.
*
@@ -64,6 +84,22 @@ public abstract class URIDataStore extends DataStore implements StoreResource, R
protected URIDataStore(final DataStoreProvider provider, final StorageConnector connector) throws DataStoreException {
super(provider, connector);
location = connector.getStorageAs(URI.class);
+ final Object storage = connector.getStorage();
+ if (storage instanceof Path) {
+ locationAsPath = (Path) storage;
+ } else if (storage instanceof File) {
+ locationAsPath = ((File) storage).toPath();
+ }
+ locationIsPath = (locationAsPath != null);
+ }
+
+ /**
+ * If the location was specified as a {@link Path} or {@link File} instance, returns that path.
+ * Otherwise returns {@code null}. This method does not try to convert URI to {@link Path}
+ * because this conversion may fail for HTTP and FTP connections.
+ */
+ final Path getSpecifiedPath() {
+ return locationIsPath ? locationAsPath : null;
}
/**
@@ -78,23 +114,24 @@ public abstract class URIDataStore extends DataStore implements StoreResource, R
/**
* Returns the {@linkplain #location} as a {@code Path} component or an empty array if none.
- * The default implementation converts the URI to a {@link Path}. Note that if the original
- * {@link StorageConnector} argument given by user at construction time already contained a
- * {@link Path}, then the {@code Path} → {@code URI} → {@code Path} roundtrip is equivalent
- * to returning the {@link Path#toAbsolutePath()} value of user argument.
+ * The default implementation returns the storage specified at construction time if it was
+ * a {@link Path} or {@link File}, or converts the URI to a {@link Path} otherwise.
*
* @return the URI as a path, or an empty array if the URI is null.
* @throws DataStoreException if the URI can not be converted to a {@link Path}.
*/
@Override
public Path[] getComponentFiles() throws DataStoreException {
- final Path path;
- if (location == null) {
- return new Path[0];
- } else try {
- path = Paths.get(location);
- } catch (IllegalArgumentException | FileSystemNotFoundException e) {
- throw new DataStoreException(e);
+ Path path = locationAsPath;
+ if (path == null) {
+ if (location == null) {
+ return new Path[0];
+ } else try {
+ path = Paths.get(location);
+ } catch (IllegalArgumentException | FileSystemNotFoundException e) {
+ throw new DataStoreException(e);
+ }
+ locationAsPath = path;
}
return new Path[] {path};
}
[sis] 02/02: Bug fix: colors applied to ASCII Grid images were wrongly applied to other kind of images if they shared the same `ImageProcessor`.
Posted by de...@apache.org.
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 c3ca073d5e90a798af9cad8b62352e9cbf41e23f
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Mon Apr 4 17:24:20 2022 +0200
Bug fix: colors applied to ASCII Grid images were wrongly applied
to other kind of images if they shared the same `ImageProcessor`.
---
.../org/apache/sis/coverage/grid/GridCoverage.java | 4 +-
.../sis/coverage/grid/GridCoverageBuilder.java | 2 +-
.../apache/sis/coverage/grid/ImageRenderer.java | 3 +-
.../java/org/apache/sis/image/Visualization.java | 4 +-
.../internal/coverage/j2d/ColorModelFactory.java | 2 +-
.../sis/internal/coverage/j2d/Colorizer.java | 74 ++++++++++++++++------
.../sis/internal/coverage/j2d/ColorsForRange.java | 61 +++++++++---------
.../sis/internal/coverage/j2d/ColorizerTest.java | 4 +-
8 files changed, 97 insertions(+), 57 deletions(-)
diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverage.java b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverage.java
index 8232a77d9f..2d1ade3610 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverage.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverage.java
@@ -282,7 +282,9 @@ public abstract class GridCoverage extends BandedCoverage {
final int visibleBand = Math.max(0, ImageUtilities.getVisibleBand(source));
final Colorizer colorizer = new Colorizer(Colorizer.GRAYSCALE);
final ColorModel colors;
- if (colorizer.initialize(sampleDimensions[visibleBand]) || colorizer.initialize(source.getColorModel())) {
+ if (colorizer.initialize(source.getSampleModel(), sampleDimensions[visibleBand]) ||
+ colorizer.initialize(source.getColorModel()))
+ {
colors = colorizer.createColorModel(bandType.toDataBufferType(), sampleDimensions.length, visibleBand);
} else {
colors = Colorizer.NULL_COLOR_MODEL;
diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverageBuilder.java b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverageBuilder.java
index 5a83745739..1e437360e5 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverageBuilder.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverageBuilder.java
@@ -473,7 +473,7 @@ public class GridCoverageBuilder {
final SampleModel sm = raster.getSampleModel();
final Colorizer colorizer = new Colorizer(Colorizer.GRAYSCALE);
final ColorModel colors;
- if (colorizer.initialize(bands.get(visibleBand)) || colorizer.initialize(sm, visibleBand)) {
+ if (colorizer.initialize(sm, bands.get(visibleBand)) || colorizer.initialize(sm, visibleBand)) {
colors = colorizer.createColorModel(ImageUtilities.getBandType(sm), bands.size(), visibleBand);
} else {
colors = Colorizer.NULL_COLOR_MODEL;
diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ImageRenderer.java b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ImageRenderer.java
index bf2f424b91..9bb02bc94a 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ImageRenderer.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ImageRenderer.java
@@ -731,7 +731,8 @@ public class ImageRenderer {
final Raster raster = createRaster();
final Colorizer colorizer = new Colorizer(colors);
final ColorModel colors;
- if (colorizer.initialize(bands[visibleBand]) || colorizer.initialize(raster.getSampleModel(), visibleBand)) {
+ final SampleModel sm = raster.getSampleModel();
+ if (colorizer.initialize(sm, bands[visibleBand]) || colorizer.initialize(sm, visibleBand)) {
colors = colorizer.createColorModel(buffer.getDataType(), bands.length, visibleBand);
} else {
colors = Colorizer.NULL_COLOR_MODEL;
diff --git a/core/sis-feature/src/main/java/org/apache/sis/image/Visualization.java b/core/sis-feature/src/main/java/org/apache/sis/image/Visualization.java
index 393f65fb1f..f749ebf56d 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/image/Visualization.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/image/Visualization.java
@@ -219,10 +219,10 @@ final class Visualization extends ResampledImage {
* in various ways: sample dimensions, scaled color model, statistics in last resort.
*/
colorizer = new Colorizer(categoryColors);
- initialized = (sourceBands != null) && colorizer.initialize(sourceBands.get(visibleBand));
+ initialized = (sourceBands != null) && colorizer.initialize(source.getSampleModel(), sourceBands.get(visibleBand));
if (initialized) {
/*
- * If we have been able to configure Colorizer using the SampleModel, apply an adjustment based
+ * If we have been able to configure Colorizer using SampleDimension, apply an adjustment based
* on the ScaledColorModel if it exists. Use case: an image is created with an IndexColorModel
* determined by the SampleModel, then user enhanced contrast by a call to `stretchColorRamp(…)`
* above. We want to preserve that contrast enhancement.
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 bf0835fafb..c502db5fd5 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
@@ -398,7 +398,7 @@ public final class ColorModelFactory {
final double lower, final double upper, final Color... colors)
{
return createPiecewise(dataType, numBands, visibleBand, new ColorsForRange[] {
- new ColorsForRange(null, new NumberRange<>(Double.class, lower, true, upper, false), colors)
+ new ColorsForRange(null, new NumberRange<>(Double.class, lower, true, upper, false), colors, true)
});
}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/Colorizer.java b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/Colorizer.java
index c39cadb2fd..715c521c2a 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/Colorizer.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/Colorizer.java
@@ -28,6 +28,7 @@ import java.awt.image.ColorModel;
import java.awt.image.DataBuffer;
import java.awt.image.IndexColorModel;
import java.awt.image.SampleModel;
+import org.opengis.util.InternationalString;
import org.opengis.referencing.operation.MathTransform1D;
import org.opengis.referencing.operation.NoninvertibleTransformException;
import org.apache.sis.referencing.operation.transform.MathTransforms;
@@ -58,8 +59,8 @@ import org.apache.sis.util.Debug;
* There is no {@code initialize(Raster)} or {@code initialize(RenderedImage)} method because if those methods
* were present, users may expect them to iterate over sample values for finding minimum and maximum values.
* We do not perform such iteration because they are potentially costly and give unstable results:
- * the resulting color model varies from image to image, which is confusing when they are images of the same
- * product as different depth or different time.
+ * the resulting color model varies from image to image, which is confusing when many images exist
+ * for the same product at different times or at different depths.
*
* @author Martin Desruisseaux (Geomatys)
* @version 1.2
@@ -77,6 +78,21 @@ public final class Colorizer {
*/
public static final ColorModel NULL_COLOR_MODEL = null;
+ /**
+ * Names to use for the synthetic categories and sample dimensions created for visualization purposes.
+ * Transparent pixel is usually 0 and opaque pixels are in the range 1 to {@value #MAX_VALUE} inclusive.
+ *
+ * <p>For safety, we use names that are different than the default "No data" and "Data" names assigned by
+ * {@link SampleDimension.Builder}. The "[No] data" default names are often used by formats that are poor
+ * in metadata, for example ASCII Grid. If we were using the same names, a {@link #colors} function could
+ * confuse synthetic categories with "real" categories with uninformative name, and consequently apply
+ * wrong colors.</p>
+ */
+ private static final InternationalString
+ TRANSPARENT = Vocabulary.formatInternational(Vocabulary.Keys.Transparent),
+ COLOR_INDEX = Vocabulary.formatInternational(Vocabulary.Keys.ColorIndex),
+ VISUAL = Vocabulary.formatInternational(Vocabulary.Keys.Visual);
+
/**
* Maximal index value which can be used with a 8 bits {@link IndexColorModel}, inclusive.
* Sample values must be in that range for enabling the use of {@link #TYPE_COMPACT}.
@@ -192,20 +208,33 @@ public final class Colorizer {
* the sample dimension, colors will be determined by a call to {@code colors.apply(category)}
* where {@code colors} is the function specified at construction time.
*
+ * @param model the sample model used with the data, or {@code null} if unknown.
* @param source description of range of values in the source image, or {@code null}.
* @return {@code true} on success, or {@code false} if no range of values has been found.
* @throws IllegalStateException if a sample dimension is already defined on this colorizer.
*/
- public boolean initialize(final SampleDimension source) {
+ public boolean initialize(final SampleModel model, final SampleDimension source) {
checkInitializationStatus(false);
if (source != null) {
this.source = source;
final List<Category> categories = source.getCategories();
if (!categories.isEmpty()) {
- final ColorsForRange[] entries = new ColorsForRange[categories.size()];
+ boolean missingNodata = true;
+ ColorsForRange[] entries = new ColorsForRange[categories.size()];
for (int i=0; i<entries.length; i++) {
final Category category = categories.get(i);
- entries[i] = new ColorsForRange(category, category.getSampleRange(), colors.apply(category));
+ entries[i] = new ColorsForRange(category, colors);
+ missingNodata &= category.isQuantitative();
+ }
+ /*
+ * If the model uses floating point values and there is no "no data" category, add one.
+ * We force a "no data" category because floating point values may be NaN.
+ */
+ if (missingNodata && (model == null || !ImageUtilities.isIntegerType(model))) {
+ final int count = entries.length;
+ entries = Arrays.copyOf(entries, count + 1);
+ entries[count] = new ColorsForRange(Vocabulary.formatInternational(Vocabulary.Keys.Nodata),
+ NumberRange.create(Float.class, Float.NaN), null, false);
}
// Leave `target` to null. It will be computed by `compact()` if needed.
this.entries = entries;
@@ -271,8 +300,9 @@ public final class Colorizer {
* Applies colors on the given range of values. The 0 index will be reserved for NaN value,
* and indices in the [1 … 255] will be mapped to the given range.
*
- * <p>This method is typically used as a last resort fallback when other {@code initialize(…)}
- * methods failed or can not be applied.</p>
+ * <p>This method is typically used as a last resort fallback when all other {@code initialize(…)}
+ * methods failed or can not be applied. This method assumes that no {@link Category} information
+ * is available.</p>
*
* @param minimum minimum value, inclusive.
* @param maximum maximum value, inclusive.
@@ -284,15 +314,20 @@ public final class Colorizer {
ArgumentChecks.ensureFinite("maximum", maximum);
defaultRange = NumberRange.create(minimum, true, maximum, true);
target = new SampleDimension.Builder()
- .setBackground(null, 0)
- .addQuantitative(null, NumberRange.create(1, true, MAX_VALUE, true), defaultRange).build();
-
+ .setBackground(TRANSPARENT, 0)
+ .addQuantitative(COLOR_INDEX, NumberRange.create(1, true, MAX_VALUE, true), defaultRange)
+ .setName(VISUAL).build();
+ /*
+ * We created a synthetic `SampleDimension` with the specified range of values.
+ * Recompute `source` and `entries` fields for consistency with the new ranges.
+ * The `source` is recreated as a matter of principle, but will not be used by
+ * `compact()` because `target` will take precedence.
+ */
source = target.forConvertedValues(true);
- final List<Category> categories = source.getCategories();
+ final List<Category> categories = target.getCategories();
final ColorsForRange[] entries = new ColorsForRange[categories.size()];
for (int i=0; i<entries.length; i++) {
- final Category category = categories.get(i);
- entries[i] = new ColorsForRange(category, category.forConvertedValues(false).getSampleRange(), colors.apply(category));
+ entries[i] = new ColorsForRange(categories.get(i), GRAYSCALE);
}
this.entries = entries;
}
@@ -417,13 +452,12 @@ reuse: if (source != null) {
for (int i=0; i<count; i++) {
final ColorsForRange entry = entries[i];
NumberRange<?> sourceRange = entry.sampleRange;
- if (!entry.isData()) {
+ if (!entry.isData) {
if (lower >= MAX_VALUE) {
throw new IllegalArgumentException(Resources.format(Resources.Keys.TooManyQualitatives));
}
final NumberRange<Integer> targetRange = NumberRange.create(lower, true, ++lower, false);
if (mapper.put(targetRange, entry) == null) {
- final CharSequence name = entry.name();
final double value = sourceRange.getMinDouble();
/*
* In the usual case where we have a mix of quantitative and qualitative categories,
@@ -433,12 +467,12 @@ reuse: if (source != null) {
* computing a transfer function, but those categories should not be returned to user.
*/
if (Double.isNaN(value)) {
- builder.mapQualitative(name, targetRange, (float) value);
+ builder.mapQualitative(entry.name, targetRange, (float) value);
} else {
if (value == entry.sampleRange.getMaxDouble()) {
sourceRange = NumberRange.create(value - 0.5, true, value + 0.5, false);
}
- builder.addQuantitative(name, targetRange, sourceRange);
+ builder.addQuantitative(entry.name, targetRange, sourceRange);
themes = (themes != null) ? themes.unionAny(sourceRange) : sourceRange;
}
}
@@ -472,7 +506,7 @@ reuse: if (source != null) {
span += sourceRange.getSpan();
final ColorsForRange[] tmp = Arrays.copyOf(entries, ++count);
System.arraycopy(entries, deferred, tmp, ++deferred, count - deferred);
- tmp[deferred-1] = new ColorsForRange(null, sourceRange, new Color[] {Color.BLACK, Color.WHITE});
+ tmp[deferred-1] = new ColorsForRange(null, sourceRange, new Color[] {Color.BLACK, Color.WHITE}, true);
entries = tmp;
}
}
@@ -494,7 +528,7 @@ reuse: if (source != null) {
}
final NumberRange<Integer> samples = NumberRange.create(lower, true, upper, false);
if (mapper.put(samples, entry) == null) {
- builder.addQuantitative(entry.name(), samples, entry.sampleRange);
+ builder.addQuantitative(entry.name, samples, entry.sampleRange);
}
lower = upper;
}
@@ -505,7 +539,7 @@ reuse: if (source != null) {
if (source != null) {
builder.setName(source.getName());
} else {
- builder.setName(Vocabulary.format(Vocabulary.Keys.Visual));
+ builder.setName(VISUAL);
}
target = builder.build();
for (final Category category : target.getCategories()) {
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/ColorsForRange.java b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/ColorsForRange.java
index 3236904432..23a6bbd66b 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/ColorsForRange.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/ColorsForRange.java
@@ -18,6 +18,7 @@ package org.apache.sis.internal.coverage.j2d;
import java.util.Map;
import java.util.Collection;
+import java.util.function.Function;
import java.awt.Color;
import java.awt.image.IndexColorModel;
import org.apache.sis.coverage.Category;
@@ -31,7 +32,7 @@ import org.apache.sis.util.ArraysExt;
* the time needed for {@link ColorModelFactory#createColorModel(int, int, int, ColorsForRange[])}.
*
* @author Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.2
*
* @see ColorModelFactory#createColorModel(int, int, int, ColorsForRange[])
*
@@ -40,10 +41,10 @@ import org.apache.sis.util.ArraysExt;
*/
final class ColorsForRange implements Comparable<ColorsForRange> {
/**
- * If this {@code ColorsForRange} has been created for a category, that category.
- * Otherwise {@code null}.
+ * A name identifying the range of values. the category name is used if available,
+ * otherwise this is a string representation of the range.
*/
- private final Category category;
+ final CharSequence name;
/**
* The range of sample values on which the colors will be applied. Shall never be null.
@@ -58,18 +59,40 @@ final class ColorsForRange implements Comparable<ColorsForRange> {
*/
private final Color[] colors;
+ /**
+ * {@code true} if this entry should be taken as data, or {@code false} if it should be ignored.
+ * Entry to ignore are entries associated to NaN values.
+ */
+ final boolean isData;
+
+ /**
+ * Creates a new instance for the given category.
+ *
+ * @param category the category for which this {@code ColorsForRange} is created, or {@code null}.
+ * @param colors colors to apply on the category.
+ */
+ ColorsForRange(final Category category, final Function<Category,Color[]> colors) {
+ final CharSequence name = category.getName();
+ this.name = (name != null) ? name : sampleRange.toString();
+ this.sampleRange = category.getSampleRange();
+ this.colors = colors.apply(category);
+ this.isData = category.isQuantitative();
+ }
+
/**
* Creates a new instance for the given range of values.
*
- * @param category the category for which this {@code ColorsForRange} is created, or {@code null}.
+ * @param name a name identifying the range of values, or {@code null} for automatic.
* @param sampleRange range of sample values on which the colors will be applied.
* @param colors colors to apply on the range of sample values, or {@code null} for transparent.
+ * @param isData whether this entry should be taken as main data (not fill values).
*/
- ColorsForRange(final Category category, final NumberRange<?> sampleRange, final Color[] colors) {
+ ColorsForRange(final CharSequence name, final NumberRange<?> sampleRange, final Color[] colors, final boolean isData) {
ArgumentChecks.ensureNonNull("sampleRange", sampleRange);
- this.category = category;
+ this.name = (name != null) ? name : sampleRange.toString();
this.sampleRange = sampleRange;
this.colors = colors;
+ this.isData = isData;
}
/**
@@ -85,37 +108,17 @@ final class ColorsForRange implements Comparable<ColorsForRange> {
final ColorsForRange[] entries = new ColorsForRange[colors.size()];
int n = 0;
for (final Map.Entry<NumberRange<?>,Color[]> entry : colors) {
- entries[n++] = new ColorsForRange(null, entry.getKey(), entry.getValue());
+ entries[n++] = new ColorsForRange(null, entry.getKey(), entry.getValue(), true);
}
return ArraysExt.resize(entries, n); // `resize` should not be needed, but we are paranoiac.
}
- /**
- * Returns {@code true} if this entry should be taken as data, or {@code false} if it should be ignored.
- * Entry to ignore are entries associated to NaN values.
- */
- final boolean isData() {
- return category == null || category.isQuantitative();
- }
-
- /**
- * Returns a name identifying the range of values. the category name is used if available,
- * otherwise a string representation of the range is created.
- */
- final CharSequence name() {
- if (category != null) {
- final CharSequence name = category.getName();
- if (name != null) return name;
- }
- return sampleRange.toString();
- }
-
/**
* Returns a string representation for debugging purposes.
*/
@Override
public String toString() {
- return name().toString();
+ return name.toString();
}
/**
diff --git a/core/sis-feature/src/test/java/org/apache/sis/internal/coverage/j2d/ColorizerTest.java b/core/sis-feature/src/test/java/org/apache/sis/internal/coverage/j2d/ColorizerTest.java
index 5d37bb214d..8fc4c8a49d 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/internal/coverage/j2d/ColorizerTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/internal/coverage/j2d/ColorizerTest.java
@@ -38,7 +38,7 @@ import static org.junit.Assert.*;
* Tests {@link Colorizer}.
*
* @author Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.2
* @since 1.1
* @module
*/
@@ -101,7 +101,7 @@ public final strictfp class ColorizerTest extends TestCase {
.setName("Temperature").build();
final Colorizer colorizer = new Colorizer(Colorizer.GRAYSCALE);
- assertTrue("initialize", colorizer.initialize(sd));
+ assertTrue("initialize", colorizer.initialize(null, sd));
final IndexColorModel cm = (IndexColorModel) colorizer.compactColorModel(1, 0); // Must be first.
/*
* Test conversion of a few sample values to packed values.