/*
* Geotoolkit - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 2014-2015, Geomatys
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*/
package org.geotoolkit.gui.javafx.style;
import java.awt.Color;
import java.awt.image.RenderedImage;
import java.io.IOException;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.logging.Level;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ComboBox;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.Label;
import javafx.scene.control.ListView;
import javafx.scene.control.SpinnerValueFactory;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.Border;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.CornerRadii;
import javafx.util.Callback;
import org.apache.sis.storage.DataStoreException;
import org.geotoolkit.coverage.grid.GeneralGridGeometry;
import org.geotoolkit.coverage.grid.GridCoverage2D;
import org.geotoolkit.coverage.io.CoverageStoreException;
import org.geotoolkit.coverage.io.GridCoverageReadParam;
import org.geotoolkit.coverage.io.GridCoverageReader;
import org.geotoolkit.display2d.GO2Utilities;
import org.geotoolkit.filter.DefaultLiteral;
import org.apache.sis.geometry.GeneralEnvelope;
import static org.geotoolkit.gui.javafx.style.FXStyleElementController.getFilterFactory;
import static org.geotoolkit.gui.javafx.style.FXStyleElementController.getStyleFactory;
import org.geotoolkit.gui.javafx.util.FXNumberSpinner;
import org.geotoolkit.gui.javafx.util.FXTableCell;
import org.geotoolkit.gui.javafx.util.FXUtilities;
import org.geotoolkit.internal.GeotkFX;
import org.geotoolkit.internal.Loggers;
import org.geotoolkit.map.CoverageMapLayer;
import org.geotoolkit.map.MapLayer;
import org.geotoolkit.processing.coverage.statistics.StatisticOp;
import org.apache.sis.geometry.Envelopes;
import org.geotoolkit.storage.coverage.CoverageReference;
import org.geotoolkit.storage.coverage.CoverageUtilities;
import org.geotoolkit.style.StyleConstants;
import static org.geotoolkit.style.StyleConstants.DEFAULT_CATEGORIZE_LOOKUP;
import static org.geotoolkit.style.StyleConstants.DEFAULT_FALLBACK;
import org.geotoolkit.style.function.Categorize;
import org.geotoolkit.style.function.DefaultInterpolationPoint;
import org.geotoolkit.style.function.Interpolate;
import org.geotoolkit.style.function.InterpolationPoint;
import org.geotoolkit.style.function.Jenks;
import org.geotoolkit.style.function.Method;
import org.geotoolkit.style.function.Mode;
import org.geotoolkit.style.function.ThreshholdsBelongTo;
import org.geotoolkit.style.interval.Palette;
import org.opengis.coverage.grid.GridCoverage;
import org.opengis.coverage.grid.GridEnvelope;
import org.opengis.filter.expression.Expression;
import org.opengis.filter.expression.Literal;
import org.opengis.metadata.content.AttributeGroup;
import org.opengis.metadata.content.CoverageDescription;
import org.opengis.metadata.content.RangeDimension;
import org.opengis.metadata.content.SampleDimension;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.TransformException;
import org.opengis.style.ColorMap;
/**
*
* @author Johann Sorel (Geomatys)
*/
public class FXColorMap extends FXStyleElementController<ColorMap> {
private static final NumberFormat FORMATTER = new DecimalFormat("#0.000");
private static final Literal TRS = new DefaultLiteral(new Color(0, 0, 0, 0));
@FXML
private CheckBox uiInvert;
@FXML
private CheckBox uiNaN;
@FXML
private Label uiMethodLbl;
@FXML
private FXNumberSpinner uiBand;
@FXML
private TableView<InterOrCategorize> uiTable;
@FXML
private Button uiAddOne;
@FXML
private Label uiDynamic;
@FXML
private Label uiBandLbl;
@FXML
private Button uiGenerate;
@FXML
private Button uiRemoveAll;
@FXML
private Label uiNoData;
@FXML
private FXNumberSpinner uiMinimum;
@FXML
private ComboBox<String> uiMethod;
@FXML
private Label uiPaletteLbl;
@FXML
private Label uiDivisionLbl;
@FXML
private FXNumberSpinner uiDivision;
@FXML
private FXNumberSpinner uiMaximum;
@FXML
private ComboBox<Object> uiPalette;
private Class function = null;
private Value1Column value1Col;
private Value2Column value2Col;
private ColorColumn colorCol;
@FXML
private void methodChange(ActionEvent event) {
final String method = uiMethod.getSelectionModel().getSelectedItem();
if("Interpolate".equals(method)){
if(Interpolate.class.isAssignableFrom(function)){
//nothing to do
return;
}else if(Categorize.class.isAssignableFrom(function)){
final List<InterOrCategorize> points = new ArrayList<>(uiTable.getItems());
if(points.size()>0){
//remove the first element which is the -inf thredhold
points.remove(0);
}
uiTable.getItems().setAll(points);
}else{
uiTable.getItems().clear();
}
function = Interpolate.class;
}else if("Categorize".equals(method)){
if(Categorize.class.isAssignableFrom(function)){
//nothing to do
return;
}else if(Interpolate.class.isAssignableFrom(function)){
//we need to convert from interpolate to categorize
final List<InterOrCategorize> points = new ArrayList<>();
points.add(new InterOrCategorize(StyleConstants.CATEGORIZE_LESS_INFINITY, TRS));
points.addAll(uiTable.getItems());
uiTable.getItems().setAll(points);
}else{
final List<InterOrCategorize> points = new ArrayList<>(uiTable.getItems());
points.add(new InterOrCategorize(StyleConstants.CATEGORIZE_LESS_INFINITY, TRS));
points.add(new InterOrCategorize(StyleConstants.LITERAL_ONE_FLOAT, TRS));
uiTable.getItems().setAll(points);
}
function = Categorize.class;
}else{
if(function == Jenks.class){
//nothing to do
return;
}else{
uiTable.getItems().clear();
}
function = Jenks.class;
}
postParse();
//ensure the NaN is set as defined
nanChange(null);
}
@FXML
private void nanChange(ActionEvent event) {
final boolean withNaN = uiNaN.isSelected();
if(function == Interpolate.class || function == Categorize.class){
final List<InterOrCategorize> points = uiTable.getItems();
for(int i=0,n=points.size();i<n;i++){
final InterOrCategorize entry = points.get(i);
final Object num = entry.value.get().evaluate(null);
if(num instanceof Number && (Double.isNaN(((Number)num).doubleValue()) || Float.isNaN(((Number)num).floatValue()))){
if(withNaN){
//color model already has a NaN
return;
}else{
//remove it
points.remove(i);
return;
}
}
}
if(withNaN){
//add NaN entry
points.add(new InterOrCategorize(getFilterFactory().literal(Float.NaN), TRS));
}
postParse();
}
}
@FXML
private void fitToData(ActionEvent event) {
if(!(layer instanceof CoverageMapLayer)) return;
final CoverageMapLayer cml = (CoverageMapLayer)layer;
final CoverageReference cref = cml.getCoverageReference();
final Double[] range = findMinMaxInMeta();
if(range!=null && range[0]!=null && range[1]!=null){
uiMinimum.valueProperty().setValue(range[0]);
uiMaximum.valueProperty().setValue(range[1]);
return;
}
GridCoverageReader reader = null;
GeneralGridGeometry gridGeometry = null;
GridCoverageReadParam readParam = null;
GridCoverage coverage = null;
GridCoverage2D coverage2D = null;
RenderedImage image = null;
try {
reader = cref.acquireReader();
gridGeometry = reader.getGridGeometry(cref.getImageIndex());
if (gridGeometry.isDefined(GeneralGridGeometry.GRID_TO_CRS)
&& gridGeometry.isDefined(GeneralGridGeometry.EXTENT)) {
MathTransform gridToCRS = gridGeometry.getGridToCRS();
GridEnvelope extent = gridGeometry.getExtent();
int dim = extent.getDimension();
double[] low = new double[dim];
double[] high = new double[dim];
low[0] = extent.getLow(0);
high[0] = extent.getHigh(0);
low[1] = extent.getLow(1);
high[1] = extent.getHigh(1);
GeneralEnvelope sliceExtent = new GeneralEnvelope(gridGeometry.getCoordinateReferenceSystem());
for (int i = 0; i < dim; i++) {
sliceExtent.setRange(i, low[i], high[i]);
}
readParam = new GridCoverageReadParam();
readParam.setEnvelope(Envelopes.transform(gridToCRS, sliceExtent));
readParam.setResolution(high[0]-low[0], high[1]-low[1]);
readParam.setCoordinateReferenceSystem(gridGeometry.getCoordinateReferenceSystem());
coverage = reader.read(cref.getImageIndex(), readParam);
coverage2D = CoverageUtilities.firstSlice(coverage);
image = coverage2D.getRenderedImage();
final Map<String, Object> an = StatisticOp.analyze(image);
final double[] minArray = (double[]) an.get(StatisticOp.MINIMUM);
final double[] maxArray = (double[]) an.get(StatisticOp.MAXIMUM);
final Integer index = uiBand.valueProperty().get().intValue();
uiMinimum.valueProperty().setValue(minArray[index]);
uiMaximum.valueProperty().setValue(maxArray[index]);
}
cref.recycle(reader);
} catch (CoverageStoreException ex) {
Loggers.JAVAFX.log(Level.WARNING, ex.getMessage(),ex);
} catch (DataStoreException ex) {
Loggers.JAVAFX.log(Level.WARNING, ex.getMessage(),ex);
} catch (TransformException ex) {
Loggers.JAVAFX.log(Level.WARNING, ex.getMessage(), ex);
}
}
/**
* Get sample dimension min and max values from coverage metadata if
* present.
*
* @return min,max array or null if metadatas do not contain the informations.
*/
private Double[] findMinMaxInMeta(){
final CoverageMapLayer cml = (CoverageMapLayer)layer;
final CoverageReference cref = cml.getCoverageReference();
final CoverageDescription covdesc = cref.getMetadata();
if(covdesc==null) return null;
final Integer index = uiBand.valueProperty().get().intValue();
//search for band statistics
search:
for(AttributeGroup attg : covdesc.getAttributeGroups()){
for(RangeDimension rd : attg.getAttributes()){
if(!(rd instanceof SampleDimension)) continue;
final int i = Integer.parseInt(rd.getSequenceIdentifier().tip().toString());
if(i==index){
final SampleDimension sd = (SampleDimension) rd;
return new Double[]{sd.getMinValue(),sd.getMaxValue()};
}
}
}
return null;
}
@FXML
private void addValue(ActionEvent event) {
uiTable.getItems().add(new InterOrCategorize());
}
@FXML
private void removeAll(ActionEvent event) {
uiTable.getItems().clear();
}
@FXML
private void generate(ActionEvent event) {
if(!(layer instanceof CoverageMapLayer)){
return;
}
uiTable.getItems().clear();
final List<InterOrCategorize> lst = new ArrayList<>();
//add the NaN if specified
if(uiNaN.isSelected()){
lst.add(new InterOrCategorize(Double.NaN, new Color(0, 0, 0, 0)));
}
boolean mustInterpolation = true;
final Object paletteValue = (Object) uiPalette.getSelectionModel().getSelectedItem();
List<Entry<Double, Color>> steps = new ArrayList<>();
if (paletteValue instanceof Palette) {
final Palette palette = (Palette) paletteValue;
steps = palette.getSteps();
} else if (paletteValue instanceof String) {
try {
final Color[] paletteColors = FXUtilities.PF.getColors(String.valueOf(paletteValue));
final double stepValue = 1.0f/(paletteColors.length-1);
for (int i = 0; i < paletteColors.length; i++) {
final double fragment = i * stepValue;
steps.add(new AbstractMap.SimpleEntry(fragment, paletteColors[i]));
}
} catch (IOException ex) {
Loggers.JAVAFX.log(Level.WARNING, ex.getMessage(), ex);
}
}
for(int i=0,n=steps.size();i<n;i++){
final double k = steps.get(i).getKey();
if(k < -0.01 || k > 1.01){
mustInterpolation = false;
}
}
//recalculate number of steps
final int nbStep = (Integer)uiDivision.valueProperty().get().intValue();
if(steps.size() != nbStep){
//recalculate steps
double min = steps.get(0).getKey();
double max = min;
final List<InterpolationPoint> points = new ArrayList<InterpolationPoint>();
for(int i=0;i<steps.size();i++){
points.add(new DefaultInterpolationPoint(steps.get(i).getKey(), getStyleFactory().literal(steps.get(i).getValue())));
min = Math.min(min, steps.get(i).getKey());
max = Math.max(max, steps.get(i).getKey());
}
Interpolate inter = getStyleFactory().interpolateFunction(DEFAULT_CATEGORIZE_LOOKUP, points,Method.COLOR, Mode.LINEAR, DEFAULT_FALLBACK);
//rebuild steps
steps.clear();
for(int i=0;i<nbStep;i++){
final double val = min + ( (max-min)/(nbStep-1) * i );
final Color color = inter.evaluate(val, Color.class);
steps.add(new AbstractMap.SimpleEntry(val,color));
}
}
if(uiInvert.isSelected()){
final List<Entry<Double, Color>> inverted = new ArrayList<Entry<Double, Color>>();
for(int i=0,n=steps.size();i<n;i++){
final double k = steps.get(i).getKey();
inverted.add(new AbstractMap.SimpleImmutableEntry(
k, steps.get(n-1-i).getValue()));
}
steps = inverted;
}
if(layer instanceof CoverageMapLayer){
final CoverageMapLayer cml = (CoverageMapLayer)layer;
try {
if(mustInterpolation){
double min = uiMinimum.valueProperty().get().doubleValue();
double max = uiMaximum.valueProperty().get().doubleValue();
lst.addAll(getInterpolationPoints(min, max, steps));
}else{
for(int s=0,l=steps.size();s<l;s++){
final Entry<Double, Color> step = steps.get(s);
lst.add(new InterOrCategorize(step.getKey(), step.getValue()));
}
}
} catch (CoverageStoreException ex) {
Loggers.JAVAFX.log(Level.INFO, ex.getMessage(),ex);
}
}
final String method = uiMethod.getSelectionModel().getSelectedItem();
if("Categorize".equals(method)){
if(!lst.isEmpty()){
final InterOrCategorize ioc = lst.get(0);
if(!StyleConstants.CATEGORIZE_LESS_INFINITY.equals(ioc.value.getValue())){
//first category must contains -inf
lst.add(new InterOrCategorize(StyleConstants.CATEGORIZE_LESS_INFINITY, TRS));
}
}
}
uiTable.getItems().setAll(lst);
updateColumns();
updateColorMapValue();
}
private void updateColorMapValue(){
final ObservableList<InterOrCategorize> lst = uiTable.getItems();
final String method = uiMethod.getSelectionModel().getSelectedItem();
if("Interpolate".equals(method)){
final List<InterpolationPoint> points = new ArrayList<>();
for(InterOrCategorize ioc : lst){
points.add(GO2Utilities.STYLE_FACTORY.interpolationPoint(
ioc.value.get().evaluate(null, Number.class),
ioc.color.get()));
}
final Interpolate fct = GO2Utilities.STYLE_FACTORY.interpolateFunction(DEFAULT_CATEGORIZE_LOOKUP, new ArrayList(points),
Method.COLOR, Mode.LINEAR, DEFAULT_FALLBACK);
valueProperty().set(GO2Utilities.STYLE_FACTORY.colorMap(fct));
}else if("Categorize".equals(method)){
final Expression lookup = DEFAULT_CATEGORIZE_LOOKUP;
final Literal fallback = DEFAULT_FALLBACK;
final Map<Expression,Expression> map = new HashMap<>();
for(InterOrCategorize ioc : lst){
map.put(ioc.value.get(), ioc.color.get());
}
final Categorize fct = GO2Utilities.STYLE_FACTORY.categorizeFunction(lookup, map, ThreshholdsBelongTo.PRECEDING, fallback);
valueProperty().set(GO2Utilities.STYLE_FACTORY.colorMap(fct));
}
}
private void updateColumns(){
final String method = uiMethod.getSelectionModel().getSelectedItem();
uiTable.getColumns().clear();
if("Interpolate".equals(method)){
value1Col.setText(GeotkFX.getString(this, "val"));
uiTable.getColumns().add(value1Col);
uiTable.getColumns().add(colorCol);
}else if("Categorize".equals(method)){
value1Col.setText(GeotkFX.getString(FXColorMap.class, "valmin"));
uiTable.getColumns().add(value1Col);
uiTable.getColumns().add(value2Col);
uiTable.getColumns().add(colorCol);
}
}
public int getSelectedBand(){
return ((Number)uiBand.getSpinner().valueProperty().get()).intValue();
}
private List<InterOrCategorize> getInterpolationPoints(final double min, final double max,
List<Entry<Double, Color>> steps) throws CoverageStoreException {
final List<InterOrCategorize> lsts = new ArrayList<>();
for(int s=0,l=steps.size();s<l;s++){
final Entry<Double, Color> step = steps.get(s);
lsts.add(new InterOrCategorize(min + (step.getKey()*(max-min)), step.getValue()));
}
return lsts;
}
@Override
public Class<ColorMap> getEditedClass() {
return ColorMap.class;
}
@Override
public ColorMap newValue() {
return StyleConstants.DEFAULT_RASTER_COLORMAP;
}
@Override
protected void updateEditor(ColorMap target) {
uiTable.getItems().clear();
if(target!=null && target.getFunction()!=null){
function = target.getFunction().getClass();
if(Interpolate.class.isAssignableFrom(function)){
final List<InterpolationPoint> points = ((Interpolate)target.getFunction()).getInterpolationPoints();
uiTable.getItems().setAll(toInterOrCategorize(points));
}else if(Categorize.class.isAssignableFrom(function)){
final Map<Expression,Expression> th = ((Categorize)target.getFunction()).getThresholds();
uiTable.getItems().setAll(toInterOrCategorize(th.entrySet()));
}else if(Jenks.class.isAssignableFrom(function)){
final Jenks jenks = (Jenks) target.getFunction();
uiTable.getItems().clear();
final String paletteName = jenks.getPalette().evaluate(null, String.class);
if (paletteName != null) {
uiPalette.getSelectionModel().select(paletteName);
} else {
uiPalette.getSelectionModel().select(0);
}
uiDivision.valueProperty().set(jenks.getClassNumber().evaluate(null, Integer.class));
}else{
function = Interpolate.class;
uiTable.getItems().clear();
Loggers.JAVAFX.log(Level.WARNING, "Unknowned colormap function : {0}", function);
}
}else{
//create an empty interpolate colormodel
function = Interpolate.class;
uiTable.getItems().clear();
}
postParse();
}
private static List<InterOrCategorize> toInterOrCategorize(Collection cdts){
final List<InterOrCategorize> lst = new ArrayList<>();
for(Object obj : cdts){
if(obj instanceof InterpolationPoint){
final InterpolationPoint ip = (InterpolationPoint) obj;
lst.add(new InterOrCategorize(getFilterFactory().literal(ip.getData()), ip.getValue()));
}else if(obj instanceof Entry){
final Entry<Expression,Expression> step = (Entry<Expression,Expression>) obj;
lst.add(new InterOrCategorize(step.getKey(), step.getValue()));
}
}
return lst;
}
private void postParse(){
uiInvert.setDisable(false);
uiMinimum.getSpinner().setEditable(true);
uiMaximum.getSpinner().setEditable(true);
if(Interpolate.class.isAssignableFrom(function)){
uiMethod.getSelectionModel().select("Interpolate");
uiPalette.setItems(FXCollections.observableList(FXUtilities.PALETTES));
value2Col.setVisible(false);
final List<InterOrCategorize> ips = uiTable.getItems();
//restore NaN and min/max values
boolean hasNaN = false;
double min = Double.NaN;
double max = Double.NaN;
for(int i=0,n=ips.size();i<n;i++){
final Double v = ips.get(i).value.get().evaluate(null, Double.class);
if(v!=null && !Double.isNaN(v)){
min = Double.isNaN(min) ? v : Math.min(v, min);
max = Double.isNaN(max) ? v : Math.max(v, max);
}else{
hasNaN = true;
}
}
if(!Double.isNaN(min)){
uiMinimum.valueProperty().set(min);
uiMaximum.valueProperty().set(max);
}
uiDivision.valueProperty().set(ips.size()-(hasNaN?1:0));
uiNaN.setSelected(hasNaN);
}else if(Categorize.class.isAssignableFrom(function)){
uiMethod.getSelectionModel().select("Categorize");
uiPalette.setItems(FXCollections.observableList(FXUtilities.PALETTES));
value2Col.setVisible(true);
final List<InterOrCategorize> ips = uiTable.getItems();
//restore NaN and min/max values
boolean hasNaN = false;
double min = Double.NaN;
double max = Double.NaN;
for(int i=0,n=ips.size();i<n;i++){
final InterOrCategorize exps = ips.get(i);
Object val = exps.value.get().evaluate(n, Number.class);
if(val instanceof Number){
final double v = ((Number)val).doubleValue();
if(!Double.isNaN(v)){
if(!Double.isInfinite(v)){
min = Double.isNaN(min) ? v : Math.min(v, min);
max = Double.isNaN(max) ? v : Math.max(v, max);
}
}else{
hasNaN = true;
}
}
}
if(!Double.isNaN(min)){
uiMinimum.valueProperty().set(min);
uiMaximum.valueProperty().set(max);
}
uiDivision.valueProperty().set(ips.size()-(hasNaN?2:1));
uiNaN.setSelected(hasNaN);
}else if(Jenks.class.isAssignableFrom(function)){
uiMethod.getSelectionModel().select("Jenks");
uiPalette.setItems(FXCollections.observableList(FXUtilities.PALETTES_NAMED));
uiInvert.setDisable(true);
uiMinimum.setDisable(true);
uiMaximum.setDisable(true);
}
//disable and hide value table for jenks method
final boolean isJenks = (function == Jenks.class);
uiAddOne.setVisible(!isJenks);
uiRemoveAll.setVisible(!isJenks);
uiTable.setVisible(!isJenks);
uiDynamic.setVisible(isJenks);
//uiNoData.setVisible(function instanceof Jenks);
//noDataContainer.setVisible(function instanceof Jenks);
final boolean da = !(layer instanceof CoverageMapLayer);
uiPalette.setDisable(da);
uiPaletteLbl.setDisable(da);
uiBand.setDisable(da);
uiBandLbl.setDisable(da);
uiNaN.setDisable(da);
uiInvert.setDisable(da);
uiGenerate.setDisable(da);
uiDivision.setDisable(da);
uiDivisionLbl.setDisable(da);
updateColumns();
}
private void initBandSpinner() {
//update nbBands spinner
try {
if (layer instanceof CoverageMapLayer) {
final CoverageReference covRef = ((CoverageMapLayer) layer).getCoverageReference();
final GridCoverageReader reader = covRef.acquireReader();
final GeneralGridGeometry gridGeometry = reader.getGridGeometry(covRef.getImageIndex());
if (gridGeometry.isDefined(GeneralGridGeometry.GRID_TO_CRS)
&& gridGeometry.isDefined(GeneralGridGeometry.EXTENT)) {
MathTransform gridToCRS = gridGeometry.getGridToCRS();
GridEnvelope extent = gridGeometry.getExtent();
int dim = extent.getDimension();
double[] low = new double[dim];
double[] high = new double[dim];
low[0] = extent.getLow(0);
high[0] = extent.getHigh(0);
low[1] = extent.getLow(1);
high[1] = extent.getHigh(1);
GeneralEnvelope sliceExtent = new GeneralEnvelope(gridGeometry.getCoordinateReferenceSystem());
final double[] res = new double[dim];
for (int i = 0; i < dim; i++) {
sliceExtent.setRange(i, low[i], high[i]);
res[i] = Double.MAX_VALUE;
}
GridCoverageReadParam readParam = new GridCoverageReadParam();
readParam.setEnvelope(Envelopes.transform(gridToCRS, sliceExtent));
readParam.setCoordinateReferenceSystem(gridGeometry.getCoordinateReferenceSystem());
readParam.setResolution(res);
final GridCoverage coverage = reader.read(covRef.getImageIndex(), readParam);
final int nbBands = coverage.getNumSampleDimensions() - 1;
uiBand.getSpinner().setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(0, nbBands, 0, 1));
}
covRef.recycle(reader);
}
} catch (CoverageStoreException ex) {
Loggers.JAVAFX.log(Level.WARNING, ex.getMessage(), ex);
} catch (DataStoreException ex) {
Loggers.JAVAFX.log(Level.WARNING, ex.getMessage(), ex);
} catch (TransformException ex) {
Loggers.JAVAFX.log(Level.WARNING, ex.getMessage(), ex);
}
}
@Override
public void setLayer(MapLayer layer) {
super.setLayer(layer);
initBandSpinner();
}
@Override
public void initialize() {
super.initialize();
value1Col = new Value1Column();
value2Col = new Value2Column();
colorCol = new ColorColumn();
uiNoData.setVisible(false);
uiDynamic.setVisible(false);
uiDivision.getSpinner().setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(0, Integer.MAX_VALUE, 10, 1));
uiBand.getSpinner().setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(0, Integer.MAX_VALUE, 0, 1));
uiMinimum.valueProperty().set(0.0);
uiMaximum.valueProperty().set(1.0);
uiMinimum.getSpinner().setValueFactory(new SpinnerValueFactory.DoubleSpinnerValueFactory(Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, 0, 1));
uiMaximum.getSpinner().setValueFactory(new SpinnerValueFactory.DoubleSpinnerValueFactory(Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, 0, 1));
uiNaN.setSelected(true);
uiPalette.setItems(FXCollections.observableList(FXUtilities.PALETTES));
uiPalette.setCellFactory((ListView<Object> param) -> new FXPaletteCell());
uiPalette.setButtonCell((new FXPaletteCell()));
if(!uiPalette.getItems().isEmpty()){
uiPalette.getSelectionModel().select(0);
}
final List<String> methods = new ArrayList<>();
methods.add("Interpolate");
methods.add("Categorize");
//methods.add(Jenks.class); //TODO
uiMethod.setItems(FXCollections.observableList(methods));
function = Interpolate.class;
uiTable.setItems(FXCollections.observableArrayList());
uiTable.itemsProperty().addListener((ObservableValue<? extends ObservableList<InterOrCategorize>> observable, ObservableList<InterOrCategorize> oldValue, ObservableList<InterOrCategorize> newValue) -> {
valueProperty().set(buildColorMap());
});
uiTable.setEditable(true);
uiMethod.getSelectionModel().select("Interpolate");
updateColumns();
}
private ColorMap buildColorMap(){
return null;
}
public InterOrCategorize getNext(InterOrCategorize current){
final ObservableList<InterOrCategorize> items = uiTable.getItems();
for(int i=0,n=items.size();i<n;i++){
if(items.get(i) == current){
if(i<n-1){
return items.get(i+1);
}else{
return null;
}
}
}
return null;
}
private static class InterOrCategorize {
private final ObjectProperty<Expression> value = new SimpleObjectProperty<>();
private final ObjectProperty<Expression> color = new SimpleObjectProperty<>();
public InterOrCategorize() {
}
public InterOrCategorize(Double value, Color color) {
this.value.set(getFilterFactory().literal(value));
this.color.set(getStyleFactory().literal(color));
}
public InterOrCategorize(Expression value, Expression color) {
this.value.set(value);
this.color.set(color);
}
public InterOrCategorize(Map.Entry<Expression,Expression> entry) {
this.value.set(entry.getKey());
this.value.set(entry.getValue());
}
}
private class Value1Column extends TableColumn<InterOrCategorize, Expression>{
public Value1Column() {
setText(GeotkFX.getString(FXColorMap.class, "valmin"));
setEditable(true);
setCellValueFactory((CellDataFeatures<InterOrCategorize, Expression> param) -> (ObservableValue)param.getValue().value);
setCellFactory(new Callback<TableColumn<InterOrCategorize, Expression>, TableCell<InterOrCategorize, Expression>>() {
@Override
public TableCell<InterOrCategorize, Expression> call(TableColumn<InterOrCategorize, Expression> param) {
return new FXValueExpressionCell();
}
});
}
}
private class Value2Column extends TableColumn<InterOrCategorize, Expression>{
public Value2Column() {
setText(GeotkFX.getString(FXColorMap.class, "valmax"));
setEditable(false);
setCellValueFactory(new Callback<CellDataFeatures<InterOrCategorize, Expression>, ObservableValue<Expression>>() {
public ObservableValue<Expression> call(TableColumn.CellDataFeatures<InterOrCategorize, Expression> param) {
final InterOrCategorize ioc = param.getValue();
final InterOrCategorize next = getNext(ioc);
if(next!=null){
return (ObservableValue)next.value;
}else{
return new SimpleObjectProperty<>(GO2Utilities.FILTER_FACTORY.literal(Double.POSITIVE_INFINITY));
}
}
});
setCellFactory(new Callback<TableColumn<InterOrCategorize, Expression>, TableCell<InterOrCategorize, Expression>>() {
@Override
public TableCell<InterOrCategorize, Expression> call(TableColumn<InterOrCategorize, Expression> param) {
return new FXValueExpressionCell();
}
});
}
}
private static class ColorColumn extends TableColumn<InterOrCategorize, Expression>{
public ColorColumn() {
setText(GeotkFX.getString(FXColorMap.class, "color"));
setMinWidth(120);
setMaxWidth(120);
setPrefWidth(120);
setResizable(false);
setEditable(false);
setCellValueFactory((TableColumn.CellDataFeatures<InterOrCategorize, Expression> param) -> (ObservableValue)param.getValue().color);
setCellFactory(new Callback<TableColumn<InterOrCategorize, Expression>, TableCell<FXColorMap.InterOrCategorize, Expression>>() {
@Override
public TableCell<InterOrCategorize, Expression> call(TableColumn<InterOrCategorize, Expression> param) {
return new TableCell<InterOrCategorize,Expression>(){
private final BorderPane pane = new BorderPane();
{
pane.setBorder(Border.EMPTY);
pane.setPadding(Insets.EMPTY);
setBorder(Border.EMPTY);
setPadding(Insets.EMPTY);
}
@Override
protected void updateItem(Expression item, boolean empty) {
super.updateItem(item, empty);
if(!empty && item!=null){
final Color color = item.evaluate(null, Color.class);
if(color!=null){
setGraphic(pane);
pane.setBackground(new Background(new BackgroundFill(FXUtilities.toFxColor(color), CornerRadii.EMPTY, Insets.EMPTY)));
}else{
setGraphic(null);
}
}else{
setGraphic(null);
}
}
};
}
});
}
}
private class FXValueExpressionCell extends FXTableCell<InterOrCategorize,Expression>{
private final FXNumberSpinner field = new FXNumberSpinner();
public FXValueExpressionCell() {
setGraphic(field);
setAlignment(Pos.CENTER_RIGHT);
setContentDisplay(ContentDisplay.CENTER);
field.getSpinner().setValueFactory(new SpinnerValueFactory.DoubleSpinnerValueFactory(Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, 0, 1));
}
@Override
public void terminateEdit() {
final double v = field.valueProperty().get().doubleValue();
final Expression exp;
if(Double.isInfinite(v) && v<0){
exp = StyleConstants.CATEGORIZE_LESS_INFINITY;
}else{
exp = GO2Utilities.FILTER_FACTORY.literal(v);
}
commitEdit(exp);
}
@Override
public void startEdit() {
Number value = getItem().evaluate(null,Double.class);
if(StyleConstants.CATEGORIZE_LESS_INFINITY.equals(getItem())){
value = Double.NEGATIVE_INFINITY;
}
if (value == null) {
value = 0;
}
field.valueProperty().set(value);
super.startEdit();
setText(null);
setGraphic(field);
field.getSpinner().requestFocus();
}
@Override
public void commitEdit(Expression newValue) {
itemProperty().set(newValue);
super.commitEdit(newValue);
updateItem(newValue, false);
final InterOrCategorize ioc = (InterOrCategorize) getTableRow().getItem();
ioc.value.setValue(newValue);
updateColorMapValue();
}
@Override
public void cancelEdit() {
super.cancelEdit();
updateItem(getItem(), false);
}
@Override
protected void updateItem(Expression item, boolean empty) {
super.updateItem(item, empty);
setText(null);
setGraphic(null);
if (item != null) {
Object v = item.evaluate(null,Double.class);
if(StyleConstants.CATEGORIZE_LESS_INFINITY.equals(item)){
v = Double.NEGATIVE_INFINITY;
}
if(v instanceof Double){
final String str = FORMATTER.format(((Number)v).doubleValue());
setText(str);
}
}
}
}
}