petascope.wcs2.extensions.AbstractFormatExtension.java Source code

Java tutorial

Introduction

Here is the source code for petascope.wcs2.extensions.AbstractFormatExtension.java

Source

/*
 * This file is part of rasdaman community.
 *
 * Rasdaman community is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Rasdaman community 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  General Public License for more details.
 *
 * You should have received a copy of the GNU  General Public License
 * along with rasdaman community.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Copyright 2003 - 2014 Peter Baumann / rasdaman GmbH.
 *
 * For more information please see <http://www.rasdaman.org>
 * or contact Peter Baumann via <baumann@rasdaman.com>.
 */
package petascope.wcs2.extensions;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import petascope.core.CoverageMetadata;
import petascope.core.DbMetadataSource;
import petascope.exceptions.ExceptionCode;
import petascope.exceptions.PetascopeException;
import petascope.exceptions.rasdaman.RasdamanException;
import petascope.exceptions.SecoreException;
import petascope.exceptions.WCPSException;
import petascope.exceptions.WCSException;
import petascope.util.BigDecimalUtil;
import petascope.util.CrsUtil;
import petascope.util.ListUtil;
import petascope.util.Pair;
import petascope.util.StringUtil;
import petascope.util.TimeUtil;
import petascope.util.WcsUtil;
import petascope.util.XMLSymbols;
import petascope.util.ras.RasUtil;
import petascope.wcps.metadata.CellDomainElement;
import petascope.wcps.metadata.DomainElement;
import petascope.wcps.server.core.Wcps;
import petascope.wcs2.parsers.GetCoverageMetadata;
import petascope.wcs2.parsers.GetCoverageRequest;

import static petascope.wcs2.parsers.subsets.DimensionSubset.ASTERISK;

import petascope.wcs2.parsers.subsets.DimensionSlice;
import petascope.wcs2.parsers.subsets.DimensionSubset;
import petascope.wcs2.parsers.subsets.DimensionTrim;

import static petascope.wcs2.parsers.subsets.DimensionSubset.QUOTED_SUBSET;

import petascope.wcs2.parsers.GetCoverageRequest.Scaling;

/**
 * An abstract implementation of {@link FormatExtension}, which provides some
 * convenience methods to concrete implementations.
 *
 * @author <a href="mailto:d.misev@jacobs-university.de">Dimitar Misev</a>
 */
public abstract class AbstractFormatExtension implements FormatExtension {

    private static final Logger log = LoggerFactory.getLogger(AbstractFormatExtension.class);

    /**
     * Update m with the correct bounds and axes (mostly useful when there is slicing/trimming in the request)
     *
     * @param request
     * @param m
     * @param dbMeta
     * @throws PetascopeException
     * @throws SecoreException
     * @throws WCSException
     */
    protected void updateGetCoverageMetadata(GetCoverageRequest request, GetCoverageMetadata m,
            DbMetadataSource dbMeta) throws PetascopeException, SecoreException, WCSException {

        // Init variables, to be then filled scanning the request subsets.

        // Grid axis labels, and grid bounds : grid (rasdaman) order
        Map<Integer, String> axesLabels = new TreeMap<Integer, String>();
        String lowerCellDom = "";
        String upperCellDom = "";
        // Tuples of external CRS bounds : CRS order
        // Create a key-ordered map {axisLabel->dom} so that the correct order can be reconstructed automatically.
        Map<Integer, String> lowerDom = new TreeMap<Integer, String>();
        Map<Integer, String> upperDom = new TreeMap<Integer, String>();
        // Same as lowerDom/upperDom but forcing easting first, for GIS binary formats encoding.
        String lowerGisDom = "";
        String upperGisDom = "";

        // CRS need to be sliced accordingly upon dimension slicings
        String crsName;
        Set<String> slicedAxes = new HashSet<String>();

        // miscellanea
        CoverageMetadata meta = m.getMetadata();
        boolean domUpdated;
        Iterator<DomainElement> domsIt = meta.getDomainIterator();
        Iterator<CellDomainElement> cellDomsIt = meta.getCellDomainIterator();
        DomainElement domainEl;
        CellDomainElement cellDomainEl;
        List<DimensionSubset> subsList = request.getSubsets();

        // NOTE: single loop since still N=M for Petascope, being N = dim(grid) and M=dim(CRS).
        // Keep domainElement order (grid order), but need to re-order the coordinates in the tuple for lowerDom/upperDom
        while (domsIt.hasNext()) {
            // Check if one subset trims on /this/ dimension:
            // Order and quantity of subsets not necessarily coincide with domain of the coverage
            // (e.g. single subset on Y over a nD coverage)
            domUpdated = false;
            domainEl = domsIt.next();
            cellDomainEl = cellDomsIt.next();
            // Loop through each subsets in the request and check if this axis is involved
            Iterator<DimensionSubset> subsIt = subsList.iterator();
            DimensionSubset subset;
            while (subsIt.hasNext()) {
                subset = subsIt.next();
                if (subset.getDimension().equals(domainEl.getLabel())) {
                    try {
                        // Compare subset with domain borders and update
                        if (subset instanceof DimensionTrim) {

                            // Replace asterisks and fetch subset bounds
                            stars2bounds((DimensionTrim) subset, domainEl);
                            String trimLow = ((DimensionTrim) subset).getTrimLow();
                            String trimHigh = ((DimensionTrim) subset).getTrimHigh();

                            // Append axis/uom label
                            axesLabels.put(CrsUtil.getCrsAxisOrder(meta.getCrsUris(), domainEl.getLabel()),
                                    subset.getDimension());
                            // Append updated bounds
                            // TODO: if request is specified via grid coords, need a backwards transform here
                            //       {cellDomain->domain} to show domain values in the WCS response:
                            //       Crs.convertToDomainCoords()
                            if (trimLow.contains("\"")) {
                                // Convert timestamp to temporal numeric coordinate
                                String datumOrigin = domainEl.getAxisDef().getCrsDefinition().getDatumOrigin();
                                trimLow = "" + (TimeUtil.countOffsets(datumOrigin, trimLow, domainEl.getUom(), 1D)); // do not normalize by vector here: absolute time coords needed
                            }
                            String lower = new BigDecimal(
                                    Math.max(Double.parseDouble(trimLow), domainEl.getMinValue().doubleValue()))
                                            .toPlainString();
                            // align with sample space of grid points:
                            lower = WcsUtil.fitToSampleSpace(lower, domainEl, false);
                            lowerGisDom += lower + " ";
                            // The map is automatically sorted by key value (axis order in the CRS definition)
                            lowerDom.put(CrsUtil.getCrsAxisOrder(meta.getCrsUris(), domainEl.getLabel()), lower);
                            if (trimHigh.contains("\"")) {
                                // Convert timestamp to temporal numeric coordinate
                                String datumOrigin = domainEl.getAxisDef().getCrsDefinition().getDatumOrigin();
                                String stringHi = trimHigh;
                                trimHigh = ""
                                        + (TimeUtil.countOffsets(datumOrigin, stringHi, domainEl.getUom(), 1D)); // do not normalize by vector here: absolute time coords needed
                            }
                            String upper = new BigDecimal(
                                    Math.min(Double.parseDouble(trimHigh), domainEl.getMaxValue().doubleValue()))
                                            .toPlainString();
                            // align with sample space of grid points:
                            upper = WcsUtil.fitToSampleSpace(upper, domainEl, true);
                            upperGisDom += upper + " ";
                            // The map is automatically sorted by key value (axis order in the CRS definition)
                            upperDom.put(CrsUtil.getCrsAxisOrder(meta.getCrsUris(), domainEl.getLabel()), upper);
                            // Append updated pixel bounds
                            String decimalsExp = "\\.[0-9]+";
                            long[] cellDom = (CrsUtil.GRID_CRS.equals(subset.getCrs()) || // : subset=x,CRS:1(x1,x2) || subsettingCrs=CRS:1
                                    m.getCoverageType().equals(XMLSymbols.LABEL_GRID_COVERAGE)) ? new long[] { // NOTE: e.g. parseInt("10.0") throws exception: need to remove decimals.
                                            Integer.parseInt(trimLow.replaceAll(decimalsExp, "").trim()),
                                            Integer.parseInt(trimHigh.replaceAll(decimalsExp, "").trim()) } // subsets are already grid indexes
                                            : CrsUtil.convertToInternalGridIndices(m.getMetadata(), dbMeta,
                                                    domainEl.getLabel(), trimLow, !trimLow.matches(QUOTED_SUBSET),
                                                    trimHigh, !trimHigh.matches(QUOTED_SUBSET));
                            // If SCALING on this dimension, fix upperCellDom and offset vector by scale factor
                            if (request.isScaled()) {
                                // SCALING EXTENSION: geometry changes
                                Scaling scaling = request.getScaling();
                                String axisLabel = subset.getDimension();
                                if (scaling.isScaled(axisLabel)) {
                                    BigDecimal scalingFactor = ScalingExtension.computeScalingFactor(scaling,
                                            axisLabel, new BigDecimal(cellDom[0]), new BigDecimal(cellDom[1]));
                                    // update grid envelope
                                    long scaledExtent = Math.round(
                                            Math.floor((cellDom[1] - cellDom[0] + 1) / scalingFactor.floatValue()));
                                    cellDom[1] = (long) (cellDom[0] + scaledExtent - 1);
                                    // update offset vectors
                                    // [!] NOTE: do *not* use domainEl.setScalarResolution since world2pixel conversions are cached.
                                    m.setScalingFactor(axisLabel, scalingFactor);
                                }
                            }
                            // In any case, properly trim the bounds by the image extremes
                            int cellDomainElLo = cellDomainEl.getLoInt();
                            int cellDomainElHi = cellDomainEl.getHiInt();
                            lowerCellDom += (cellDomainElLo > cellDom[0]) ? cellDomainElLo + " " : cellDom[0] + " ";
                            upperCellDom += (cellDomainElHi < cellDom[1]) ? cellDomainElHi + " " : cellDom[1] + " ";

                        } else if (subset instanceof DimensionSlice) {
                            log.debug("Axis " + domainEl.getLabel()
                                    + " has been sliced: remove it from the boundedBy element and track the axis for CRS slicing.");
                            slicedAxes.add(subset.getDimension());
                        } else {
                            throw new WCSException(ExceptionCode.InternalComponentError,
                                    "Subset '" + subset + "' is not recognized as trim nor slice.");
                        }
                        // flag: if no subset has updated the bounds, then need to append the bbox value
                        domUpdated = true;
                    } catch (NumberFormatException ex) {
                        String message = "Error while casting a subset to numeric format for comparison: "
                                + ex.getMessage();
                        log.error(message);
                        throw new WCSException(ExceptionCode.InvalidRequest, message);
                    } catch (WCSException ex) {
                        throw ex;
                    }
                }
            } // END subsets iterator
            if (!domUpdated) {
                // This dimension is not involved in any subset: use bbox bounds
                axesLabels.put(CrsUtil.getCrsAxisOrder(meta.getCrsUris(), domainEl.getLabel()),
                        domainEl.getLabel());
                lowerGisDom += BigDecimalUtil.stripDecimalZeros(domainEl.getMinValue()) + " ";
                upperGisDom += BigDecimalUtil.stripDecimalZeros(domainEl.getMaxValue()) + " ";
                // The map is automatically sorted by key value (axis order in the CRS definition)
                lowerDom.put(CrsUtil.getCrsAxisOrder(meta.getCrsUris(), domainEl.getLabel()),
                        BigDecimalUtil.stripDecimalZeros(domainEl.getMinValue()).toPlainString());
                upperDom.put(CrsUtil.getCrsAxisOrder(meta.getCrsUris(), domainEl.getLabel()),
                        BigDecimalUtil.stripDecimalZeros(domainEl.getMaxValue()).toPlainString());

                // SCALING: geometry changes
                long loCellDom = cellDomainEl.getLoInt();
                long hiCellDom = cellDomainEl.getHiInt();
                Scaling scaling = request.getScaling();
                String axisLabel = domainEl.getLabel();
                if (scaling.isScaled(axisLabel)) {
                    BigDecimal scalingFactor = ScalingExtension.computeScalingFactor(scaling, axisLabel,
                            BigDecimal.valueOf(loCellDom), BigDecimal.valueOf(hiCellDom));
                    // update grid envelope
                    long scaledExtent = Math
                            .round(Math.floor((hiCellDom - loCellDom + 1) / scalingFactor.floatValue()));
                    hiCellDom = (long) (loCellDom + scaledExtent - 1);
                    // update offset vectors
                    // [!] NOTE: do *not* use domainEl.setScalarResolution since world2pixel conversions are cached.
                    m.setScalingFactor(axisLabel, scalingFactor);
                }
                if (hiCellDom < loCellDom) {
                    lowerCellDom += hiCellDom + " ";
                    upperCellDom += loCellDom + " ";
                } else {
                    lowerCellDom += loCellDom + " ";
                    upperCellDom += hiCellDom + " ";
                }
            }
        } // END domains iterator

        // Update axes labels
        m.setAxisLabels(StringUtils.join(axesLabels.values(), " "));
        // Update **pixel-domain** bounds
        m.setLow(lowerCellDom);
        m.setHigh(upperCellDom);
        // Update **domain** bounds (GIS- and CRS-induced)
        m.setDomLow(ListUtil.printList(new ArrayList<String>(lowerDom.values()), " "));
        m.setDomHigh(ListUtil.printList(new ArrayList<String>(upperDom.values()), " "));
        m.setGisDomLow(lowerGisDom);
        m.setGisDomHigh(upperGisDom);
        // Update the **CRS**
        if (!slicedAxes.isEmpty()) {
            crsName = CrsUtil.sliceAxesOut(meta.getCrsUris(), slicedAxes);
            m.setCrs(crsName);
        }

        if (request.hasRangeSubsetting()) {
            // RANGE TYPE update
            m.setRangeFields(request.getRangeSubset().getSelectedComponents());
        }
    }

    /**
     * Execute rasql query, given GetCoverage request, request metadata, and
     * format of result. Request subsets values are pre-transformed if necessary
     * (e.g. CRS reprojection, timestamps).
     *
     * @param request
     * @param covmeta
     * @param meta
     * @param format
     * @param params
     * @return (result of executing the query, axes)
     * @throws PetascopeException
     * @throws RasdamanException
     * @throws WCPSException
     * @throws WCSException
     */
    protected Pair<Object, String> executeRasqlQuery(GetCoverageRequest request, CoverageMetadata covmeta,
            DbMetadataSource meta, String format, String params)
            throws PetascopeException, RasdamanException, WCPSException, WCSException {

        //This variable is now local to the method to avoid concurrency problems
        Wcps wcps;

        try {
            wcps = new Wcps(meta);
        } catch (Exception ex) {
            throw new WCSException(ExceptionCode.InternalComponentError, "Error initializing WCPS engine", ex);
        }

        // Add double quotes in possible timestamp-based temporal subsets:
        // WCPS needs then to recognize a StringExpression from a NumericalExpression:
        // --> set quotes directly in the WCS request if timestamps want to be used (' = %27, " = %22)
        // since there are cases which can create conflict (eg 2010 is year 2010 or numeric temporal coordinate 2010?)

        // Proceed to WCPS:
        String rquery = null;
        Pair<String, String> pair;
        try {
            pair = constructWcpsQuery(request, meta, covmeta, format, params);
            rquery = RasUtil.abstractWCPSToRasql(pair.fst, wcps);
        } catch (PetascopeException ex) {
            throw new PetascopeException(ex.getExceptionCode(),
                    "Error converting WCPS query to rasql query: " + ex.getMessage(), ex);
        }

        Object res = null;
        try {
            if ("sdom".equals(format) && !rquery.contains(":")) {
                res = null;
            } else {
                res = RasUtil.executeRasqlQuery(rquery);
            }
        } catch (RasdamanException ex) {
            throw new WCSException(ExceptionCode.RasdamanRequestFailed,
                    "Error executing rasql query: " + ex.getMessage(), ex);
        }

        return Pair.of(res, pair.snd);
    }

    /**
     * Given a GetCoverage request, construct an abstract WCPS query.
     *
     * @param req    GetCoverage request
     * @param cov    coverage metadata
     * @param dbMeta
     * @param format
     * @param params
     * @return (WCPS query in abstract syntax, axes)
     * @throws WCSException
     * @throws PetascopeException
     */
    protected Pair<String, String> constructWcpsQuery(GetCoverageRequest req, DbMetadataSource dbMeta,
            CoverageMetadata cov, String format, String params) throws WCSException, PetascopeException {
        String axes = "";
        //keep a list of the axes defined in the coverage
        ArrayList<String> axesList = new ArrayList<String>();
        Iterator<DomainElement> dit = cov.getDomainIterator();
        while (dit.hasNext()) {
            String axis = dit.next().getLabel();
            axes += axis + " ";
            axesList.add(axis);
        }
        String proc = "c";

        //Process rangesubsetting based on the coverage alias
        if (req.hasRangeSubsetting()) {
            proc = RangeSubsettingExtension.processWCPSRequest(proc, req.getRangeSubset());
        }
        //End range subsetting processing

        HashMap<String, Pair<String, String>> newdim = new HashMap<String, Pair<String, String>>(); // saves the new limits of the axes after trimming or slicing

        // process subsetting operations
        for (DimensionSubset subset : req.getSubsets()) {
            String dim = subset.getDimension();
            DomainElement de = cov.getDomainByName(dim);

            //Check if the supplied axis is in the coverage axes and throw exception if not
            if (!axesList.contains(dim)) {
                throw new WCSException(ExceptionCode.InvalidAxisLabel,
                        "The axis label " + dim + " was not found in the list of available axes");
            }

            // Parametrized CRSs can have quotes and other reserved entities which break abstract WCPS queries (and XML)
            String crs = StringUtil.escapeXmlPredefinedEntities(de.getNativeCrs());

            // in-subset CRS specification (standard inconsistency CRS hanlding in KVP/XML: see OGC 12-167 change request #257)
            // accept direct internal index subsets even if no CRS extension is provided (this is not a geo-reprojection)
            if (null != subset.getCrs() && subset.getCrs().equals(CrsUtil.GRID_CRS)) {
                crs = subset.getCrs(); // replace native with grid crs
            }

            if (subset instanceof DimensionTrim) {
                DimensionTrim trim = (DimensionTrim) subset;
                proc = "trim(" + proc + ",{" + dim + ":\"" + crs + "\" (" + trim.getTrimLow() + ":"
                        + trim.getTrimHigh() + ")})";
                newdim.put(dim, new Pair(trim.getTrimLow(), trim.getTrimHigh()));

            } else if (subset instanceof DimensionSlice) {
                DimensionSlice slice = (DimensionSlice) subset;
                proc = "slice(" + proc + ",{" + dim + ":\"" + crs + "\" (" + slice.getSlicePoint() + ")})";
                newdim.put(dim, new Pair(slice.getSlicePoint(), slice.getSlicePoint()));
                log.debug("Dimension" + dim);
                log.debug(axes);
                axes = axes.replaceFirst(dim + " ?", ""); // remove axis
            }
        }

        if (req.isScaled()) {
            if (!WcsUtil.isGrid(cov.getCoverageType())) {
                throw new WCSException(ExceptionCode.InvalidCoverageType.locator(req.getCoverageId()));
            }
            Scaling scaling = req.getScaling();
            int axesNumber = 0; // for checking if all axes in the query were used
            String crs = CrsUtil.GRID_CRS; // scaling involves pixels
            proc = "scale(" + proc + ", {";
            Iterator<DomainElement> it = cov.getDomainIterator();
            Iterator<CellDomainElement> cit = cov.getCellDomainIterator();
            // Need to loop through all dimensions to set scaling dims for un-trimmed axes too
            while (it.hasNext() && cit.hasNext()) {
                DomainElement el = it.next();
                CellDomainElement cel = cit.next();
                String dim = el.getLabel();

                //FIXME: hack for ticket #823 - number 5. to be fixed with ticket #824
                /*if (el.isIrregular() && !req.isSliced(dim) && scaling.isScaled(dim)) {
                log.error("Trying to scale an irregular axis but we cannot scale coefficients' values.");
                throw new PetascopeException(ExceptionCode.UnsupportedCombination,
                        "Scaling on irregular axis is not supported.");
                }*/
                // Sliced dimensions shall not be referenced by the scaling parameters
                if (!req.isSliced(dim)) {
                    long lo = cel.getLoInt();
                    long hi = cel.getHiInt();
                    long scaledExtent;
                    if (newdim.containsKey(dim)) {
                        long[] lohi = CrsUtil.convertToInternalGridIndices(cov, dbMeta, dim, newdim.get(dim).fst,
                                req.getSubset(dim).isNumeric(), newdim.get(dim).snd,
                                req.getSubset(dim).isNumeric());
                        lo = lohi[0];
                        hi = lohi[1];
                    }
                    long hiAfterScale;
                    switch (scaling.getType()) {
                    case 1:
                        // SCALE-BY-FACTOR: divide extent by global scaling factor
                        scaledExtent = Math.round(Math.floor((hi - lo + 1) / scaling.getFactor()));

                        hiAfterScale = Math.round(Math.floor(lo + scaledExtent - 1));
                        if (lo > hiAfterScale) {
                            long temp = lo;
                            lo = hiAfterScale;
                            hiAfterScale = temp;
                        }

                        proc = proc + dim + ":\"" + crs + "\"(" + lo + ":" + hiAfterScale + "),";
                        break;
                    case 2:
                        // SCALE-AXES: divide extent by axis scaling factor
                        if (scaling.isPresentFactor(dim)) {
                            scaledExtent = Math.round(Math.floor((hi - lo + 1) / scaling.getFactor(dim)));

                            hiAfterScale = Math.round(Math.floor(lo + scaledExtent - 1));
                            if (lo > hiAfterScale) {
                                long temp = lo;
                                lo = hiAfterScale;
                                hiAfterScale = temp;
                            }
                            proc = proc + dim + ":\"" + crs + "\"(" + lo + ":" + hiAfterScale + "),";
                            axesNumber++;
                        } else {
                            if (lo > hi) {
                                long temp = lo;
                                lo = hi;
                                hi = temp;
                            }
                            proc = proc + dim + ":\"" + crs + "\"(" + lo + ":" + hi + "),";
                        }
                        break;
                    case 3:
                        // SCALE-SIZE: set extent of dimension
                        if (scaling.isPresentSize(dim)) {
                            hiAfterScale = (lo + scaling.getSize(dim) - 1);
                            if (lo > hiAfterScale) {
                                long temp = lo;
                                lo = hiAfterScale;
                                hiAfterScale = temp;
                            }
                            proc = proc + dim + ":\"" + crs + "\"(" + lo + ":" + hiAfterScale + "),";
                            axesNumber++;
                        } else {
                            if (lo > hi) {
                                long temp = lo;
                                lo = hi;
                                hi = temp;
                            }
                            proc = proc + dim + ":\"" + crs + "\"(" + lo + ":" + hi + "),";
                        }
                        break;
                    case 4:
                        // SCALE-EXTENT: set extent of dimension
                        if (scaling.isPresentExtent(dim)) {
                            proc = proc + dim + ":\"" + crs + "\"(" + scaling.getExtent(dim).fst + ":"
                                    + scaling.getExtent(dim).snd + "),";
                            axesNumber++;
                        } else {
                            if (lo > hi) {
                                long temp = lo;
                                lo = hi;
                                hi = temp;
                            }
                            proc = proc + dim + ":\"" + crs + "\"(" + lo + ":" + hi + "),";
                        }
                        break;
                    }
                }
            }
            if (axesNumber != scaling.getAxesNumber()) {
                throw new WCSException(ExceptionCode.ScaleAxisUndefined);
            }
            //TODO find out which axis was not found and add the locator to scaleFactor or scaleExtent or scaleDomain
            proc = proc.substring(0, proc.length() - 1);
            proc += "})";

        }
        log.trace(proc); // query after scaling

        if (params != null) {
            // Additional paramters (e.g. bbox/crs in case of GTiff encoding)
            // NOTE: the whole format string is eventually wrapped into quotes (see below)
            format += "\", \"" + params;
        }

        String query = "for c in (" + req.getCoverageId() + ") return encode(" + proc + ", \"" + format + "\")";
        log.debug("==========================================================");
        log.debug(query);
        log.debug("==========================================================");
        return Pair.of(query, axes.trim());
    }

    /**
     * Replaces asterisk in a trimming with real numeric bounds.
     *
     * @param trim  the input subsetting (trimming)
     * @param domEl The axis on which the subset is requested
     */
    private void stars2bounds(DimensionTrim trim, DomainElement domEl) throws WCSException {
        String lo = trim.getTrimLow();
        String hi = trim.getTrimHigh();
        try {
            if (lo.equals(ASTERISK)) {
                if (hi.matches(QUOTED_SUBSET) && TimeUtil.isValidTimestamp(hi)) {
                    // other end of interval is a timestamp: need to make a uniform subset
                    trim.setTrimLow(StringUtil.quote(TimeUtil.coordinate2timestamp(
                            domEl.getMinValue().multiply(domEl.getScalarResolution()).doubleValue(),
                            domEl.getCrsDef().getDatumOrigin(), domEl.getUom())));
                } else {
                    trim.setTrimLow(domEl.getMinValue().toPlainString());
                }
            }
            if (hi.equals(ASTERISK)) {
                if (lo.matches(QUOTED_SUBSET) && TimeUtil.isValidTimestamp(lo)) {
                    // other end of interval is a timestamp: need to make a uniform subset
                    trim.setTrimHigh(StringUtil.quote(TimeUtil.coordinate2timestamp(
                            domEl.getMaxValue().multiply(domEl.getScalarResolution()).doubleValue(),
                            domEl.getCrsDef().getDatumOrigin(), domEl.getUom())));
                } else {
                    trim.setTrimHigh(domEl.getMaxValue().toPlainString());
                }
            }
        } catch (PetascopeException ex) {
            log.debug("Error while converting asterisk to time instant equivalent.");
            throw new WCSException(ExceptionCode.InternalComponentError, ex);
        }
    }
}