/* Copyright (c) 2001 - 2007 TOPP - www.openplans.org. All rights reserved. * This code is licensed under the GPL 2.0 license, availible at the root * application directory. */ package org.geoserver.wms.capabilities; import java.io.IOException; import java.math.BigDecimal; import java.text.SimpleDateFormat; import java.util.Date; import java.util.TimeZone; import java.util.TreeSet; import java.util.logging.Level; import java.util.logging.Logger; import org.geoserver.catalog.Catalog; import org.geoserver.catalog.CoverageInfo; import org.geoserver.catalog.CoverageStoreInfo; import org.geoserver.catalog.DimensionInfo; import org.geoserver.catalog.DimensionPresentation; import org.geoserver.catalog.FeatureTypeInfo; import org.geoserver.catalog.LayerInfo; import org.geoserver.catalog.ResourceInfo; import org.geoserver.catalog.util.ReaderDimensionsAccessor; import org.geoserver.platform.ServiceException; import org.geoserver.wms.WMS; import org.geotools.coverage.grid.io.AbstractGridCoverage2DReader; import org.geotools.factory.GeoTools; import org.geotools.temporal.object.DefaultPeriodDuration; import org.geotools.util.logging.Logging; import org.xml.sax.Attributes; import org.xml.sax.helpers.AttributesImpl; /** * Helper class avoiding to duplicate the time/elevation management code between WMS 1.1 and 1.3 * * @author Andrea Aime - GeoSolutions */ abstract class DimensionHelper { static final Logger LOGGER = Logging.getLogger(DimensionHelper.class); enum Mode { WMS11, WMS13 } Mode mode; WMS wms; public DimensionHelper(Mode mode, WMS wms) { this.mode = mode; this.wms = wms; } /** * Implement to write out an element */ protected abstract void element(String element, String content); /** * Implement to write out an element */ protected abstract void element(String element, String content, Attributes atts); void handleVectorLayerDimensions(LayerInfo layer) { // do we have time and elevation? FeatureTypeInfo typeInfo = (FeatureTypeInfo) layer.getResource(); DimensionInfo timeInfo = typeInfo.getMetadata().get(ResourceInfo.TIME, DimensionInfo.class); DimensionInfo elevInfo = typeInfo.getMetadata().get(ResourceInfo.ELEVATION, DimensionInfo.class); boolean hasTime = timeInfo != null && timeInfo.isEnabled(); boolean hasElevation = elevInfo != null && elevInfo.isEnabled(); // skip if no need if (!hasTime && !hasElevation) { return; } if (mode == Mode.WMS11) { declareWMS11Dimensions(hasTime, hasElevation); } // Time dimension if (hasTime) { try { handleTimeDimensionVector(typeInfo); } catch (IOException e) { throw new RuntimeException("Failed to handle time attribute for layer " + e); } } // elevation dimension if (hasElevation) { try { handleElevationDimensionVector(typeInfo); } catch (IOException e) { throw new RuntimeException(e); } } } /** * Writes down the raster layer dimensions, if any * * @param layer * @throws RuntimeException */ void handleRasterLayerDimensions(final LayerInfo layer) throws RuntimeException { // do we have time and elevation? CoverageInfo cvInfo = (CoverageInfo) layer.getResource(); DimensionInfo timeInfo = cvInfo.getMetadata() .get(ResourceInfo.TIME, DimensionInfo.class); DimensionInfo elevInfo = cvInfo.getMetadata().get(ResourceInfo.ELEVATION, DimensionInfo.class); boolean hasTime = timeInfo != null && timeInfo.isEnabled(); boolean hasElevation = elevInfo != null && elevInfo.isEnabled(); // skip if nothing is configured if (!hasTime && !hasElevation) { return; } if (cvInfo == null) throw new ServiceException("Unable to acquire coverage resource for layer: " + layer.getName()); Catalog catalog = cvInfo.getCatalog(); if (catalog == null) throw new ServiceException("Unable to acquire catalog resource for layer: " + layer.getName()); CoverageStoreInfo csinfo = cvInfo.getStore(); if (csinfo == null) throw new ServiceException("Unable to acquire coverage store resource for layer: " + layer.getName()); AbstractGridCoverage2DReader reader = null; try { reader = (AbstractGridCoverage2DReader) catalog.getResourcePool() .getGridCoverageReader(csinfo, GeoTools.getDefaultHints()); } catch (Throwable t) { LOGGER.log(Level.SEVERE, "Unable to acquire a reader for this coverage with format: " + csinfo.getFormat().getName(), t); } if (reader == null) { throw new ServiceException("Unable to acquire a reader for this coverage with format: " + csinfo.getFormat().getName()); } ReaderDimensionsAccessor dimensions = new ReaderDimensionsAccessor(reader); if (mode == Mode.WMS11) { declareWMS11Dimensions(hasTime, hasElevation); } // timeDimension if (hasTime && dimensions.hasTime()) { handleTimeDimensionRaster(timeInfo, dimensions); } // elevationDomain if (hasElevation && dimensions.hasElevation()) { handleElevationDimensionRaster(elevInfo, dimensions); } } private void handleElevationDimensionRaster(DimensionInfo elevInfo, ReaderDimensionsAccessor dimensions) { TreeSet<Double> elevations = dimensions.getElevationDomain(); String elevationMetadata = getZDomainRepresentation(elevInfo, elevations); writeElevationDimension(elevations, elevationMetadata); } private void handleTimeDimensionRaster(DimensionInfo timeInfo, ReaderDimensionsAccessor dimension) { TreeSet<Date> temporalDomain = dimension.getTimeDomain(); String timeMetadata = getTemporalDomainRepresentation(timeInfo, temporalDomain); writeTimeDimension(timeMetadata); } /** * Writes WMS 1.1.1 conforming dimensions (WMS 1.3 squashed dimensions and extent in the same tag instead) * @param hasTime * @param hasElevation */ private void declareWMS11Dimensions(boolean hasTime, boolean hasElevation) { // we have to declare time and elevation before the extents if (hasTime) { AttributesImpl timeDim = new AttributesImpl(); timeDim.addAttribute("", "name", "name", "", "time"); timeDim.addAttribute("", "units", "units", "", "ISO8601"); element("Dimension", null, timeDim); } if (hasElevation) { AttributesImpl elevDim = new AttributesImpl(); elevDim.addAttribute("", "name", "name", "", "elevation"); elevDim.addAttribute("", "units", "units", "", "EPSG:5030"); element("Dimension", null, elevDim); } } protected String getZDomainRepresentation(DimensionInfo dimension, TreeSet<Double> values) { String elevationMetadata = null; final StringBuilder buff = new StringBuilder(); if (DimensionPresentation.LIST == dimension.getPresentation()) { for (Double val : values) { buff.append(val); buff.append(","); } elevationMetadata = buff.substring(0, buff.length() - 1).toString().replaceAll("\\[", "").replaceAll("\\]", "").replaceAll(" ", ""); } else if (DimensionPresentation.CONTINUOUS_INTERVAL == dimension.getPresentation()) { buff.append(values.first()); buff.append("/"); buff.append(values.last()); buff.append("/"); Double resolution = values.last() - values.first(); buff.append(resolution); elevationMetadata = buff.toString(); } else if (DimensionPresentation.DISCRETE_INTERVAL == dimension.getPresentation()) { buff.append(values.first()); buff.append("/"); buff.append(values.last()); buff.append("/"); BigDecimal resolution = dimension.getResolution(); if (resolution != null) { buff.append(resolution.doubleValue()); } else { if (values.size() >= 2) { int count = 2, i = 2; Double[] zPositions = new Double[count]; for (Double val : values) { zPositions[count - i--] = val; if (i == 0) break; } double span = zPositions[count - 1] - zPositions[count - 2]; buff.append(span); } else { buff.append(0.0); } } elevationMetadata = buff.toString(); } return elevationMetadata; } /** * Builds the proper presentation given the current * * @param resourceInfo * @param values * @return */ String getTemporalDomainRepresentation(DimensionInfo dimension, TreeSet<Date> values) { String timeMetadata = null; final StringBuilder buff = new StringBuilder(); final SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS"); df.setTimeZone(TimeZone.getTimeZone("UTC")); if (DimensionPresentation.LIST == dimension.getPresentation()) { for (Date date : values) { buff.append(df.format(date)).append("Z");// ZULU buff.append(","); } timeMetadata = buff.substring(0, buff.length() - 1).toString().replaceAll("\\[", "") .replaceAll("\\]", "").replaceAll(" ", ""); } else if (DimensionPresentation.CONTINUOUS_INTERVAL == dimension.getPresentation()) { buff.append(df.format(((TreeSet<Date>) values).first())).append("Z");// ZULU buff.append("/"); buff.append(df.format(((TreeSet<Date>) values).last())).append("Z");// ZULU buff.append("/"); long durationInMilliSeconds = ((TreeSet<Date>) values).last().getTime() - ((TreeSet<Date>) values).first().getTime(); buff.append(new DefaultPeriodDuration(durationInMilliSeconds).toString()); timeMetadata = buff.toString(); } else if (DimensionPresentation.DISCRETE_INTERVAL == dimension.getPresentation()) { buff.append(df.format(((TreeSet<Date>) values).first())).append("Z");// ZULU buff.append("/"); buff.append(df.format(((TreeSet<Date>) values).last())).append("Z");// ZULU buff.append("/"); final BigDecimal resolution = dimension.getResolution(); if (resolution != null) { buff.append(new DefaultPeriodDuration(resolution.longValue()).toString()); } else { if (values.size() >= 2) { int count = 2, i = 2; Date[] timePositions = new Date[count]; for (Date date : values) { timePositions[count - i--] = date; if (i == 0) break; } long durationInMilliSeconds = timePositions[count - 1].getTime() - timePositions[count - 2].getTime(); buff.append(new DefaultPeriodDuration(durationInMilliSeconds).toString()); } else { buff.append(new DefaultPeriodDuration(0).toString()); } } timeMetadata = buff.toString(); } return timeMetadata; } /** * Writes out metadata for the time dimension * * @param typeInfo * @param source * @param timeAttribute * @throws IOException */ private void handleTimeDimensionVector(FeatureTypeInfo typeInfo) throws IOException { // build the time dim representation TreeSet<Date> values = wms.getFeatureTypeTimes(typeInfo); DimensionInfo timeInfo = typeInfo.getMetadata().get(ResourceInfo.TIME, DimensionInfo.class); String timeMetadata = getTemporalDomainRepresentation(timeInfo, (TreeSet<Date>) values); writeTimeDimension(timeMetadata); } private void handleElevationDimensionVector(FeatureTypeInfo typeInfo) throws IOException { TreeSet<Double> elevations = new TreeSet<Double>(wms.getFeatureTypeElevations(typeInfo)); DimensionInfo di = typeInfo.getMetadata().get(ResourceInfo.ELEVATION, DimensionInfo.class); final String elevationMetadata = getZDomainRepresentation(di, elevations); writeElevationDimension(elevations, elevationMetadata); } private void writeTimeDimension(String timeMetadata) { AttributesImpl timeDim = new AttributesImpl(); if (mode == Mode.WMS11) { timeDim.addAttribute("", "name", "name", "", "time"); timeDim.addAttribute("", "default", "default", "", "current"); element("Extent", timeMetadata, timeDim); } else { timeDim.addAttribute("", "name", "name", "", "time"); timeDim.addAttribute("", "default", "default", "", "current"); timeDim.addAttribute("", "units", "units", "", "ISO8601"); element("Dimension", timeMetadata, timeDim); } } private void writeElevationDimension(TreeSet<Double> elevations, final String elevationMetadata) { AttributesImpl elevDim = new AttributesImpl(); if (mode == Mode.WMS11) { elevDim.addAttribute("", "name", "name", "", "elevation"); elevDim.addAttribute("", "default", "default", "", Double.toString(elevations.first())); element("Extent", elevationMetadata, elevDim); } else { elevDim.addAttribute("", "name", "name", "", "elevation"); elevDim.addAttribute("", "default", "default", "", Double.toString(elevations.first())); elevDim.addAttribute("", "units", "units", "", "EPSG:5030"); elevDim.addAttribute("", "unitSymbol", "unitSymbol", "", "m"); element("Dimension", elevationMetadata, elevDim); } } }