/**
     * Set additional dimension metadata to the DescribeCoverage element
     *
     * @param name the custom dimension name
     * @param dimension the custom dimension related {@link DimensionInfo} instance
     * @param helper the {@link WCSDimensionsHelper} instance to be used to parse domains
     * @throws IOException
     */
    private void setAdditionalDimensionMetadata(
        final String name, final DimensionInfo dimension, WCSDimensionsHelper helper)
        throws IOException {
      Utilities.ensureNonNull("helper", helper);
      final String startTag =
          initStartMetadataTag(TAG.ADDITIONAL_DIMENSION, name, dimension, helper);

      start(startTag);
      // Custom dimension only supports List presentation
      final List<String> domain = helper.getDomain(name);
      // TODO: check if we are in the list of instants case, or in the list of periods case

      // list case
      int i = 0;
      for (String item : domain) {
        Date date = WCSDimensionsValueParser.parseAsDate(item);
        if (date != null) {
          final String dimensionId = helper.getCoverageId() + "_dd_" + i;
          encodeDate(date, helper, dimensionId);
          continue;
        }

        Double number = WCSDimensionsValueParser.parseAsDouble(item);
        if (number != null) {
          element(TAG.SINGLE_VALUE, item.toString());
          continue;
        }

        NumberRange<Double> range = WCSDimensionsValueParser.parseAsDoubleRange(item);
        if (range != null) {
          encodeInterval(
              range.getMinValue().toString(), range.getMaxValue().toString(), null, null);
          continue;
        }

        // TODO: Add support for date Ranges
        if (item instanceof String) {
          element(TAG.SINGLE_VALUE, item.toString());
        }
        //                else if (item instanceof DateRange) {
        //                    final String dimensionId = helper.getCoverageId() + "_dd_" + i;
        //                    encodeDateRange((DateRange) item, helper, dimensionId);
        //                }

        // TODO: Add more cases
        i++;
      }
      end(TAG.ADDITIONAL_DIMENSION);
    }
 /**
  * Encode a Date item as a GML TimeInstant
  *
  * @param item
  * @param helper
  * @param id
  */
 private void encodeDate(final Date item, final WCSDimensionsHelper helper, final String id) {
   final AttributesImpl atts = new AttributesImpl();
   atts.addAttribute("", "gml:id", "gml:id", "", id);
   start("gml:TimeInstant", atts);
   element("gml:timePosition", helper.format(item));
   end("gml:TimeInstant");
 }
 /**
  * Initialize the metadata start tag for a custom dimension, setting dimension name, checking
  * for UOM, defaultValue, ...
  *
  * @param dimensionTag the TAG referring to type of dimension (Time, Elevation, Additional ,...)
  * @param name the name of the custom dimension
  * @param dimension the custom dimension {@link DimensionInfo} instance
  * @param helper the {@link WCSDimensionsHelper} instance used to parse default values
  * @return
  * @throws IOException
  */
 private String initStartMetadataTag(
     final String dimensionTag,
     final String name,
     final DimensionInfo dimension,
     final WCSDimensionsHelper helper)
     throws IOException {
   final String uom = dimension.getUnitSymbol();
   String defaultValue = null;
   String prolog = null;
   if (dimensionTag.equals(TAG.ADDITIONAL_DIMENSION)) {
     prolog = TAG.ADDITIONAL_DIMENSION + " name = \"" + name + "\"";
     defaultValue = helper.getDefaultValue(name);
   } else if (dimensionTag.equals(TAG.ELEVATION_DOMAIN)) {
     prolog = TAG.ELEVATION_DOMAIN;
     defaultValue = helper.getBeginElevation();
   } else if (dimensionTag.equals(TAG.TIME_DOMAIN)) {
     prolog = TAG.TIME_DOMAIN;
     defaultValue = helper.getEndTime();
   }
   return prolog
       + (uom != null ? (" uom=\"" + uom + "\"") : "")
       + (defaultValue != null ? (" default=\"" + defaultValue + "\"") : "");
 }
 /**
  * Look for additional dimensions in the dimensionsHelper and put additional domains to the
  * metadata
  *
  * @param helper
  * @throws IOException
  */
 private void handleAdditionalDimensionMetadata(final WCSDimensionsHelper helper)
     throws IOException {
   Utilities.ensureNonNull("helper", helper);
   final Map<String, DimensionInfo> additionalDimensions = helper.getAdditionalDimensions();
   final Set<String> dimensionsName = additionalDimensions.keySet();
   final Iterator<String> dimensionsIterator = dimensionsName.iterator();
   while (dimensionsIterator.hasNext()) {
     final String dimensionName = dimensionsIterator.next();
     final DimensionInfo customDimension = additionalDimensions.get(dimensionName);
     if (customDimension != null) {
       setAdditionalDimensionMetadata(dimensionName, customDimension, helper);
     }
   }
 }
    /**
     * Set the timeDomain metadata in case the dimensionsHelper instance has a timeDimension
     *
     * @param helper
     * @throws IOException
     */
    private void handleTimeMetadata(WCSDimensionsHelper helper) throws IOException {
      Utilities.ensureNonNull("helper", helper);
      final DimensionInfo timeDimension = helper.getTimeDimension();
      if (timeDimension != null) {
        start(initStartMetadataTag(TAG.TIME_DOMAIN, null, timeDimension, helper));
        final DimensionPresentation presentation = timeDimension.getPresentation();
        final String id = helper.getCoverageId();
        switch (presentation) {
          case CONTINUOUS_INTERVAL:
            encodeTimePeriod(helper.getBeginTime(), helper.getEndTime(), id + "_tp_0", null, null);
            break;
          case DISCRETE_INTERVAL:
            encodeTimePeriod(
                helper.getBeginTime(),
                helper.getEndTime(),
                id + "_tp_0",
                helper.getTimeResolutionUnit(),
                helper.getTimeResolutionValue());
            break;
          default:
            // TODO: check if we are in the list of instants case, or in the list of periods case

            // list case
            final TreeSet<Object> domain = helper.getTimeDomain();
            int i = 0;
            for (Object item : domain) {
              // gml:id is mandatory for time instant...
              if (item instanceof Date) {
                encodeDate((Date) item, helper, id + "_td_" + i);
              } else if (item instanceof DateRange) {
                encodeDateRange((DateRange) item, helper, id + "_td_" + i);
              }
              i++;
            }
            break;
        }
        end(TAG.TIME_DOMAIN);
      }
    }
    /**
     * Set the elevationDomain metadata in case the dimensionsHelper instance has an
     * elevationDimension
     *
     * @param helper
     * @throws IOException
     */
    private void handleElevationMetadata(WCSDimensionsHelper helper) throws IOException {
      // Null check has been performed in advance
      final DimensionInfo elevationDimension = helper.getElevationDimension();
      if (elevationDimension != null) {
        start(initStartMetadataTag(TAG.ELEVATION_DOMAIN, null, elevationDimension, helper));
        final DimensionPresentation presentation = elevationDimension.getPresentation();
        switch (presentation) {
            // Where _er_ means elevation range
          case CONTINUOUS_INTERVAL:
            encodeInterval(helper.getBeginElevation(), helper.getEndElevation(), null, null);
            break;
          case DISCRETE_INTERVAL:
            encodeInterval(
                helper.getBeginElevation(),
                helper.getEndElevation(),
                helper.getElevationResolutionUnit(),
                helper.getElevationResolutionValue());
            break;
          default:
            // TODO: check if we are in the list of instants case, or in the list of periods case

            // list case
            final TreeSet<Object> domain = helper.getElevationDomain();
            for (Object item : domain) {
              if (item instanceof Number) {
                element(TAG.SINGLE_VALUE, item.toString());
              } else if (item instanceof NumberRange) {
                NumberRange range = (NumberRange) item;
                encodeInterval(
                    range.getMinValue().toString(), range.getMaxValue().toString(), null, null);
              }
            }
            break;
        }
        end(TAG.ELEVATION_DOMAIN);
      }
    }
    /**
     * Encodes the boundedBy element
     *
     * <p>e.g.:
     *
     * <pre>{@code
     * <gml:boundedBy>
     *    <gml:Envelope srsName="http://www.opengis.net/def/crs/EPSG/0/4326" axisLabels="Lat Long" uomLabels="deg deg" srsDimension="2">
     *       <gml:lowerCorner>1 1</gml:lowerCorner>
     *       <gml:upperCorner>5 3</gml:upperCorner>
     *    </gml:Envelope>
     * </gml:boundedBy>
     * }</pre>
     *
     * @param ci
     * @param gc2d
     * @param ePSGCode
     * @param axisSwap
     * @param srsName
     * @param axesNames
     * @param axisLabels
     * @throws IOException
     */
    public void handleBoundedBy(
        final GeneralEnvelope envelope,
        boolean axisSwap,
        String srsName,
        String axisLabels,
        WCSDimensionsHelper dimensionHelper)
        throws IOException {
      final CoordinateReferenceSystem crs = envelope.getCoordinateReferenceSystem();
      final CoordinateSystem cs = crs.getCoordinateSystem();

      // TODO time
      String uomLabels =
          extractUoM(crs, cs.getAxis(axisSwap ? 1 : 0).getUnit())
              + " "
              + extractUoM(crs, cs.getAxis(axisSwap ? 0 : 1).getUnit());

      // time and elevation dimensions management
      boolean hasElevation = false;
      boolean hasTime = false;
      if (dimensionHelper != null) {
        if (dimensionHelper.getElevationDimension() != null) {
          uomLabels = uomLabels + " m"; // TODO: Check elevation uom
          hasElevation = true;
        }
        if (dimensionHelper.getTimeDimension() != null) {
          uomLabels = uomLabels + " s";
          hasTime = true;
        }
      }
      final int srsDimension = cs.getDimension() + (hasElevation ? 1 : 0);

      // Setting up envelope bounds (including elevation)
      final String lower =
          new StringBuilder()
              .append(envelope.getLowerCorner().getOrdinate(axisSwap ? 1 : 0))
              .append(" ")
              .append(envelope.getLowerCorner().getOrdinate(axisSwap ? 0 : 1))
              .append(hasElevation ? " " + dimensionHelper.getBeginElevation() : "")
              .toString();

      final String upper =
          new StringBuilder()
              .append(envelope.getUpperCorner().getOrdinate(axisSwap ? 1 : 0))
              .append(" ")
              .append(envelope.getUpperCorner().getOrdinate(axisSwap ? 0 : 1))
              .append(hasElevation ? " " + dimensionHelper.getEndElevation() : "")
              .toString();

      // build the fragment
      final AttributesImpl envelopeAttrs = new AttributesImpl();
      envelopeAttrs.addAttribute("", "srsName", "srsName", "", srsName);
      envelopeAttrs.addAttribute("", "axisLabels", "axisLabels", "", axisLabels);
      envelopeAttrs.addAttribute("", "uomLabels", "uomLabels", "", uomLabels);
      envelopeAttrs.addAttribute(
          "", "srsDimension", "srsDimension", "", String.valueOf(srsDimension));
      start("gml:boundedBy");
      String envelopeName;
      if (dimensionHelper != null && (hasTime || hasElevation)) {
        envelopeName = "gml:EnvelopeWithTimePeriod";
      } else {
        envelopeName = "gml:Envelope";
      }
      start(envelopeName, envelopeAttrs);

      element("gml:lowerCorner", lower);
      element("gml:upperCorner", upper);

      if (dimensionHelper != null && hasTime) {
        element("gml:beginPosition", dimensionHelper.getBeginTime());
        element("gml:endPosition", dimensionHelper.getEndTime());
      }

      end(envelopeName);
      end("gml:boundedBy");
    }
 /**
  * Encode a DateRange item as a GML TimePeriod
  *
  * @param range
  * @param helper
  * @param id
  */
 private void encodeDateRange(
     final DateRange range, final WCSDimensionsHelper helper, final String id) {
   encodeTimePeriod(
       helper.format(range.getMinValue()), helper.format(range.getMaxValue()), id, null, null);
 }