/*
* Geotoolkit - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 2012, 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.wms;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.TimeZone;
import java.util.List;
import java.util.SortedSet;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.measure.Unit;
import org.opengis.geometry.Envelope;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.cs.AxisDirection;
import org.opengis.referencing.cs.CoordinateSystemAxis;
import org.opengis.util.FactoryException;
import org.apache.sis.geometry.GeneralEnvelope;
import org.apache.sis.internal.referencing.GeodeticObjectBuilder;
import org.apache.sis.referencing.crs.DefaultEngineeringCRS;
import org.apache.sis.referencing.cs.AbstractCS;
import org.apache.sis.referencing.cs.DefaultCoordinateSystemAxis;
import org.apache.sis.referencing.datum.DefaultEngineeringDatum;
import org.apache.sis.util.ArgumentChecks;
import org.apache.sis.util.logging.Logging;
import org.apache.sis.referencing.CommonCRS;
import org.geotoolkit.client.CapabilitiesException;
import org.apache.sis.referencing.CRS;
import org.geotoolkit.referencing.IdentifiedObjects;
import org.geotoolkit.referencing.cs.DiscreteReferencingFactory;
import org.geotoolkit.temporal.object.ISODateParser;
import org.geotoolkit.temporal.object.TemporalUtilities;
import org.geotoolkit.temporal.util.PeriodUtilities;
import org.geotoolkit.wms.xml.AbstractDimension;
import org.geotoolkit.wms.xml.AbstractLayer;
import org.geotoolkit.wms.xml.AbstractWMSCapabilities;
import org.geotoolkit.wms.xml.Style;
import org.apache.sis.measure.Units;
/**
* Convinient WMS methods.
*
* @author Johann Sorel (Geomatys)
* @module
*/
public final class WMSUtilities {
private static final Logger LOGGER = Logging.getLogger("org.geotoolkit.wms");
private static final SimpleDateFormat PERIOD_DATE_FORMAT = new java.text.SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
static {
PERIOD_DATE_FORMAT.setTimeZone(TimeZone.getTimeZone("GMT+0"));
}
private WMSUtilities() {}
/**
* Verify if the server supports the given {@linkplain CoordinateReferenceSystem crs}.
*
* @param server web map server
* @param layername wms layer name
* @param crs The {@linkplain CoordinateReferenceSystem crs} to test.
* @return {@code True} if the given {@linkplain CoordinateReferenceSystem crs} is present
* in the list of supported crs in the GetCapabilities response. {@code False} otherwise.
* @throws FactoryException
* @throws CapabilitiesException if failed to read capabilities or layer name does not exist in document
*/
public static boolean supportCRS(final WebMapClient server, final String layername, final CoordinateReferenceSystem crs)
throws FactoryException, CapabilitiesException {
ArgumentChecks.ensureNonNull("server", server);
ArgumentChecks.ensureNonNull("layer name", layername);
final AbstractLayer[] stack = server.getCapabilities().getLayerStackFromName(layername);
if(stack != null){
final String srid = IdentifiedObjects.lookupIdentifier(crs, true);
if(srid == null){
//current crs has no knowned id, we can ask the server for this crs
return false;
}
//start by the most accurate layer
for(int i=stack.length-1; i>=0; i--){
for (String str : stack[i].getCRS()) {
if (srid.equalsIgnoreCase(str)) {
return true;
}
}
}
}else{
throw new CapabilitiesException(
"Layer : "+layername+" could not be found in the getCapabilities. "
+ "This can be caused by an incorrect layer name "
+ "(check case-sensitivity) or a non-compliant wms serveur."
+ "WMS server:" + server.getURL());
}
return false;
}
/**
* Find the best original crs of the data in the capabilities.
*
* @param server web map server
* @param layername wms layer name
* @return
* @throws FactoryException
* @throws CapabilitiesException
*/
public static CoordinateReferenceSystem findOriginalCRS(final WebMapClient server,
final String layername) throws CapabilitiesException {
ArgumentChecks.ensureNonNull("server", server);
ArgumentChecks.ensureNonNull("layer name", layername);
final AbstractLayer[] stack = server.getCapabilities().getLayerStackFromName(layername);
if(stack != null){
//start by the most accurate layer
for(int i=stack.length-1; i>=0; i--){
for (final String srid : stack[i].getCRS()) {
//search and return the first crs that we succesfuly parsed.
try{
CoordinateReferenceSystem crs = CRS.forCode(srid);
if(crs != null){
return crs;
}
}catch(FactoryException ex){
LOGGER.log(Level.FINE, "Could not parse crs code : {0}", srid);
}
}
}
}else{
throw new CapabilitiesException(
"Layer : "+layername+" could not be found in the getCapabilities. "
+ "This can be caused by an incorrect layer name "
+ "(check case-sensitivity) or a non-compliant wms serveur.");
}
return null;
}
/**
* Search in the getCapabilities the closest date.
*
* @param server web map server
* @param layername wms layer name
* @return
*/
public static Long findClosestDate(final WebMapClient server, final String layername,
final long date) throws CapabilitiesException {
ArgumentChecks.ensureNonNull("server", server);
ArgumentChecks.ensureNonNull("layer name", layername);
final AbstractLayer layer = server.getCapabilities().getLayerFromName(layername);
if(layer != null){
for(AbstractDimension dim : layer.getAbstractDimension()){
if("time".equalsIgnoreCase(dim.getName())){
//we found the temporal dimension
final ISODateParser parser = new ISODateParser();
final String[] dates = dim.getValue().split(",");
final long d = date;
Long closest = null;
for(String str : dates){
str = str.replaceAll("\n", "");
str = str.trim();
long candidate = parser.parseToMillis(str);
if(closest == null){
closest = candidate;
}else if( Math.abs(d-candidate) < Math.abs(d-closest)){
closest = candidate;
}
}
return closest;
}
}
}else{
throw new CapabilitiesException(
"Layer : "+layername+" could not be found in the getCapabilities. "
+ "This can be caused by an incorrect layer name "
+ "(check case-sensitivity) or a non-compliant wms serveur.");
}
return null;
}
/**
* Find layer envelope
*
* @param server web map server
* @param layername wms layer name
* @return
* @throws CapabilitiesException
*/
public static Envelope findEnvelope(final WebMapClient server,
final String layername) throws CapabilitiesException {
ArgumentChecks.ensureNonNull("server", server);
ArgumentChecks.ensureNonNull("layer name", layername);
final AbstractWMSCapabilities capa = server.getCapabilities();
final AbstractLayer layer = capa.getLayerFromName(layername);
if (layer == null) {
return null;
}
GeneralEnvelope layerEnvelope = (GeneralEnvelope) layer.getEnvelope();
final List<AbstractDimension> dimensions = (List<AbstractDimension>) layer.getDimension();
if (!dimensions.isEmpty()) {
final CoordinateReferenceSystem envCRS = layerEnvelope.getCoordinateReferenceSystem();
final List<CoordinateReferenceSystem> dimensionsCRS = new ArrayList<CoordinateReferenceSystem>();
dimensionsCRS.add(envCRS);
final List<Double> lower = new ArrayList<Double>();
final List<Double> upper = new ArrayList<Double>();
lower.add(layerEnvelope.getLower(0));
lower.add(layerEnvelope.getLower(1));
upper.add(layerEnvelope.getUpper(0));
upper.add(layerEnvelope.getUpper(1));
for (final AbstractDimension dim : dimensions) {
CoordinateReferenceSystem dimCRS = null;
final String dimName = dim.getName();
final String dimUnitSymbol = dim.getUnitSymbol();
final Unit<?> unit = dimUnitSymbol != null ? Units.valueOf(dimUnitSymbol) : Units.UNITY;
//create CRS
if ("time".equals(dimName)) {
dimCRS = CommonCRS.Temporal.JAVA.crs();
} else if ("elevation".equals(dimName)) {
dimCRS = CommonCRS.Vertical.ELLIPSOIDAL.crs();
} else {
final DefaultEngineeringDatum dimDatum = new DefaultEngineeringDatum(Collections.singletonMap("name", dimName));
final CoordinateSystemAxis csAxis = new DefaultCoordinateSystemAxis(
Collections.singletonMap("name", dimName), dimName.substring(0, 1), AxisDirection.OTHER, unit);
final AbstractCS dimCs = new AbstractCS(Collections.singletonMap("name", dimName), csAxis);
dimCRS = new DefaultEngineeringCRS(Collections.singletonMap("name", dimName), dimDatum, dimCs);
}
double minVal = Double.MIN_VALUE;
double maxVal = Double.MAX_VALUE;
//extract discret values
final String dimValues = dim.getValue();
if(dimValues != null){
if (!CommonCRS.Temporal.JAVA.crs().equals(dimCRS)) {
//serie of values
final String[] dimStrArray = dimValues.split(",");
final double[] dblValues = new double[dimStrArray.length];
for (int i = 0; i < dimStrArray.length; i++) {
dblValues[i] = Double.valueOf(dimStrArray[i]).doubleValue();
}
dimCRS = DiscreteReferencingFactory.createDiscreteCRS(dimCRS, dblValues);
minVal = dblValues[0];
maxVal = dblValues[dblValues.length - 1];
}else{
//might be serie of dates or periods
final String[] dimStrArray = dimValues.split(",");
final List<Double> dblValues = new ArrayList<Double>();
for (int i = 0; i < dimStrArray.length; i++) {
final String candidate = dimStrArray[i];
//try to parse a period
synchronized(PERIOD_DATE_FORMAT){
final PeriodUtilities parser = new PeriodUtilities(PERIOD_DATE_FORMAT);
try {
final SortedSet<Date> dates = parser.getDatesFromPeriodDescription(candidate);
for(Date date : dates){
dblValues.add((double)date.getTime());
}
continue;
} catch (ParseException ex) {
Logging.getLogger("org.geotoolkit.wms").log(Level.FINER, "Value : {0} is not a period", candidate);
}
}
//try to parse a single date
try {
final Date date = TemporalUtilities.parseDate(candidate);
dblValues.add((double)date.getTime());
continue;
} catch (ParseException ex) {
Logging.getLogger("org.geotoolkit.wms").log(Level.FINER, "Value : {0} is not a date", candidate);
}
}
final double[] values = new double[dblValues.size()];
for(int i=0;i<values.length;i++){
values[i] = dblValues.get(i);
}
dimCRS = DiscreteReferencingFactory.createDiscreteCRS(dimCRS, values);
if(values.length>0){
minVal = values[0];
maxVal = values[values.length - 1];
}
}
}
//add CRS to list
if (dimCRS != null) {
dimensionsCRS.add(dimCRS);
lower.add(minVal);
upper.add(maxVal);
}
}
// build new envelope with all dimension CRSs and lower/upper ordinates.
if (!dimensionsCRS.isEmpty()) {
final CoordinateReferenceSystem outCRS;
try {
outCRS = new GeodeticObjectBuilder().addName(layer.getName())
.createCompoundCRS(dimensionsCRS.toArray(new CoordinateReferenceSystem[dimensionsCRS.size()]));
} catch (FactoryException ex) {
throw new CapabilitiesException("", ex);
}
layerEnvelope = new GeneralEnvelope(outCRS);
//build ordinate list like (xmin, ymin, zmin, xmax, ymax, zmax)
final List<Double> ordinateList = new ArrayList<Double>(lower);
ordinateList.addAll(upper);
final double[] ordinates = new double[ordinateList.size()];
for (int i = 0; i < ordinateList.size(); i++) {
ordinates[i] = ordinateList.get(i);
}
layerEnvelope.setEnvelope(ordinates);
}
}
return layerEnvelope;
}
/**
* List available styles for given layer
*
* @param server web map server
* @param layername wms layer name
* @return
* @throws CapabilitiesException
*/
public static List<? extends Style> findStyleCandidates(final WebMapClient server,
final String layername) throws CapabilitiesException{
ArgumentChecks.ensureNonNull("server", server);
ArgumentChecks.ensureNonNull("layer name", layername);
final AbstractLayer layer = server.getCapabilities().getLayerFromName(layername);
if(layer != null){
return layer.getStyle();
}
return Collections.emptyList();
}
}