use of org.opengis.style.ColorMap in project geotoolkit by Geomatys.
the class PaletteReader method read.
public ColorMap read(String candidate) throws IOException {
final String[] parts = candidate.split("\n");
boolean isHSV = false;
final List<Row> rows = new ArrayList<>();
lines: for (String part : parts) {
part = part.trim();
if (part.isEmpty())
continue lines;
// check if we ignore this line
for (Pattern p : ignorePatterns) {
if (p.matcher(part).matches()) {
// check for something like :
// COLOR_MODEL = +HSV
// COLOR_MODEL = HSV
isHSV |= part.contains("COLOR_MODEL") && part.contains("HSV");
continue lines;
}
}
// parse values
String pattern = valuePattern;
final Row row = new Row();
while (!part.isEmpty()) {
boolean optional = false;
if (pattern.isEmpty()) {
// ignore anything remaining
break;
} else if (pattern.charAt(0) == '?') {
optional = true;
pattern = pattern.substring(1);
}
if (!valStart.matcher(pattern).matches()) {
char c = pattern.charAt(0);
if (c == ' ') {
part = part.trim();
} else {
char v = part.charAt(0);
if (v == c) {
part = part.substring(1);
} else if (!optional) {
throw new IOException("Pattern do not match.");
}
}
pattern = pattern.substring(1);
continue;
}
// we work with a value
final int numberEnd = numberEnd(part);
if (numberEnd == 0) {
pattern = pattern.substring(2);
if (optional) {
continue;
} else {
throw new IOException("Pattern do not match.");
}
}
Double val = parseDouble(part, numberEnd);
part = part.substring(numberEnd);
if (pattern.startsWith("v1"))
row.v1 = val;
if (pattern.startsWith("v2"))
row.v2 = val;
if (pattern.startsWith("r1"))
row.r1 = val;
if (pattern.startsWith("r2"))
row.r2 = val;
if (pattern.startsWith("g1"))
row.g1 = val;
if (pattern.startsWith("g2"))
row.g2 = val;
if (pattern.startsWith("b1"))
row.b1 = val;
if (pattern.startsWith("b2"))
row.b2 = val;
pattern = pattern.substring(2);
}
rows.add(row);
}
// sort values in ascending order
Collections.sort(rows);
final ColorMap colorMap;
if (!categorize) {
// interpolated color model
final List<InterpolationPoint> values = new ArrayList<>();
values.add(SF.interpolationPoint(Double.NaN, SF.literal(new Color(0, 0, 0, 0))));
for (Row row : rows) {
values.add(SF.interpolationPoint(row.v1, SF.literal(row.getColor1(isHSV))));
}
final Expression function = SF.interpolateFunction(DEFAULT_CATEGORIZE_LOOKUP, values, Method.COLOR, Mode.LINEAR, DEFAULT_FALLBACK);
colorMap = SF.colorMap(function);
} else {
// categorize color model
final Map<Expression, Expression> values = new HashMap<>();
for (int i = 0, n = rows.size(); i < n; i++) {
final Row row = rows.get(i);
if (values.isEmpty()) {
if (row.v1 == null) {
values.put(StyleConstants.CATEGORIZE_LESS_INFINITY, SF.literal(row.getColor1(isHSV)));
} else {
// add a translucent range from -infinity to value
values.put(StyleConstants.CATEGORIZE_LESS_INFINITY, SF.literal(new Color(0f, 0f, 0f, 0f)));
values.put(FF.literal(row.v1), SF.literal(row.getColor1(isHSV)));
}
} else {
// two values, two colors
final Row lastRow = rows.get(i - 1);
if (lastRow.v2.doubleValue() == row.v1.doubleValue() && lastRow.r2 != null && !lastRow.getColor2(isHSV).equals(row.getColor1(isHSV))) {
// a color change at exact value, add a row for this one
values.put(FF.literal(lastRow.v2), SF.literal(lastRow.getColor2(isHSV)));
values.put(FF.literal(Math.nextUp(row.v1)), SF.literal(row.getColor1(isHSV)));
} else {
values.put(FF.literal(row.v1), SF.literal(row.getColor1(isHSV)));
}
}
// special case for last element
if (i == n - 1) {
if (row.r2 == null) {
values.put(FF.literal(row.v2), SF.literal(new Color(0f, 0f, 0f, 0f)));
} else {
values.put(FF.literal(row.v2), SF.literal(row.getColor2(isHSV)));
}
// add a NaN group
values.put(FF.literal(Double.NaN), SF.literal(new Color(0f, 0f, 0f, 0f)));
}
}
final Expression function = SF.categorizeFunction(DEFAULT_CATEGORIZE_LOOKUP, values, ThreshholdsBelongTo.SUCCEEDING, DEFAULT_FALLBACK);
colorMap = SF.colorMap(function);
}
return colorMap;
}
use of org.opengis.style.ColorMap in project geotoolkit by Geomatys.
the class CachedIsolineSymbolizer method extractSteps.
/**
* Extract isolines steps from RasterSymbolizer ColorMap.
*/
private double[] extractSteps(RasterSymbolizer rasterSymbolizer) {
Set<Double> steps = new HashSet<Double>();
if (rasterSymbolizer != null && rasterSymbolizer.getColorMap() != null) {
ColorMap colorMap = rasterSymbolizer.getColorMap();
Expression function = colorMap.getFunction();
if (function instanceof Interpolate) {
Interpolate interpolate = (Interpolate) function;
List<InterpolationPoint> points = interpolate.getInterpolationPoints();
for (InterpolationPoint point : points) {
steps.add(point.getData().doubleValue());
}
dynamicColorMap = false;
} else if (function instanceof Categorize) {
Categorize categorize = (Categorize) function;
Map<Expression, Expression> thresholds = categorize.getThresholds();
for (Map.Entry<Expression, Expression> entry : thresholds.entrySet()) {
Expression key = entry.getKey();
Number step = (Number) key.apply(null);
if (step != null && Double.isFinite(step.doubleValue())) {
steps.add(step.doubleValue());
}
}
dynamicColorMap = false;
} else if (function instanceof Jenks) {
Jenks jenks = (Jenks) function;
Map<Double, Color> jenksColorMap = jenks.getColorMap();
if (jenksColorMap != null) {
for (Double jenksStep : jenksColorMap.keySet()) {
if (jenksStep != null && !jenksStep.isNaN()) {
steps.add(jenksStep);
}
}
}
dynamicColorMap = true;
}
}
int i = 0;
Iterator<Double> iterator = steps.iterator();
double[] stepsArray = new double[steps.size()];
while (iterator.hasNext()) {
stepsArray[i++] = iterator.next();
}
return stepsArray;
}
use of org.opengis.style.ColorMap in project geotoolkit by Geomatys.
the class RasterSymbolizerRenderer method applyColorMapStyle.
/**
* Apply {@linkplain RasterSymbolizer#getColorMap() color map style properties} on current coverage if need.<br><br>
*
* In case where no {@linkplain ColorMap#getFunction() sample to geophysic}
* transformation function is available and coverage is define as {@link ViewType#GEOPHYSICS}
* a way is find to avoid empty result, like follow : <br>
* The first band from {@linkplain GridCoverage2D#getRenderedImage() coverage image} is selected
* and a grayscale color model is apply from {@linkplain ImageStatistics computed image statistic}.
*
* @param ref needed to compute statistics from internal metadata in case where missing informations.
* @param coverage color map style apply on this object.
* @param styleElement the {@link RasterSymbolizer} which contain color map properties.
* @return image which is the coverage exprimate into {@link ViewType#PHOTOGRAPHIC}.
* @throws ProcessException if problem during statistic problem.
*/
private static RenderedImage applyColorMapStyle(final GridCoverageResource ref, GridCoverage coverage, final RasterSymbolizer styleElement) throws ProcessException, IOException, PortrayalException {
ensureNonNull("CoverageReference", ref);
ensureNonNull("coverage", coverage);
ensureNonNull("styleElement", styleElement);
RenderedImage resultImage;
// Recolor coverage -----------------------------------------------------
final ColorMap recolor = styleElement.getColorMap();
recolorCase: if (recolor == null || recolor.getFunction() == null) {
resultImage = coverage.forConvertedValues(false).render(null);
final SampleModel sampleMod = resultImage.getSampleModel();
final ColorModel riColorModel = resultImage.getColorModel();
/**
* Break computing statistic if indexcolormodel is already adapted for java 2d interpretation
* (which mean index color model with positive colormap array index -> DataBuffer.TYPE_BYTE || DataBuffer.TYPE_USHORT)
* or if image has already 3 or 4 bands Byte typed.
*/
if (riColorModel != null && !defaultStyleIsNeeded(sampleMod, riColorModel)) {
break recolorCase;
}
// if there is no geophysic, the same coverage is returned
coverage = coverage.forConvertedValues(true);
CoverageDescription covRefMetadata = null;
if (covRefMetadata == null) {
final Metadata metadata;
try {
metadata = ref.getMetadata();
} catch (DataStoreException ex) {
throw new IOException("Cannot fetch metadata from input resource.", ex);
}
covRefMetadata = MetadataUtilities.extractCoverageDescription(metadata).findFirst().orElse(null);
}
ImageStatistics analyse = null;
if (covRefMetadata != null) {
analyse = ImageStatistics.transform(covRefMetadata);
if (analyse != null) {
// Ensure band statistics are valid.
for (ImageStatistics.Band b : analyse.getBands()) {
if (b.getMax() == null || b.getMin() == null || b.getMax() - b.getMin() < 1e-11) {
analyse = null;
break;
}
}
}
}
// ensure consistency over tiled rendering (cf. OpenLayer/WMS).
if (analyse == null) {
analyse = Statistics.analyse(coverage.render(null), true);
}
final Optional<MutableStyle> styleFromStats = GO2Utilities.inferStyle(analyse, (riColorModel == null) ? true : riColorModel.hasAlpha());
if (styleFromStats.isPresent()) {
/* WARNING: That's neither optimal nor stable. However, do not know any other way to override style on
* the fly.
*
* !!! IMPORTANT !!!
* The canvas here is created with the geometry of input coverage, because otherwise, we would apply
* two times the affine transform to display system.
*/
final MapLayers subCtx = MapBuilder.createContext();
subCtx.getComponents().add(MapBuilder.createCoverageLayer(coverage, styleFromStats.get()));
resultImage = DefaultPortrayalService.portray(new CanvasDef(coverage.getGridGeometry()), new SceneDef(subCtx));
}
} else {
// color map is applied on geophysics view
// if there is no geophysic, the same coverage is returned
coverage = coverage.forConvertedValues(true);
resultImage = (RenderedImage) recolor.getFunction().apply(coverage.render(null));
}
assert resultImage != null : "applyColorMapStyle : image can't be null.";
return resultImage;
}
use of org.opengis.style.ColorMap in project geotoolkit by Geomatys.
the class RasterSymbolizerRendererService method glyph.
/**
* {@inheritDoc }
*/
@Override
public void glyph(final Graphics2D g, final Rectangle2D rectangle, final CachedRasterSymbolizer symbol, final MapLayer layer) {
float[] fractions;
Color[] colors;
final ColorMap cm = symbol.getSource().getColorMap();
// paint default Glyph
if (cm == null || cm.getFunction() == null || (!(cm.getFunction() instanceof Interpolate) && !(cm.getFunction() instanceof Jenks) && !(cm.getFunction() instanceof Categorize))) {
fractions = new float[] { 0.0f, 0.5f, 1.0f };
colors = new Color[] { Color.RED, Color.GREEN, Color.BLUE };
final MultipleGradientPaint.CycleMethod cycleMethod = MultipleGradientPaint.CycleMethod.NO_CYCLE;
final LinearGradientPaint paint = new LinearGradientPaint(new Point2D.Double(rectangle.getMinX(), rectangle.getMinY()), new Point2D.Double(rectangle.getMaxX(), rectangle.getMinY()), fractions, colors, cycleMethod);
g.setPaint(paint);
g.fill(rectangle);
return;
}
// paint Interpolation, Categorize and Jenks Glyphs
final Map<Object, Color> colorMap = getMapColor(symbol);
if (!colorMap.isEmpty()) {
boolean doInterpolation = true;
if (colorMap.keySet().iterator().next() instanceof Range) {
doInterpolation = false;
}
// find an appropriate number format without too much digits for this value range
NumberFormat numFormat = null;
if (doInterpolation) {
double min = Double.POSITIVE_INFINITY;
double max = Double.NEGATIVE_INFINITY;
for (Object o : colorMap.keySet()) {
if (o instanceof String) {
try {
o = Double.valueOf(o.toString().trim());
} catch (NumberFormatException ex) {
continue;
}
}
if (o instanceof Number) {
min = Math.min(((Number) o).doubleValue(), min);
max = Math.max(((Number) o).doubleValue(), max);
}
}
if (!Double.isInfinite(max)) {
final double step = (max - min) / (colorMap.size() * 10);
final int nbDigit = DecimalFunctions.fractionDigitsForDelta(step, false);
numFormat = NumberFormat.getNumberInstance();
numFormat.setMaximumFractionDigits(nbDigit);
}
}
final int colorMapSize = colorMap.size();
int fillHeight = Double.valueOf(rectangle.getHeight()).intValue();
int intervalHeight = fillHeight / colorMapSize;
Rectangle2D paintRectangle = new Rectangle((int) rectangle.getX(), (int) rectangle.getY(), LEGEND_PALETTE_WIDTH, fillHeight);
g.setClip(rectangle);
if (doInterpolation) {
// fill color array
colors = colorMap.values().toArray(new Color[colorMapSize]);
// check we don't have any null colors
for (int i = 0; i < colors.length; i++) {
if (colors[i] == null) {
colors[i] = new Color(0, 0, 0, 0);
}
}
// fill fraction array
final float interval = 0.9f / colorMapSize;
float fraction = 0.1f;
fractions = new float[colorMapSize];
for (int i = 0; i < colorMapSize; i++) {
fractions[i] = fraction;
fraction += interval;
}
// paint nothing
if (colors.length == 0) {
return;
}
// ensure we have at least 2 colors
if (colors.length == 1) {
colors = new Color[] { colors[0], colors[0] };
fractions = new float[] { fractions[0], 1.0f };
}
// create gradient
final LinearGradientPaint paint = new LinearGradientPaint(new Point2D.Double(paintRectangle.getMinX(), rectangle.getMinY()), new Point2D.Double(paintRectangle.getMinX(), rectangle.getMaxY()), fractions, colors, MultipleGradientPaint.CycleMethod.NO_CYCLE);
g.setPaint(paint);
g.fill(paintRectangle);
} else {
// paint all colors rectangles
Collection<Color> colorsList = colorMap.values();
int intX = Double.valueOf(rectangle.getMinX()).intValue();
int intY = Double.valueOf(rectangle.getMinY()).intValue();
for (Color color : colorsList) {
final Rectangle2D colorRect = new Rectangle(intX, intY, LEGEND_PALETTE_WIDTH, intervalHeight);
g.setPaint(color);
g.fill(colorRect);
intY += intervalHeight;
}
}
// paint text
float Y = Double.valueOf(rectangle.getMinY()).floatValue();
float shift = doInterpolation ? 0.6f : 0.7f;
g.setColor(Color.BLACK);
Object[] keys = colorMap.keySet().toArray(new Object[colorMap.size()]);
for (int i = 0; i < keys.length; i++) {
final Object current = keys[i];
final Object next = (i < keys.length - 1) ? keys[i + 1] : null;
final StringBuilder text = getLineText(current, next, numFormat);
g.drawString(text.toString(), LEGEND_PALETTE_WIDTH + 1f, Y + intervalHeight * shift);
Y += intervalHeight;
}
}
}
use of org.opengis.style.ColorMap in project geotoolkit by Geomatys.
the class RasterSymbolizerRendererService method getMapColor.
/**
* Create a map of object and colors from symbolizer colormap functions like
* Interpolate, Jenks and Categorize.
*
* @param symbol CachedRaserSymbolizer
* @return a Map containing Object like Range or String for key and Color as value.
*/
private Map<Object, Color> getMapColor(final CachedRasterSymbolizer symbol) {
Map<Object, Color> colorMap = new LinkedHashMap<>();
final ColorMap cm = symbol.getSource().getColorMap();
if (cm != null && cm.getFunction() != null) {
final Expression fct = cm.getFunction();
if (fct instanceof Interpolate) {
final Interpolate interpolate = (Interpolate) fct;
final List<InterpolationPoint> points = interpolate.getInterpolationPoints();
final int size = points.size();
for (int i = 0; i < size; i++) {
final InterpolationPoint pt = points.get(i);
Color color = (Color) pt.getValue().apply(null);
if (color == null)
try {
color = ObjectConverters.convert(pt.getValue().toString(), Color.class);
} catch (UnconvertibleObjectException e) {
Logging.recoverableException(LOGGER, RasterSymbolizerRendererService.class, "getMapColor", e);
// TODO - do we really want to ignore?
}
colorMap.put(pt.getData().toString(), color);
}
} else if (fct instanceof Jenks) {
final Jenks jenks = (Jenks) fct;
final Map<Double, Color> jenksColorMap = jenks.getColorMap();
final Map<Color, List<Double>> rangeJenksMap = new HashMap<>();
for (Map.Entry<Double, Color> elem : jenksColorMap.entrySet()) {
if (rangeJenksMap.containsKey(elem.getValue())) {
final List<Double> values = rangeJenksMap.get(elem.getValue());
values.add(elem.getKey());
Collections.sort(values);
rangeJenksMap.put(elem.getValue(), values);
} else {
final List<Double> values = new ArrayList<Double>();
values.add(elem.getKey());
rangeJenksMap.put(elem.getValue(), values);
}
}
// create range sorted map.
colorMap = new TreeMap(new RangeComparator());
for (Map.Entry<Color, List<Double>> elem : rangeJenksMap.entrySet()) {
final List<Double> values = elem.getValue();
Collections.sort(values);
colorMap.put(new NumberRange<>(Double.class, values.get(0), true, values.get(values.size() - 1), true), elem.getKey());
}
} else if (fct instanceof Categorize) {
final Categorize categorize = (Categorize) fct;
final Map<Expression, Expression> thresholds = categorize.getThresholds();
final Map<Color, List<Double>> colorValuesMap = new HashMap<>();
for (Map.Entry<Expression, Expression> entry : thresholds.entrySet()) {
final Color currentColor = (Color) entry.getValue().apply(null);
Double currentValue = Double.NEGATIVE_INFINITY;
try {
Double value = doubleValue(entry.getKey());
if (value != null) {
currentValue = value;
}
} catch (Exception e) {
if (StyleConstants.CATEGORIZE_LESS_INFINITY.equals(entry.getKey())) {
currentValue = Double.NEGATIVE_INFINITY;
} else {
// Cannot read value, it's not a number, neither a "categorize less infinity".
LOGGER.log(Level.INFO, "A color map value cannot be evaluated. it will be ignored.\nCause : " + e.getLocalizedMessage());
currentValue = null;
}
}
if (currentColor != null && currentValue != null) {
if (colorValuesMap.containsKey(currentColor)) {
final LinkedList<Double> values = (LinkedList<Double>) colorValuesMap.get(currentColor);
values.add(currentValue);
colorValuesMap.put(currentColor, values);
} else {
final LinkedList<Double> values = new LinkedList<Double>();
values.add(currentValue);
colorValuesMap.put(currentColor, values);
}
}
}
// create range sorted map.
colorMap = new TreeMap(new RangeComparator());
for (Map.Entry<Color, List<Double>> elem : colorValuesMap.entrySet()) {
final List<Double> values = elem.getValue();
Collections.sort(values);
colorMap.put(new NumberRange<>(Double.class, values.get(0), true, values.get(values.size() - 1), true), elem.getKey());
}
}
}
return colorMap;
}
Aggregations