/*
* 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.util;
import org.geotoolkit.display2d.ext.pattern.PatternSymbolizer;
import org.geotoolkit.filter.DefaultFilterFactory2;
import org.geotoolkit.gml.xml.v311.DirectPositionType;
import org.geotoolkit.gml.xml.v311.TimePositionType;
import org.geotoolkit.style.DefaultStyleFactory;
import org.geotoolkit.style.MutableStyle;
import org.geotoolkit.style.MutableStyleFactory;
import org.geotoolkit.style.function.ThreshholdsBelongTo;
import org.geotoolkit.wcs.xml.RangeSubset;
import org.geotoolkit.wcs.xml.v100.IntervalType;
import org.geotoolkit.wcs.xml.v100.TypedLiteralType;
import org.opengis.filter.FilterFactory2;
import org.opengis.filter.expression.Expression;
import org.opengis.filter.expression.Literal;
import org.opengis.metadata.extent.GeographicBoundingBox;
import org.opengis.style.FeatureTypeStyle;
import org.opengis.style.Rule;
import org.opengis.style.Symbolizer;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.SortedSet;
import java.util.TimeZone;
import org.constellation.ws.MimeType;
/**
*
* @author Johann Sorel (Geomatys)
*/
public final class WCSUtils {
private static final MutableStyleFactory SF = new DefaultStyleFactory();
private static final FilterFactory2 FF = new DefaultFilterFactory2();
/**
* The date format to match.
*/
private static final String DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss'Z'";
public static final DateFormat FORMATTER = new SimpleDateFormat(DATE_FORMAT);
static {
FORMATTER.setTimeZone(TimeZone.getTimeZone("UTC"));
}
private WCSUtils(){}
/**
* Generates a style from a list of categories to highlight.
*
* @param style The source style.
* @param categories A list of categories to highlight in the returned style.
* @return A style that highlights the categories selected.
*/
public static MutableStyle filterStyle(MutableStyle style, final RangeSubset categories) throws IllegalArgumentException {
if(style.featureTypeStyles().size() != 1) return style;
final FeatureTypeStyle fts = style.featureTypeStyles().get(0);
if(fts.rules().size() != 1) return style;
final Rule rule = fts.rules().get(0);
if(rule.symbolizers().size() != 1) return style;
final Symbolizer symbol = rule.symbolizers().get(0);
if(!(symbol instanceof PatternSymbolizer)) return style;
final PatternSymbolizer patterns = (PatternSymbolizer) symbol;
final Map<Expression,List<Symbolizer>> oldThredholds = patterns.getRanges();
final LinkedHashMap<Expression,List<Symbolizer>> newThredholds = new LinkedHashMap<>();
final List<Double[]> visibles = toRanges(categories);
double from;
double to = Double.NaN;
List<Symbolizer> symbols = null;
for(final Entry<Expression,List<Symbolizer>> entry : oldThredholds.entrySet()){
from = to;
if(entry.getKey() != null){
to = entry.getKey().evaluate(null,Double.class);
}else{
//key is null, means it's negative infinity
to = Double.NEGATIVE_INFINITY;
}
append(newThredholds, visibles, from, to, symbols);
symbols = entry.getValue();
}
from = to;
to = Double.POSITIVE_INFINITY;
append(newThredholds, visibles, from, to, symbols);
return SF.style(new PatternSymbolizer(patterns.getChannel(), newThredholds, ThreshholdsBelongTo.PRECEDING));
}
private static void append(Map<Expression,List<Symbolizer>> steps, List<Double[]> visibles,
double from, double to, List<Symbolizer> symbols){
if(Double.isNaN(from) || Double.isNaN(to) || symbols == null) return;
for(final Double[] interval : visibles){
if(interval[1] < from){
//interval is before, ignore it
}else if (interval[0] > to){
//interval is after, ignore it
}else if (interval[0] <= from && interval[1] >= to){
//interval overlaps
steps.put(toLiteral(from), symbols);
return;
}else if (interval[0] >= from && interval[1] <= to){
//interval is within
steps.put(toLiteral(interval[0]), symbols);
from = Math.nextUp(interval[1]);
continue;
}else if (interval[0] < from && interval[1] < to){
//intersect left limit
steps.put(toLiteral(from), symbols);
from = Math.nextUp(interval[1]);
continue;
}else if (interval[0] > from && interval[1] > to){
//intersect right limit
steps.put(toLiteral(to), symbols);
return;
}else{
throw new IllegalArgumentException("should not possibly happen");
//another case ?
}
}
//fill remaining range
if(from != to){
//no style for this range
steps.put(toLiteral(from), new ArrayList<Symbolizer>());
}
}
private static Literal toLiteral(Double limit){
if(limit == Double.NEGATIVE_INFINITY){
return null;
}else{
return FF.literal(limit.doubleValue());
}
}
private static List<Double[]> toRanges(final RangeSubset ranges) throws IllegalArgumentException{
if ( !(ranges instanceof org.geotoolkit.wcs.xml.v100.RangeSubsetType) ) {
return new ArrayList<>();
}
final List<Double[]> exts = new ArrayList<>();
final org.geotoolkit.wcs.xml.v100.RangeSubsetType rangeSubset =
(org.geotoolkit.wcs.xml.v100.RangeSubsetType) ranges;
final List<Object> objects = rangeSubset.getAxisSubset().get(0).getIntervalOrSingleValue();
for(Object o : objects){
if(o instanceof IntervalType){
final IntervalType t = (IntervalType) o;
final Double d1 = Double.valueOf(t.getMin().getValue());
final Double d2 = Double.valueOf(t.getMax().getValue());
exts.add( new Double[]{d1,d2} ) ;
}else if(o instanceof TypedLiteralType){
final TypedLiteralType t = (TypedLiteralType)o;
final Double d = Double.valueOf(t.getValue());
exts.add( new Double[]{d,d} ) ;
}
}
Collections.sort(exts, new Comparator<Double[]>(){
@Override
public int compare(Double[] t, Double[] t1) {
double res = t[0] - t1[0];
if(res < 0){
return -1;
}else if(res > 0){
return 1;
}else{
res = t[1] - t1[1];
if(res < 0){
return -1;
}else if(res > 0){
return 1;
}else{
return 0;
}
}
}
});
return exts;
}
/**
* Transform a geographicBoundingBox into a list of direct positions.
*
* @param inputGeoBox
* @param elevations
* @return
*/
public static List<DirectPositionType> buildPositions(final GeographicBoundingBox inputGeoBox, final SortedSet<Number> elevations) {
final List<Double> pos1 = new ArrayList<>();
pos1.add(inputGeoBox.getWestBoundLongitude());
pos1.add(inputGeoBox.getSouthBoundLatitude());
final List<Double> pos2 = new ArrayList<>();
pos2.add(inputGeoBox.getEastBoundLongitude());
pos2.add(inputGeoBox.getNorthBoundLatitude());
if (elevations != null && elevations.size() >= 2) {
pos1.add(elevations.first().doubleValue());
pos2.add(elevations.last().doubleValue());
}
final List<DirectPositionType> pos = new ArrayList<>();
pos.add(new DirectPositionType(pos1));
pos.add(new DirectPositionType(pos2));
return pos;
}
public static List<Object> formatDateList(final SortedSet<Date> dates) {
final List<Object> times = new ArrayList<>();
synchronized(FORMATTER) {
for (Date d : dates) {
times.add(new TimePositionType(FORMATTER.format(d)));
}
}
return times;
}
public static String getExtension(String mime) {
final String ext;
switch (mime) {
case "image/tiff": ext = ".tiff";break;
case "image/geotiff": ext = ".tiff";break;
case "application/x-netcdf": ext = ".nc";break;
case "text/x-matrix": ext = ".txt";break;
case "text/x-ascii-grid": ext = ".asc";break;
case MimeType.IMAGE_PNG: ext = ".png";break;
case MimeType.IMAGE_GIF: ext = ".gif";break;
case MimeType.IMAGE_BMP: ext = ".bmp";break;
case MimeType.IMAGE_JPEG: ext = ".jpg";break;
default : ext = "";
}
return ext;
}
}