/* (c) 2014 - 2016 Open Source Geospatial Foundation - all rights reserved
* (c) 2001 - 2014 OpenPlans
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
package org.geoserver.wms.dimension.impl;
import java.io.IOException;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.TreeSet;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.geoserver.catalog.CoverageInfo;
import org.geoserver.catalog.DimensionInfo;
import org.geoserver.catalog.ResourceInfo;
import org.geoserver.catalog.util.ReaderDimensionsAccessor;
import org.geoserver.platform.ServiceException;
import org.geoserver.wms.dimension.AbstractDefaultValueSelectionStrategy;
import org.geotools.coverage.grid.io.GridCoverage2DReader;
import org.geotools.feature.type.DateUtil;
import org.geotools.util.Converters;
import org.geotools.util.DateRange;
import org.geotools.util.NumberRange;
import org.geotools.util.logging.Logging;
/**
* Default implementation for selecting the default values for dimensions of
* coverage (raster) resources using the nearest-domain-value-to-the-reference-value
* strategy.
*
* @author Ilkka Rinne / Spatineo Inc for the Finnish Meteorological Institute
*
*/
public class CoverageNearestValueSelectionStrategyImpl extends AbstractDefaultValueSelectionStrategy {
private static Logger LOGGER = Logging.getLogger(CoverageNearestValueSelectionStrategyImpl.class);
private Object toMatch;
private String fixedCapabilitiesValue;
/**
* Default constructor.
*/
public CoverageNearestValueSelectionStrategyImpl(Object toMatch) {
this(toMatch,null);
}
public CoverageNearestValueSelectionStrategyImpl(Object toMatch, String capabilitiesValue) {
this.toMatch = toMatch;
this.fixedCapabilitiesValue = capabilitiesValue;
}
@Override
public Object getDefaultValue(ResourceInfo resource, String dimensionName,
DimensionInfo dimension, Class clz) {
Object retval = null;
try {
GridCoverage2DReader reader = (GridCoverage2DReader) ((CoverageInfo) resource)
.getGridCoverageReader(null, null);
ReaderDimensionsAccessor dimAccessor = new ReaderDimensionsAccessor(reader);
if (dimensionName.equals(ResourceInfo.TIME)) {
Date dateToMatch = null;
if (this.toMatch instanceof Date) {
dateToMatch = (Date) this.toMatch;
} else if (this.toMatch instanceof Long) {
// Assume millis time if reference value is given as Long:
dateToMatch = new Date(((Long) this.toMatch).longValue());
} else {
try {
dateToMatch = new Date(DateUtil.parseDateTime(this.toMatch.toString()));
} catch (IllegalArgumentException e) {
throw new ServiceException(
"Error parsing value to match against while trying to find the default time value for the layer "
+ resource.getName(), e);
}
}
retval = findNearestTime(dimAccessor, dateToMatch);
} else if (dimensionName.equals(ResourceInfo.ELEVATION)) {
if (this.toMatch instanceof Number){
Double doubleToMatch = ((Number)this.toMatch).doubleValue();
retval = findNearestElevation(dimAccessor, doubleToMatch);
}
else {
throw new ServiceException(
"The default value for elevation dimension is not a number. Cannot find a default elevation value for the layer " + resource.getName());
}
} else if (dimensionName.startsWith(ResourceInfo.CUSTOM_DIMENSION_PREFIX)){
retval = findNearestCustomDimensionValue(dimensionName.substring(ResourceInfo.CUSTOM_DIMENSION_PREFIX.length()), dimAccessor, this.toMatch.toString());
}
} catch (IOException e) {
LOGGER.log(Level.FINER, e.getMessage(), e);
}
return Converters.convert(retval, clz);
}
private Date findNearestTime(ReaderDimensionsAccessor dimAccessor, Date toMatch)
throws IOException {
Date candidate = null;
TreeSet<Object> timeDomain = dimAccessor.getTimeDomain();
long shortestDistance = Long.MAX_VALUE;
long currentDistance = 0;
for (Object dateOrRange : timeDomain) {
if (dateOrRange instanceof Date) {
Date d = (Date) dateOrRange;
if (d.before(toMatch)) {
currentDistance = toMatch.getTime() - d.getTime();
if (currentDistance < shortestDistance) {
shortestDistance = currentDistance;
candidate = d;
}
} else if (d.after(toMatch)) {
currentDistance = d.getTime() - toMatch.getTime();
if (currentDistance < shortestDistance) {
candidate = d;
}
// the distance can only grow after this
// assuming the times are in ascending order,
// so stop iterating at this point for efficiency:
break;
} else if (d.equals(toMatch)) {
candidate = d;
break;
}
} else if (dateOrRange instanceof DateRange) {
DateRange d = (DateRange) dateOrRange;
if (d.getMaxValue().before(toMatch)) {
currentDistance = toMatch.getTime() - d.getMaxValue().getTime();
if (currentDistance < shortestDistance) {
shortestDistance = currentDistance;
candidate = d.getMaxValue();
}
} else if (d.getMinValue().after(toMatch)) {
currentDistance = d.getMinValue().getTime() - toMatch.getTime();
if (currentDistance < shortestDistance) {
candidate = d.getMinValue();
}
// the distance can only grow after this
// assuming the times are in ascending order,
// so stop iterating at this point for efficiency:
break;
} else {
// we are within this range, "match" will do:
candidate = toMatch;
break;
}
}
}
return candidate;
}
@SuppressWarnings("unchecked")
private Double findNearestElevation(ReaderDimensionsAccessor dimAccessor, Double toMatch)
throws IOException {
Double candidate = null;
TreeSet<Object> elevDomain = dimAccessor.getElevationDomain();
double shortestDistance = Double.MAX_VALUE;
double currentDistance = 0d;
for (Object doubleOrRange : elevDomain) {
if (doubleOrRange instanceof Double) {
Double d = (Double) doubleOrRange;
int comp = d.compareTo(toMatch);
if (comp < 0) {
currentDistance = toMatch.doubleValue() - d.doubleValue();
if (currentDistance < shortestDistance) {
shortestDistance = currentDistance;
candidate = d;
}
} else if (comp > 0) {
currentDistance = d.doubleValue() - toMatch.doubleValue();
if (currentDistance < shortestDistance) {
candidate = d;
}
// the distance can only grow after this
// assuming the times are in ascending order,
// so stop iterating at this point for efficiency:
break;
} else {
candidate = d;
break;
}
} else if (doubleOrRange instanceof NumberRange<?>) {
NumberRange<Double> d = null;
NumberRange<?> maybeD = (NumberRange<?>) doubleOrRange;
if (maybeD.getElementClass().equals(Double.class)) {
d = (NumberRange<Double>) maybeD;
} else {
d = maybeD.castTo(Double.class);
}
if (d.getMaxValue().doubleValue() < toMatch.doubleValue()) {
currentDistance = toMatch.doubleValue() - d.getMaxValue().doubleValue();
if (currentDistance < shortestDistance) {
shortestDistance = currentDistance;
candidate = d.getMaxValue();
}
} else if (d.getMinValue().doubleValue() > toMatch.doubleValue()) {
currentDistance = d.getMinValue().doubleValue() - toMatch.doubleValue();
if (currentDistance < shortestDistance) {
candidate = d.getMinValue();
}
// the distance can only grow after this
// assuming the times are in ascending order,
// so stop iterating at this point for efficiency:
break;
} else {
// we are within this range, "match" will do:
candidate = toMatch;
break;
}
}
}
return candidate;
}
private String findNearestCustomDimensionValue(String dimensionName, ReaderDimensionsAccessor dimAccessor, String toMatch)
throws IOException {
String candidate = null;
List<String> domain = dimAccessor.getDomain(dimensionName);
//TODO: decide comparison strategy based on domain data type.
//Does any coverage actually return anything else that null for this:
//String type = dimAccessor.getDomainDatatype(dimensionName);
//Just use a case insensitive lexical string comparison for now:
Comparator<String> comp = String.CASE_INSENSITIVE_ORDER;
Collections.sort(domain, comp);
long shortestDistance = Long.MAX_VALUE;
long currentDistance = 0;
for (String toCompare : domain) {
int compValue = comp.compare(toCompare, toMatch);
if (compValue < 0){
currentDistance = -compValue;
if (currentDistance < shortestDistance){
shortestDistance = currentDistance;
candidate = toCompare;
}
}
else {
currentDistance = compValue;
if (currentDistance < shortestDistance){
candidate = toCompare;
// the distance can only grow after this
// assuming the values are in ascending order,
// so stop iterating at this point for efficiency:
break;
}
}
}
return candidate;
}
@Override
public String getCapabilitiesRepresentation(ResourceInfo resource, String dimensionName,
DimensionInfo dimensionInfo) {
if (fixedCapabilitiesValue != null){
return this.fixedCapabilitiesValue;
}
else {
return super.getCapabilitiesRepresentation(resource, dimensionName, dimensionInfo);
}
}
public Object getTargetValue() {
return toMatch;
}
}