/*
* Constellation - An open source and standard compliant SDI
* http://www.constellation-sdi.org
*
* Copyright 2014 Geomatys.
*
* Licensed 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.constellation.json.binding;
import org.constellation.json.util.StyleUtilities;
import org.geotoolkit.filter.DefaultLiteral;
import org.geotoolkit.style.StyleConstants;
import org.geotoolkit.style.function.ThreshholdsBelongTo;
import org.opengis.filter.expression.Expression;
import org.opengis.filter.expression.Literal;
import java.awt.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.apache.sis.util.ArgumentChecks.ensureNonNull;
import static org.constellation.json.util.StyleFactories.SF;
/**
* @author Benjamin Garcia (Geomatys)
*/
public class Categorize implements Function {
private static final long serialVersionUID = 1L;
private List<InterpolationPoint> points = new ArrayList<InterpolationPoint>();
private Double interval;
private String nanColor;
public Categorize() {
}
@SuppressWarnings("rawtypes")
public Categorize(final org.geotoolkit.style.function.Categorize categorize) {
ensureNonNull("categorize", categorize);
final Map<Expression, Expression> thresholdsMap = categorize.getThresholds();
if (thresholdsMap != null) {
for (final Map.Entry<Expression, Expression> entry : thresholdsMap.entrySet()) {
final InterpolationPoint ip = new InterpolationPoint();
final Expression expression = entry.getKey();
final Expression colorHexExp = entry.getValue();
if (colorHexExp instanceof DefaultLiteral) {
final Object colorHex = ((DefaultLiteral) colorHexExp).getValue();
ip.setColor(StyleUtilities.toHex((Color) colorHex));
}
if (expression instanceof DefaultLiteral) {
final Object obj = ((DefaultLiteral) expression).getValue();
if (obj instanceof Double) {
if (Double.isNaN((double) obj)) {
ip.setData(null);
nanColor = ip.getColor();
} else {
ip.setData((Number) obj);
}
} else if (StyleConstants.CATEGORIZE_LESS_INFINITY.equals(expression)) {
continue; //skip for infinity first key it will be restored later.
}
}
points.add(ip);
}
this.interval = (double) categorize.getThresholds().size();
}
}
public List<InterpolationPoint> getPoints() {
return points;
}
public void setPoints(List<InterpolationPoint> points) {
this.points = points;
}
public List<InterpolationPoint> reComputePoints(final Integer nbPoints) {
//remove nan point if exists because it is added later, and it cause error for max/min values
final List<InterpolationPoint> nullPoints = new ArrayList<>();
for (final InterpolationPoint ip : points) {
if (ip.getData() == null) {
nullPoints.add(ip);
}
}
points.removeAll(nullPoints);
final Map<Expression, Expression> values = new HashMap<>();
values.put(StyleConstants.CATEGORIZE_LESS_INFINITY, new DefaultLiteral<Color>(Color.GRAY));
for (final InterpolationPoint ip : points) {
values.put(new DefaultLiteral<Double>(ip.getData().doubleValue()),
new DefaultLiteral<String>(ip.getColor()));
}
final org.geotoolkit.style.function.Categorize categorize = SF.categorizeFunction(StyleConstants.DEFAULT_CATEGORIZE_LOOKUP,
values,
ThreshholdsBelongTo.PRECEDING,
StyleConstants.DEFAULT_FALLBACK);
// Iteration to find min and max values
Double min = null, max = null;
for (final InterpolationPoint ip : points) {
if (min == null && max == null) {
min = ip.getData().doubleValue();
max = ip.getData().doubleValue();
}
min = Math.min(min, ip.getData().doubleValue());
max = Math.max(max, ip.getData().doubleValue());
}
//init final threshold map and coefficient
final Map<Expression, Expression> valuesRecompute = new HashMap<>();
if (nanColor != null) {
valuesRecompute.put(new DefaultLiteral<Double>(Double.NaN),
new DefaultLiteral<Color>(Color.decode(nanColor)));
}
if (min != null && max != null) {
double coefficient = max - min;
if (nbPoints != null) {
if (coefficient != 1) {
coefficient = coefficient / (nbPoints - 1);
}
// Loop to create values with new point evaluation
for (int i = 0; i < nbPoints; i++) {
double val = min + (coefficient * i);
Color color = categorize.evaluate(val, Color.class);
valuesRecompute.put(new DefaultLiteral<Double>(val), new DefaultLiteral<Color>(color));
}
}
}
final List<InterpolationPoint> recomputePoints = new ArrayList<>();
for(final Map.Entry<Expression,Expression> entry : valuesRecompute.entrySet()) {
final Literal value = (Literal)entry.getKey();
final Literal color = (Literal)entry.getValue();
final Color colorObj = (Color) color.getValue();
final Double valueObj = (Double) value.getValue();
final InterpolationPoint point = new InterpolationPoint();
point.setColor(StyleUtilities.toHex(colorObj));
point.setData(valueObj);
recomputePoints.add(point);
}
//sort recomputePoints
Collections.sort(recomputePoints,new InterpolationPointComparator());
return recomputePoints;
}
public double getInterval() {
return interval;
}
public void setInterval(Double interval) {
this.interval = interval;
}
public String getNanColor() {
return nanColor;
}
public void setNanColor(String nanColor) {
this.nanColor = nanColor;
}
public org.opengis.filter.expression.Function toType() {
//remove nan point if exists because it is added later, and it cause error for max/min values
final List<InterpolationPoint> nullPoints = new ArrayList<>();
for (final InterpolationPoint ip : points) {
if (ip.getData() == null) {
nullPoints.add(ip);
}
}
points.removeAll(nullPoints);
// create first threshold map to create first categorize function.
Map<Expression, Expression> values = new HashMap<>(0);
if (nanColor != null) {
values.put(new DefaultLiteral<Double>(Double.NaN),
new DefaultLiteral<String>(nanColor));
}
values.put(StyleConstants.CATEGORIZE_LESS_INFINITY,new DefaultLiteral<String>("#00ffffff"));
for (final InterpolationPoint ip : points) {
values.put(new DefaultLiteral<Double>(ip.getData().doubleValue()),
new DefaultLiteral<String>(ip.getColor()));
}
return SF.categorizeFunction(StyleConstants.DEFAULT_CATEGORIZE_LOOKUP,
values,
ThreshholdsBelongTo.PRECEDING,
StyleConstants.DEFAULT_FALLBACK);
}
private static class InterpolationPointComparator implements Comparator<InterpolationPoint> {
@Override
public int compare(InterpolationPoint o1, InterpolationPoint o2) {
return Double.compare((Double)o1.getData(), (Double)o2.getData());
}
}
}