All Downloads are FREE. Search and download functionalities are using the official Maven repository.

ucar.nc2.ncml.NcMLWriter Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 1998-2018 University Corporation for Atmospheric Research/Unidata
 * See LICENSE for license information.
 */
package ucar.nc2.ncml;

import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.Sets;
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.Namespace;
import org.jdom2.output.Format;
import org.jdom2.output.LineSeparator;
import org.jdom2.output.XMLOutputter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import thredds.client.catalog.Catalog;
import ucar.ma2.Array;
import ucar.ma2.DataType;
import ucar.ma2.Index;
import ucar.ma2.IndexIterator;
import ucar.nc2.*;
import ucar.nc2.util.URLnaming;
import ucar.nc2.util.xml.Parse;

import java.io.*;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;

/**
 * Helper class to write NcML.
 *
 * @author caron
 * @author cwardgar
 * @see ucar.nc2.NetcdfFile
 * @see http://www.unidata.ucar.edu/software/netcdf/ncml/
 */
public class NcMLWriter {
  /**
   * A default namespace constructed from the NcML URI: {@code http://www.unidata.ucar.edu/namespaces/netcdf/ncml-2.2}.
   */
  // A default namespace means that we can use it without having to prepend the "ncml:" prefix to every element name.
  // thredds.client.catalog.Catalog.ncmlNS is *not* default and therefore *does* require the prefix.
  public static final Namespace ncmlDefaultNamespace = Namespace.getNamespace(Catalog.NJ22_NAMESPACE);

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

  //////////////////////////////////////// Variable-writing predicates ////////////////////////////////////////

  /** Predicate that always returns {@code false}. */
  public static final Predicate writeNoVariablesPredicate = Predicates.alwaysFalse();

  /**
   * Predicate that returns {@code true} for variables that are {@link Variable#isMetadata() metadata variables}.
   * For such variables, the data is not actually present in the file, so we must include it in the NcML.
   * It could be synthesized (i.e. generated by an IOSP) or specified in a  element of the input NcML.
   * */
  public static final Predicate writeMetadataVariablesPredicate = Variable::isMetadata;

  /**
   * Predicate that returns {@code true} for variables that are
   * {@link Variable#isCoordinateVariable() coordinate variables}.
   **/
  public static final Predicate writeCoordinateVariablesPredicate = Variable::isCoordinateVariable;

  /** Predicate that always returns {@code true}. */
  public static final Predicate writeAllVariablesPredicate = Predicates.alwaysTrue();

  /** Predicate that returns {@code true} for variables whose names are specified to the constructor. */
  public static class WriteVariablesWithNamesPredicate implements Predicate {
    private final Set variableNames;

    public WriteVariablesWithNamesPredicate(Iterable variableNames) {
      this.variableNames = Sets.newHashSet(variableNames);
    }

    @Override
    public boolean apply(Variable var) {
      return variableNames.contains(var.getFullName());
    }
  }

  //////////////////////////////////////// Instance variables ////////////////////////////////////////

  private Namespace namespace;
  private Format xmlFormat;
  private Predicate writeVariablesPredicate;

  private final XMLOutputter xmlOutputter = new XMLOutputter();

  //////////////////////////////////////// Constructors ////////////////////////////////////////

  public NcMLWriter() {
    this.namespace = ncmlDefaultNamespace;
    this.xmlFormat = Format.getPrettyFormat().setLineSeparator(LineSeparator.UNIX);
    this.writeVariablesPredicate = writeMetadataVariablesPredicate;
  }

  //////////////////////////////////////// Getters and setters ////////////////////////////////////////

  /**
   * Gets the XML namespace for the elements in the NcML. By default, it is {@link #ncmlDefaultNamespace}.
   *
   * @return  the XML namespace.
   */
  public Namespace getNamespace() {
    return namespace;
  }

  /**
   * Sets the XML namespace.
   *
   * @param namespace  the new namespace. {@code null} implies {@link Namespace#NO_NAMESPACE}.
   */
  public void setNamespace(Namespace namespace) {
    this.namespace = namespace == null ? Namespace.NO_NAMESPACE : namespace;
  }

  /**
   * Gets the object that encapsulates XML formatting options. By default, the format is
   * {@link Format#getPrettyFormat() pretty} with {@link LineSeparator#UNIX UNIX line separators}.
   *
   * @return  the XML formatting options.
   */
  public Format getXmlFormat() {
    return xmlFormat;
  }

  public void setXmlFormat(Format xmlFormat) {
    this.xmlFormat = Preconditions.checkNotNull(xmlFormat);
  }

  /**
   * Gets the predicate that will be applied to variables to determine wither their values should be written in
   * addition to their metadata. The values will be contained within a {@code } element.
   *
   * @return the predicate.
   */
  public Predicate getWriteVariablesPredicate() {
    return writeVariablesPredicate;
  }

  /**
   * Sets the predicate that will be applied to variables to determine whether their values should be written in
   * addition to their metadata. The values will be contained within a {@code } element.
   * 

* By default, the predicate will be {@link #writeMetadataVariablesPredicate}. Their could be data loss if the values * of metadata variables aren't included in the NcML, so we recommend that you always use it, possibly as part of a * compound predicate. For example, suppose you wanted to print the values of metadata and coordinate * variables. Just do: *

   * Predicate compoundPred = Predicates.or(
   *         writeMetadataVariablesPredicate,
   *         writeCoordinateVariablesPredicate);
   * ncmlWriter.setWriteVariablesPredicate(compoundPred);
   * 
* * @param predicate the predicate to apply. */ public void setWriteVariablesPredicate(Predicate predicate) { this.writeVariablesPredicate = Preconditions.checkNotNull(predicate); } //////////////////////////////////////// Writing //////////////////////////////////////// /** * Writes an NcML element to a string. * * @param elem an NcML element. * @return the string that represents the NcML document. */ public String writeToString(Element elem) { try (StringWriter writer = new StringWriter()) { writeToWriter(elem, writer); return writer.toString(); } catch (IOException e) { throw new AssertionError("CAN'T HAPPEN: StringWriter.close() is a no-op.", e); } } /** * Writes an NcML element to an output file. * * @param elem an NcML element. * @param outFile the file to write the NcML document to. * @throws IOException if there's any problem writing. */ public void writeToFile(Element elem, File outFile) throws IOException { try (OutputStream outStream = new BufferedOutputStream(new FileOutputStream(outFile, false))) { writeToStream(elem, outStream); } } /** * Writes an NcML element to an output stream. * * @param elem an NcML element. * @param outStream the stream to write the NcML document to. Will be closed at end of the method. * @throws IOException if there's any problem writing. */ public void writeToStream(Element elem, OutputStream outStream) throws IOException { try (Writer writer = new BufferedWriter(new OutputStreamWriter( new BufferedOutputStream(outStream), xmlFormat.getEncoding()))) { writeToWriter(elem, writer); } } /** * Writes an NcML element to a Writer. * * @param elem an NcML element. * @param writer the Writer to write the NcML document to. Will be closed at end of the method. * @throws IOException if there's any problem writing. */ public void writeToWriter(Element elem, Writer writer) throws IOException { xmlOutputter.setFormat(xmlFormat); elem.detach(); // In case this element had previously been added to a Document. xmlOutputter.output(new Document(elem), writer); } //////////////////////////////////////// Element creation //////////////////////////////////////// public Element makeExplicitNetcdfElement(NetcdfFile ncFile, String location) { Element netcdfElem = makeNetcdfElement(ncFile, location); netcdfElem.addContent(0, new Element("explicit", namespace)); return netcdfElem; } public Element makeNetcdfElement(NetcdfFile ncFile, String location) { Element rootElem = makeGroupElement(ncFile.getRootGroup()); // rootElem isn't just like any other group element; we must undo some of the changes made to it in writeGroup(). rootElem.setName("netcdf"); // Was "group". rootElem.removeAttribute("name"); // This attribute is not defined on the root "netcdf" element. rootElem.addNamespaceDeclaration(namespace); if (null == location) location = ncFile.getLocation(); if (null != location) { rootElem.setAttribute("location", URLnaming.canonicalizeWrite(location)); } if (null != ncFile.getId()) rootElem.setAttribute("id", ncFile.getId()); if (null != ncFile.getTitle()) rootElem.setAttribute("title", ncFile.getTitle()); return rootElem; } public Element makeGroupElement(Group group) { Element elem = new Element("group", namespace); elem.setAttribute("name", group.getShortName()); // enumTypeDef for (EnumTypedef etd : group.getEnumTypedefs()) { elem.addContent(makeEnumTypedefElement(etd)); } // dimensions for (Dimension dim : group.getDimensions()) { elem.addContent(makeDimensionElement(dim)); } // regular variables for (Variable var : group.getVariables()) { boolean showValues = writeVariablesPredicate.apply(var); elem.addContent(makeVariableElement(var, showValues)); } // nested groups for (Group g : group.getGroups()) { Element groupElem = new Element("group", namespace); groupElem.setAttribute("name", g.getShortName()); elem.addContent(makeGroupElement(g)); } // attributes for (Attribute att : group.getAttributes()) { elem.addContent(makeAttributeElement(att)); } return elem; } // enum Typedef public Element makeEnumTypedefElement(EnumTypedef etd) { Element typeElem = new Element("enumTypedef", namespace); typeElem.setAttribute("name", etd.getShortName()); typeElem.setAttribute("type", etd.getBaseType().toString()); // Use a TreeMap so that the key-value pairs are emitted in a consistent order. TreeMap map = new TreeMap<>(etd.getMap()); for (Map.Entry entry : map.entrySet()) { typeElem.addContent(new Element("enum", namespace) .setAttribute("key", Integer.toString(entry.getKey())) .addContent(entry.getValue())); } return typeElem; } // Only for shared dimensions. public Element makeDimensionElement(Dimension dim) throws IllegalArgumentException { if (!dim.isShared()) { throw new IllegalArgumentException("Cannot create private dimension: " + "in NcML, elements are always shared."); } Element dimElem = new Element("dimension", namespace); dimElem.setAttribute("name", dim.getShortName()); dimElem.setAttribute("length", Integer.toString(dim.getLength())); if (dim.isUnlimited()) dimElem.setAttribute("isUnlimited", "true"); return dimElem; } public Element makeVariableElement(Variable var, boolean showValues) { boolean isStructure = var instanceof Structure; Element varElem = new Element("variable", namespace); varElem.setAttribute("name", var.getShortName()); StringBuilder buff = new StringBuilder(); List dims = var.getDimensions(); for (int i = 0; i < dims.size(); i++) { Dimension dim = (Dimension) dims.get(i); if (i > 0) buff.append(" "); if (dim.isShared()) buff.append(dim.getShortName()); else if (dim.isVariableLength()) buff.append("*"); else buff.append(dim.getLength()); } //if (buff.length() > 0) varElem.setAttribute("shape", buff.toString()); DataType dt = var.getDataType(); if (dt != null) { varElem.setAttribute("type", dt.toString()); if (dt.isEnum()) varElem.setAttribute("typedef", var.getEnumTypedef().getShortName()); } // attributes for (Attribute att : var.getAttributes()) { varElem.addContent(makeAttributeElement(att)); } if (isStructure) { Structure s = (Structure) var; for (Variable variable : s.getVariables()) { varElem.addContent(makeVariableElement(variable, showValues)); } } else if (showValues) { try { varElem.addContent(makeValuesElement(var, true)); } catch (IOException e) { String message = String.format("Couldn't read values for %s. Omitting element.%n\t%s", var.getFullName(), e.getMessage()); log.warn(message); } } return varElem; } public Element makeAttributeElement(Attribute attribute) { Element attElem = new Element("attribute", namespace); attElem.setAttribute("name", attribute.getShortName()); DataType dt = attribute.getDataType(); if ((dt != null) && (dt != DataType.STRING)) attElem.setAttribute("type", dt.toString()); if (attribute.getLength() == 0) { //if (attribute.isUnsigned()) // attElem.setAttribute("isUnsigned", "true"); return attElem; } if (attribute.isString()) { StringBuilder buff = new StringBuilder(); for (int i = 0; i < attribute.getLength(); i++) { String sval = attribute.getStringValue(i); if (i > 0) buff.append("|"); buff.append(sval); } attElem.setAttribute("value", Parse.cleanCharacterData(buff.toString())); if (attribute.getLength() > 1) attElem.setAttribute("separator", "|"); } else { StringBuilder buff = new StringBuilder(); for (int i = 0; i < attribute.getLength(); i++) { Number val = attribute.getNumericValue(i); if (i > 0) buff.append(" "); buff.append(val.toString()); } attElem.setAttribute("value", buff.toString()); //if (attribute.isUnsigned()) // attElem.setAttribute("isUnsigned", "true"); } return attElem; } /** * Creates a {@code } element from the variable's data. * * @param variable the variable to read values from * @param allowRegular {@code true} if regular values should be represented with {@code start}, {@code increment}, * and {@code npts} attributes instead of space-separated Element text. Has no effect if the data isn't regular. * @return the {@code } element. * @throws IOException if there was an I/O error when reading the variable's data. */ public Element makeValuesElement(Variable variable, boolean allowRegular) throws IOException { Element elem = new Element("values", namespace); StringBuilder buff = new StringBuilder(); Array a = variable.read(); if (variable.getDataType() == DataType.CHAR) { char[] data = (char[]) a.getStorage(); elem.setText(new String(data)); } else if (variable.getDataType() == DataType.STRING) { elem.setAttribute("separator", "|"); int count = 0; for (IndexIterator iter = a.getIndexIterator(); iter.hasNext(); ) { if (count++ > 0) { buff.append("|"); } buff.append(iter.getObjectNext()); } elem.setText(buff.toString()); } else { //check to see if regular if (allowRegular && (a.getRank() == 1) && (a.getSize() > 2)) { Index ima = a.getIndex(); double start = a.getDouble(ima.set(0)); double incr = a.getDouble(ima.set(1)) - start; boolean isRegular = true; for (int i = 2; i < a.getSize(); i++) { double v1 = a.getDouble(ima.set(i)); double v0 = a.getDouble(ima.set(i - 1)); if (!ucar.nc2.util.Misc.nearlyEquals(v1 - v0, incr)) isRegular = false; } if (isRegular) { elem.setAttribute("start", Double.toString(start)); elem.setAttribute("increment", Double.toString(incr)); elem.setAttribute("npts", Long.toString(variable.getSize())); return elem; } } // not regular boolean isRealType = (variable.getDataType() == DataType.DOUBLE) || (variable.getDataType() == DataType.FLOAT); IndexIterator iter = a.getIndexIterator(); buff.append(isRealType ? iter.getDoubleNext() : iter.getIntNext()); while (iter.hasNext()) { buff.append(" "); buff.append(isRealType ? iter.getDoubleNext() : iter.getIntNext()); } elem.setText(buff.toString()); } // not string return elem; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy