QuakemlProductCreator.java

  1. package gov.usgs.earthquake.eids;

  2. import gov.usgs.earthquake.event.Converter;
  3. import gov.usgs.earthquake.product.ByteContent;
  4. import gov.usgs.earthquake.product.Content;
  5. import gov.usgs.earthquake.product.Product;
  6. import gov.usgs.earthquake.product.ProductId;
  7. import gov.usgs.earthquake.product.io.ObjectProductSource;
  8. import gov.usgs.earthquake.product.io.XmlProductHandler;
  9. import gov.usgs.earthquake.quakeml.FileToQuakemlConverter;
  10. import gov.usgs.util.StreamUtils;
  11. import gov.usgs.util.XmlUtils;

  12. import java.io.File;
  13. import java.math.BigDecimal;
  14. import java.math.BigInteger;
  15. import java.util.ArrayList;
  16. import java.util.Date;
  17. import java.util.Iterator;
  18. import java.util.List;
  19. import java.util.Map;
  20. import java.util.logging.Logger;

  21. import org.quakeml_1_2.Axis;
  22. import org.quakeml_1_2.ConfidenceEllipsoid;
  23. import org.quakeml_1_2.CreationInfo;
  24. import org.quakeml_1_2.EvaluationMode;
  25. import org.quakeml_1_2.Event;
  26. import org.quakeml_1_2.EventDescription;
  27. import org.quakeml_1_2.EventDescriptionType;
  28. import org.quakeml_1_2.EventParameters;
  29. import org.quakeml_1_2.EventType;
  30. import org.quakeml_1_2.FocalMechanism;
  31. import org.quakeml_1_2.InternalEvent;
  32. import org.quakeml_1_2.Magnitude;
  33. import org.quakeml_1_2.MomentTensor;
  34. import org.quakeml_1_2.NodalPlane;
  35. import org.quakeml_1_2.NodalPlanes;
  36. import org.quakeml_1_2.Origin;
  37. import org.quakeml_1_2.OriginQuality;
  38. import org.quakeml_1_2.OriginUncertainty;
  39. import org.quakeml_1_2.OriginUncertaintyDescription;
  40. import org.quakeml_1_2.Quakeml;
  41. import org.quakeml_1_2.RealQuantity;
  42. import org.quakeml_1_2.ScenarioEvent;
  43. import org.quakeml_1_2.SourceTimeFunction;
  44. import org.quakeml_1_2.SourceTimeFunctionType;
  45. import org.quakeml_1_2.Tensor;
  46. import org.quakeml_1_2.TimeQuantity;
  47. import org.quakeml_1_2.PrincipalAxes;
  48. import org.quakeml_1_2.EvaluationStatus;

  49. /**
  50.  * Create Products from ANSS Quakeml files.
  51.  */
  52. public class QuakemlProductCreator implements ProductCreator {

  53.     /** For use in logging issues */
  54.     public static final Logger LOGGER = Logger
  55.             .getLogger(QuakemlProductCreator.class.getName());

  56.     /** Content type for xml */
  57.     public static final String XML_CONTENT_TYPE = "application/xml";
  58.     /** Content path for quakeml */
  59.     public static final String QUAKEML_CONTENT_PATH = "quakeml.xml";
  60.     /** Contents XML path */
  61.     public static final String CONTENTS_XML_PATH = "contents.xml";

  62.     /** Var for number of meters/kilometer... */
  63.     public static final BigDecimal METERS_PER_KILOMETER = new BigDecimal("1000");

  64.     /** Version */
  65.     public static final String VERSION = "1.0";

  66.     /**
  67.      * When phases exist it is a "phase" type product. When this flag is set to
  68.      * true, a lightweight, origin-only type product (without bulky phase data)
  69.      * is also sent.
  70.      */
  71.     private boolean sendOriginWhenPhasesExist = false;
  72.     private boolean sendMechanismWhenPhasesExist = false;

  73.     // xml for the eqmessage currently being processed
  74.     private String quakemlXML;
  75.     private CreationInfo eventParametersCreationInfo;

  76.     // attributes of current quakeml being processed
  77.     private String productSource;
  78.     private String productCode;
  79.     private String eventSource;
  80.     private String eventCode;
  81.     private Date updateTime;

  82.     private FileToQuakemlConverter converter = null;
  83.     private Converter formatConverter = new Converter();
  84.     private boolean validate = false;

  85.     // default to fixing padding issues
  86.     private boolean padForBase64Bug = true;

  87.     /** Default Constructor */
  88.     public QuakemlProductCreator() {
  89.         super();
  90.     }

  91.     /**
  92.      * Constructor taking in argument for Base64 bug padding
  93.      * @param padForBase64Bug Boolean if needed to pad
  94.      */
  95.     public QuakemlProductCreator(boolean padForBase64Bug) {
  96.         this.padForBase64Bug = padForBase64Bug;
  97.     }

  98.     /**
  99.      * Gets Quakeml products with no rawQuakeml
  100.      * @param message Parsed quakeml message
  101.      * @return List of products
  102.      * @throws Exception if error occurs
  103.      */
  104.     public List<Product> getQuakemlProducts(final Quakeml message)
  105.             throws Exception {
  106.         return getQuakemlProducts(message, null);
  107.     }

  108.     /**
  109.      * Gets Quakeml products with the message as a rawQuakeml
  110.      * @param message Parsed quakeml message
  111.      * @return List of products
  112.      * @throws Exception if error occurs
  113.      */
  114.     public List<Product> getQuakemlProducts(final String message)
  115.             throws Exception {
  116.         Quakeml quakeml = formatConverter.getQuakeml(message, validate);
  117.         return getQuakemlProducts(quakeml, message);
  118.     }

  119.     /**
  120.      * Get products in a quakeml message.
  121.      *
  122.      * @param message
  123.      *            the parsed quakeml message.
  124.      * @param rawQuakeml
  125.      *            bytes of quakeml message. If null, the quakeml object will be
  126.      *            serialized into xml. This parameter is used to preserve the
  127.      *            original input, instead of always serializing from the quakeml
  128.      *            object.
  129.      * @return list of products generated from quakeml message.
  130.      * @throws Exception if error occurs
  131.      */
  132.     public List<Product> getQuakemlProducts(final Quakeml message,
  133.             final String rawQuakeml) throws Exception {
  134.         List<Product> products = new ArrayList<Product>();

  135.         // serialize for embedding in product
  136.         quakemlXML = rawQuakeml;
  137.         if (quakemlXML == null) {
  138.             quakemlXML = convertQuakemlToString(message);
  139.         } else {
  140.             quakemlXML = fixRawQuakeml(quakemlXML);
  141.         }

  142.         EventParameters eventParameters = message.getEventParameters();
  143.         eventParametersCreationInfo = eventParameters.getCreationInfo();

  144.         // only process first event
  145.         Event firstEvent = QuakemlUtils.getFirstEvent(eventParameters);
  146.         if (firstEvent != null) {
  147.             if (firstEvent instanceof InternalEvent) {
  148.                 products.addAll(getInternalEventProducts(message, (InternalEvent) firstEvent));
  149.             } else if (firstEvent instanceof ScenarioEvent) {
  150.                 products.addAll(getScenarioEventProducts(message, (ScenarioEvent) firstEvent));
  151.             } else {
  152.                 products.addAll(getEventProducts(message, firstEvent));
  153.             }
  154.         }

  155.         // add property to all products to indicate they all come from the same
  156.         // eventParameters "envelope"
  157.         String eventParametersPublicID = eventParameters.getPublicID();
  158.         Iterator<Product> productIter = products.iterator();
  159.         while (productIter.hasNext()) {
  160.             Product next = productIter.next();
  161.             setProperty(next.getProperties(), "eventParametersPublicID",
  162.                     eventParametersPublicID);
  163.         }

  164.         eventParametersCreationInfo = null;
  165.         quakemlXML = null;

  166.         return products;
  167.     }

  168.     /**
  169.      * Get internal products in quakeml event element.
  170.      *
  171.      * Calls {@link #getEventProducts(Quakeml, Event)}, and adds "internal-"
  172.      * prefix to each type in the returned list of products.
  173.      *
  174.      * @param message
  175.      *            the quakeml message.
  176.      * @param event
  177.      *            the internal event element.
  178.      * @return list of internal products found in event element, may be empty.
  179.      * @throws Exception if error occurs
  180.      */
  181.     public List<Product> getInternalEventProducts(final Quakeml message,
  182.             final InternalEvent event) throws Exception {
  183.         List<Product> products = getEventProducts(message, event);
  184.         Iterator<Product> iter = products.iterator();
  185.         while (iter.hasNext()) {
  186.             ProductId nextId = iter.next().getId();
  187.             nextId.setType("internal-" + nextId.getType());
  188.         }
  189.         return products;
  190.     }

  191.     /**
  192.      * Get scenario products in quakeml event element.
  193.      *
  194.      * Calls {@link #getEventProducts(Quakeml, Event)}, and adds "-scenario"
  195.      * suffix to each type in the returned list of products.
  196.      *
  197.      * @param message
  198.      *            the quakeml message.
  199.      * @param event
  200.      *            the scenario event element.
  201.      * @return list of scenario products found in event element, may be empty.
  202.      * @throws Exception if error occurs
  203.      */
  204.     public List<Product> getScenarioEventProducts(final Quakeml message,
  205.             final ScenarioEvent event) throws Exception {
  206.         List<Product> products = getEventProducts(message, event);
  207.         Iterator<Product> iter = products.iterator();
  208.         while (iter.hasNext()) {
  209.             ProductId nextId = iter.next().getId();
  210.             nextId.setType(nextId.getType() + "-scenario");
  211.         }
  212.         return products;
  213.     }

  214.     /**
  215.      * Get products in quakeml event element.
  216.      *
  217.      * @param message
  218.      *            the quakeml message.
  219.      * @param event
  220.      *            the event element in the quakeml message.
  221.      * @return list of products found in event element, may be empty.
  222.      * @throws Exception if error occurs
  223.      */
  224.     public List<Product> getEventProducts(final Quakeml message, Event event)
  225.             throws Exception {
  226.         List<Product> products = new ArrayList<Product>();

  227.         // read catalog attributes for product source and code, and event source
  228.         // and code
  229.         productSource = event.getDatasource();
  230.         productCode = event.getDataid();
  231.         eventSource = event.getEventsource();
  232.         eventCode = event.getEventid();

  233.         if (productSource == null || eventSource == null || eventCode == null) {
  234.             LOGGER.warning("Missing catalog attributes from event element, skipping");
  235.             // not anss information, don't convert to products
  236.             return products;
  237.         } else if (productCode == null) {
  238.             productCode = eventSource + eventCode;
  239.         }
  240.         productSource = productSource.toLowerCase();

  241.         // product update time
  242.         updateTime = null;
  243.         if (eventParametersCreationInfo != null) {
  244.             updateTime = eventParametersCreationInfo.getCreationTime();
  245.         } else {
  246.             LOGGER.warning("Missing eventParameters creationTime, using now for update time");
  247.             updateTime = new Date();
  248.         }

  249.         ByteContent quakemlContent = new ByteContent(quakemlXML.getBytes());
  250.         quakemlContent.setContentType(XML_CONTENT_TYPE);
  251.         quakemlContent.setLastModified(updateTime);

  252.         boolean hasPhaseData = QuakemlUtils.hasPhaseData(event);

  253.         // create this object, may go unused
  254.         Product originProduct = new Product(new ProductId(productSource,
  255.                 (hasPhaseData ? "phase-data" : "origin"), productCode,
  256.                 updateTime));
  257.         originProduct.setEventId(eventSource, eventCode);
  258.         if (event.getCreationInfo() != null) {
  259.             originProduct.setVersion(event.getCreationInfo().getVersion());
  260.         }
  261.         originProduct.getContents().put(QUAKEML_CONTENT_PATH, quakemlContent);
  262.         originProduct.getContents().put(CONTENTS_XML_PATH, getContentsXML());

  263.         // track which event this product is from
  264.         setProperty(originProduct.getProperties(), "quakeml-publicid",
  265.                 event.getPublicID());

  266.         // delete origin product
  267.         if (event.getType() == EventType.NOT_EXISTING) {
  268.             originProduct.setStatus(Product.STATUS_DELETE);
  269.             products.add(originProduct);

  270.             Product phaseDelete = new Product(originProduct);
  271.             phaseDelete.getId().setType("phase-data");
  272.             products.add(phaseDelete);

  273.             // don't need to delete other stuff
  274.             // when origins for event are deleted, event is deleted
  275.             return products;
  276.         }

  277.         Origin origin = QuakemlUtils.getPreferredOrigin(event);
  278.         if (origin != null) {
  279.             // track which origin this product is from
  280.             setProperty(originProduct.getProperties(), "quakeml-origin-publicid",
  281.                     origin.getPublicID());

  282.             // update origin product
  283.             Map<String, String> properties = originProduct.getProperties();

  284.             TimeQuantity eventTime = origin.getTime();
  285.             if (eventTime != null) {
  286.                 originProduct.setEventTime(eventTime.getValue());
  287.                 setProperty(properties, "eventtime-error",
  288.                         eventTime.getUncertainty());
  289.             }
  290.             RealQuantity eventLatitude = origin.getLatitude();
  291.             if (eventLatitude != null) {
  292.                 originProduct.setLatitude(eventLatitude.getValue());
  293.                 setProperty(properties, "latitude-error",
  294.                         eventLatitude.getUncertainty());
  295.             }
  296.             RealQuantity eventLongitude = origin.getLongitude();
  297.             if (eventLongitude != null) {
  298.                 originProduct.setLongitude(eventLongitude.getValue());
  299.                 setProperty(properties, "longitude-error",
  300.                         eventLongitude.getUncertainty());
  301.             }
  302.             RealQuantity depth = origin.getDepth();
  303.             if (depth != null) {
  304.                 originProduct.setDepth(depth.getValue().divide(
  305.                         METERS_PER_KILOMETER));
  306.                 if (depth.getUncertainty() != null) {
  307.                     setProperty(properties, "vertical-error", depth
  308.                             .getUncertainty().divide(METERS_PER_KILOMETER));
  309.                 }
  310.                 if (origin.getDepthType() != null) {
  311.                     setProperty(properties, "depth-type", origin.getDepthType()
  312.                             .value());
  313.                 }
  314.             }

  315.             // read horizontal error
  316.             OriginUncertainty originUncertainty = origin.getOriginUncertainty();
  317.             if (originUncertainty != null) {
  318.                 if (originUncertainty.getHorizontalUncertainty() != null) {
  319.                     setProperty(properties, "horizontal-error",
  320.                             originUncertainty.getHorizontalUncertainty()
  321.                                     .divide(METERS_PER_KILOMETER));
  322.                 } else if (originUncertainty.getPreferredDescription() == OriginUncertaintyDescription.HORIZONTAL_UNCERTAINTY) {
  323.                     throw new IllegalArgumentException(
  324.                             "Missing horizontal uncertainty value");
  325.                 }

  326.                 ConfidenceEllipsoid ellipse = originUncertainty.getConfidenceEllipsoid();
  327.                 if (ellipse != null) {
  328.                     setProperty(properties, "error-ellipse-azimuth",
  329.                             ellipse.getMajorAxisAzimuth());
  330.                     setProperty(properties, "error-ellipse-plunge",
  331.                             ellipse.getMajorAxisPlunge());
  332.                     setProperty(properties, "error-ellipse-rotation",
  333.                             ellipse.getMajorAxisRotation());
  334.                     setProperty(properties, "error-ellipse-major",
  335.                             ellipse.getSemiMajorAxisLength());
  336.                     setProperty(properties, "error-ellipse-minor",
  337.                             ellipse.getSemiMinorAxisLength());
  338.                     setProperty(properties, "error-ellipse-intermediate",
  339.                             ellipse.getSemiIntermediateAxisLength());
  340.                 }
  341.             }

  342.             EventType eventType = event.getType();
  343.             properties.put("event-type",
  344.                     ((eventType == null) ? EventType.EARTHQUAKE : eventType)
  345.                             .value());

  346.             CreationInfo originCreationInfo = origin.getCreationInfo();
  347.             if (originCreationInfo != null) {
  348.                 setProperty(properties, "origin-source",
  349.                         originCreationInfo.getAgencyID());
  350.             }

  351.             OriginQuality originQuality = origin.getQuality();
  352.             if (originQuality != null) {
  353.                 setProperty(properties, "azimuthal-gap",
  354.                         originQuality.getAzimuthalGap());
  355.                 setProperty(properties, "num-phases-used",
  356.                         originQuality.getUsedPhaseCount());
  357.                 setProperty(properties, "num-stations-used",
  358.                         originQuality.getUsedStationCount());
  359.                 setProperty(properties, "minimum-distance",
  360.                         originQuality.getMinimumDistance());
  361.                 setProperty(properties, "standard-error",
  362.                         originQuality.getStandardError());
  363.             }

  364.             if (origin.getEvaluationMode() == EvaluationMode.MANUAL) {
  365.                 properties.put("review-status", "reviewed");
  366.             } else {
  367.                 properties.put("review-status", "automatic");
  368.             }

  369.             if (origin.getEvaluationStatus() != null) {
  370.                 properties.put("evaluation-status", origin
  371.                         .getEvaluationStatus().value());
  372.             } else {
  373.                 properties.put("evaluation-status",
  374.                         EvaluationStatus.PRELIMINARY.value());
  375.             }

  376.             // add magnitude properties
  377.             Magnitude magnitude = QuakemlUtils.getPreferredMagnitude(event);
  378.             if (magnitude != null) {
  379.                 // track which origin this product is from
  380.                 setProperty(originProduct.getProperties(), "quakeml-magnitude-publicid",
  381.                         magnitude.getPublicID());

  382.                 CreationInfo magnitudeCreationInfo = magnitude
  383.                         .getCreationInfo();
  384.                 if (magnitudeCreationInfo != null) {
  385.                     setProperty(properties, "magnitude-source",
  386.                             magnitudeCreationInfo.getAgencyID());
  387.                 }

  388.                 originProduct.setMagnitude(QuakemlUtils.getValue(magnitude
  389.                         .getMag()));

  390.                 setProperty(properties, "magnitude-type",
  391.                         QuakemlUtils.getMagnitudeType(magnitude.getType()));
  392.                 setProperty(properties, "magnitude-azimuthal-gap",
  393.                         magnitude.getAzimuthalGap());
  394.                 try {
  395.                     setProperty(properties, "magnitude-error", magnitude
  396.                             .getMag().getUncertainty());
  397.                 } catch (Exception e) {
  398.                     // no magnitude uncertainty
  399.                 }
  400.                 setProperty(properties, "magnitude-num-stations-used",
  401.                         magnitude.getStationCount());
  402.             } else {
  403.                 // a location without a magnitude
  404.             }

  405.             products.add(originProduct);
  406.         } else {
  407.             // no preferred origin found
  408.             // signal origin product hasn't been created
  409.             originProduct = null;
  410.         }

  411.         // look for event description products
  412.         Iterator<EventDescription> iter = event.getDescriptions().iterator();
  413.         while (iter.hasNext()) {
  414.             EventDescription next = iter.next();
  415.             EventDescriptionType type = next.getType();

  416.             if (type == EventDescriptionType.EARTHQUAKE_NAME) {
  417.                 if (originProduct != null) {
  418.                     // Region name overrides should be sent
  419.                     // from the admin pages, or attached to an origin. That way
  420.                     // only a preferred origin, or a manually sent region,
  421.                     // can alter the displayed region name
  422.                     setProperty(originProduct.getProperties(), "title", next
  423.                             .getText().trim());
  424.                 }
  425.             } else if (type == EventDescriptionType.TECTONIC_SUMMARY) {
  426.                 Product product = new Product(new ProductId(productSource,
  427.                         "tectonic-summary", productCode, updateTime));
  428.                 product.setEventId(eventSource, eventCode);
  429.                 ByteContent content = new ByteContent(next.getText().getBytes());
  430.                 content.setLastModified(updateTime);
  431.                 product.getContents().put("tectonic-summary.inc.html", content);

  432.                 products.add(product);
  433.             } else if (type == EventDescriptionType.FELT_REPORT) {
  434.                 Product product = new Product(new ProductId(productSource,
  435.                         "impact-text", productCode, updateTime));
  436.                 product.setEventId(eventSource, eventCode);
  437.                 ByteContent content = new ByteContent(next.getText().getBytes());
  438.                 content.setLastModified(updateTime);
  439.                 product.getContents().put("", content);

  440.                 products.add(product);
  441.             }
  442.         }

  443.         if (sendMechanismWhenPhasesExist || originProduct == null
  444.                 || !hasPhaseData) {
  445.             Iterator<FocalMechanism> focalMechanisms = event
  446.                     .getFocalMechanisms().iterator();
  447.             while (focalMechanisms.hasNext()) {
  448.                 FocalMechanism mech = focalMechanisms.next();
  449.                 Product mechProduct = null;

  450.                 if (hasPhaseData && originProduct != null) {
  451.                     // when a phase data product was created, send lightweight
  452.                     // focal mechanism
  453.                     Quakeml lightweightQuakeml = QuakemlUtils
  454.                             .getLightweightFocalMechanism(message,
  455.                                     mech.getPublicID());
  456.                     Event lightweightEvent = QuakemlUtils.getFirstEvent(
  457.                             lightweightQuakeml.getEventParameters());
  458.                     FocalMechanism lightweightMech = QuakemlUtils
  459.                             .getFocalMechanism(lightweightEvent,
  460.                                     mech.getPublicID());

  461.                     mechProduct = getFocalMechanismProduct(lightweightQuakeml,
  462.                             lightweightEvent, lightweightMech,
  463.                             convertQuakemlToString(lightweightQuakeml));
  464.                 } else {
  465.                     // otherwise, fall back to original
  466.                     mechProduct = getFocalMechanismProduct(message, event,
  467.                             mech, quakemlXML);
  468.                 }

  469.                 if (mechProduct != null) {
  470.                     products.add(mechProduct);
  471.                 }
  472.             }
  473.         }

  474.         // send lightweight origin product
  475.         if (sendOriginWhenPhasesExist && hasPhaseData && originProduct != null) {
  476.             // create lightweight origin product
  477.             Product lightweightOrigin = new Product(originProduct);
  478.             lightweightOrigin.getId().setType("origin");

  479.             Quakeml lightweightQuakeml = QuakemlUtils
  480.                     .getLightweightOrigin(message);

  481.             // serialize xml without phase data
  482.             ByteContent lightweightContent = new ByteContent(convertQuakemlToString(lightweightQuakeml).getBytes());
  483.             lightweightContent.setContentType(XML_CONTENT_TYPE);
  484.             lightweightContent.setLastModified(updateTime);
  485.             lightweightOrigin.getContents().put(QUAKEML_CONTENT_PATH,
  486.                     lightweightContent);

  487.             // insert at front of list
  488.             products.add(0, lightweightOrigin);
  489.         }

  490.         return products;
  491.     }

  492.     /**
  493.      * @param quakeml Quakeml
  494.      * @param event the event element in the quakeml message
  495.      * @param mech A focal mechanism
  496.      * @param quakemlContent String of content in Quakeml
  497.      * @return A product derived from a focal mechanism
  498.      */
  499.     protected Product getFocalMechanismProduct(final Quakeml quakeml,
  500.             final Event event, final FocalMechanism mech,
  501.             final String quakemlContent) {
  502.         MomentTensor momentTensor = mech.getMomentTensor();

  503.         // determine product id
  504.         String mechSource = mech.getDatasource();
  505.         String mechCode = mech.getDataid();
  506.         String mechType = mech.getDatatype();
  507.         if (mechType == null) {
  508.             // automatically determine mechanism type based on available data
  509.             mechType = "focal-mechanism";
  510.             if (momentTensor != null && momentTensor.getTensor() != null) {
  511.                 mechType = "moment-tensor";
  512.                 if (mechCode == null && momentTensor.getMethodID() != null) {
  513.                     mechCode = productCode + "_" + momentTensor.getMethodID();
  514.                 }
  515.             }
  516.         }
  517.         if (mechSource == null) {
  518.             mechSource = productSource;
  519.         }
  520.         if (mechCode == null) {
  521.             mechCode = productCode;
  522.         }
  523.         Product product = new Product(new ProductId(mechSource, mechType,
  524.                 mechCode, updateTime));
  525.         if (mech.getEvaluationStatus() == EvaluationStatus.REJECTED) {
  526.             // this is a delete
  527.             product.setStatus(Product.STATUS_DELETE);
  528.         }

  529.         // product is being contributed to containing event
  530.         product.setEventId(eventSource, eventCode);

  531.         ByteContent tensorContent = new ByteContent(quakemlContent.getBytes());
  532.         tensorContent.setContentType(XML_CONTENT_TYPE);
  533.         tensorContent.setLastModified(updateTime);
  534.         product.getContents().put(QUAKEML_CONTENT_PATH, tensorContent);
  535.         product.getContents().put(CONTENTS_XML_PATH, getContentsXML());

  536.         Map<String, String> properties = product.getProperties();

  537.         // track which mechanism is used in this product, for later
  538.         // extraction
  539.         setProperty(properties, "quakeml-publicid", mech.getPublicID());
  540.         // also track original event if set, in case this is a recontributed
  541.         // product
  542.         setProperty(properties, "original-eventsource", mech.getEventsource());
  543.         setProperty(properties, "original-eventsourcecode", mech.getEventid());

  544.         if (mech.getEvaluationMode() == EvaluationMode.MANUAL) {
  545.             properties.put("review-status", "reviewed");
  546.         } else {
  547.             properties.put("review-status", "automatic");
  548.         }

  549.         if (mech.getEvaluationStatus() != null) {
  550.             properties.put("evaluation-status", mech.getEvaluationStatus()
  551.                     .value());
  552.         } else {
  553.             properties.put("evaluation-status", EvaluationStatus.PRELIMINARY
  554.                     .value());
  555.         }

  556.         CreationInfo focalMechanismCreationInfo = mech.getCreationInfo();
  557.         if (focalMechanismCreationInfo != null) {
  558.             product.setVersion(focalMechanismCreationInfo.getVersion());
  559.             setProperty(properties, "beachball-source",
  560.                     focalMechanismCreationInfo.getAgencyID());
  561.         }

  562.         NodalPlanes planes = mech.getNodalPlanes();
  563.         if (planes != null) {
  564.             NodalPlane plane1 = planes.getNodalPlane1();
  565.             setProperty(properties, "nodal-plane-1-strike", plane1.getStrike());
  566.             setProperty(properties, "nodal-plane-1-dip", plane1.getDip());
  567.             setProperty(properties, "nodal-plane-1-rake", plane1.getRake());

  568.             NodalPlane plane2 = planes.getNodalPlane2();
  569.             setProperty(properties, "nodal-plane-2-strike", plane2.getStrike());
  570.             setProperty(properties, "nodal-plane-2-dip", plane2.getDip());
  571.             setProperty(properties, "nodal-plane-2-rake", plane2.getRake());
  572.         }

  573.         // add properties for principal axes
  574.         PrincipalAxes axes = mech.getPrincipalAxes();
  575.         if (axes != null) {
  576.             Axis tAxis = axes.getTAxis();
  577.             if (tAxis != null) {
  578.                 setProperty(properties, "t-axis-azimuth", tAxis.getAzimuth());
  579.                 setProperty(properties, "t-axis-plunge", tAxis.getPlunge());
  580.                 setProperty(properties, "t-axis-length", tAxis.getLength(), true);
  581.             }
  582.             Axis nAxis = axes.getNAxis();
  583.             if (nAxis != null) {
  584.                 setProperty(properties, "n-axis-azimuth", nAxis.getAzimuth());
  585.                 setProperty(properties, "n-axis-plunge", nAxis.getPlunge());
  586.                 setProperty(properties, "n-axis-length", nAxis.getLength(), true);
  587.             }
  588.             Axis pAxis = axes.getPAxis();
  589.             if (pAxis != null) {
  590.                 setProperty(properties, "p-axis-azimuth", pAxis.getAzimuth());
  591.                 setProperty(properties, "p-axis-plunge", pAxis.getPlunge());
  592.                 setProperty(properties, "p-axis-length", pAxis.getLength(), true);
  593.             }
  594.         }

  595.         setProperty(properties, "num-stations-used",
  596.                 mech.getStationPolarityCount());

  597.         // try to read triggering origin attributes for association
  598.         Origin triggeringOrigin = QuakemlUtils.getOrigin(event,
  599.                 mech.getTriggeringOriginID());
  600.         if (triggeringOrigin != null) {
  601.             // set properties for association purposes.
  602.             product.setLatitude(triggeringOrigin.getLatitude().getValue());
  603.             product.setLongitude(triggeringOrigin.getLongitude().getValue());
  604.             product.setEventTime(triggeringOrigin.getTime().getValue());
  605.             // add triggering depth if present
  606.             if (triggeringOrigin.getDepth() != null) {
  607.                 product.setDepth(triggeringOrigin.getDepth().getValue()
  608.                         .divide(METERS_PER_KILOMETER));
  609.             }
  610.         }

  611.         Tensor tensor = null;
  612.         if (momentTensor != null) {
  613.             setProperty(properties, "percent-double-couple",
  614.                     momentTensor.getDoubleCouple());
  615.             setProperty(properties, "scalar-moment",
  616.                     momentTensor.getScalarMoment(), true);
  617.             setProperty(properties, "beachball-type",
  618.                     momentTensor.getMethodID());
  619.             if (momentTensor.getInversionType() != null) {
  620.                 setProperty(properties, "inversion-type",
  621.                         momentTensor.getInversionType().value());
  622.             }

  623.             tensor = momentTensor.getTensor();
  624.             if (tensor != null) {
  625.                 setProperty(properties, "tensor-mpp", tensor.getMpp(), true);
  626.                 setProperty(properties, "tensor-mrp", tensor.getMrp(), true);
  627.                 setProperty(properties, "tensor-mrr", tensor.getMrr(), true);
  628.                 setProperty(properties, "tensor-mrt", tensor.getMrt(), true);
  629.                 setProperty(properties, "tensor-mtp", tensor.getMtp(), true);
  630.                 setProperty(properties, "tensor-mtt", tensor.getMtt(), true);
  631.             }

  632.             SourceTimeFunction sourceTimeFunction = momentTensor
  633.                     .getSourceTimeFunction();
  634.             if (sourceTimeFunction != null) {
  635.                 SourceTimeFunctionType sourceTimeFunctionType = sourceTimeFunction
  636.                         .getType();
  637.                 if (sourceTimeFunctionType != null) {
  638.                     setProperty(properties, "sourcetime-type",
  639.                             sourceTimeFunctionType.value());
  640.                 }
  641.                 setProperty(properties, "sourcetime-duration",
  642.                         sourceTimeFunction.getDuration());
  643.                 setProperty(properties, "sourcetime-risetime",
  644.                         sourceTimeFunction.getRiseTime());
  645.                 setProperty(properties, "sourcetime-decaytime",
  646.                         sourceTimeFunction.getDecayTime());
  647.             }

  648.             Origin derivedOrigin = QuakemlUtils.getOrigin(event,
  649.                     momentTensor.getDerivedOriginID());

  650.             if (derivedOrigin != null) {
  651.                 setProperty(properties, "derived-latitude",
  652.                         derivedOrigin.getLatitude());
  653.                 setProperty(properties, "derived-longitude",
  654.                         derivedOrigin.getLongitude());
  655.                 RealQuantity depth = derivedOrigin.getDepth();
  656.                 if (depth != null) {
  657.                     setProperty(properties, "derived-depth", depth.getValue()
  658.                             .divide(METERS_PER_KILOMETER));
  659.                 }
  660.                 setProperty(properties, "derived-eventtime",
  661.                         derivedOrigin.getTime());
  662.             }

  663.             Magnitude derivedMagnitude = QuakemlUtils.getMagnitude(event,
  664.                     momentTensor.getMomentMagnitudeID());
  665.             if (derivedMagnitude != null) {
  666.                 String derivedMagnitudeType = derivedMagnitude.getType();
  667.                 setProperty(properties, "derived-magnitude-type",
  668.                         derivedMagnitudeType);
  669.                 setProperty(properties, "derived-magnitude",
  670.                         derivedMagnitude.getMag());

  671.                 if (derivedMagnitudeType.equalsIgnoreCase("Mwd")) {
  672.                     product.getId().setType("broadband-depth");
  673.                 }
  674.             }
  675.         }

  676.         if (!Product.STATUS_DELETE.equals(product.getStatus())) {
  677.             // if not deleting, do some validation
  678.             String type = product.getId().getType();
  679.             if ("focal-mechanism".equals(type) && planes == null) {
  680.                 LOGGER.warning("Focal mechanism missing nodal planes");
  681.                 return null;
  682.             } else if ("moment-tensor".equals(type) && tensor == null) {
  683.                 LOGGER.warning("Moment tensor missing tensor parameters");
  684.                 return null;
  685.             }
  686.         }

  687.         return product;
  688.     }

  689.     /**
  690.      * setProperty for RealQuantity values. No exponentials
  691.      * @param properties to add
  692.      * @param name of property
  693.      * @param value of property
  694.      */
  695.     public void setProperty(final Map<String, String> properties,
  696.             final String name, final RealQuantity value) {
  697.         setProperty(properties, name, value, false);
  698.     }

  699.     /**
  700.      * setProperty for RealQuantity values
  701.      * @param properties to add
  702.      * @param name of property
  703.      * @param value of property
  704.      * @param allowExponential if allowed
  705.      */
  706.     public void setProperty(final Map<String, String> properties,
  707.             final String name, final RealQuantity value,
  708.             final boolean allowExponential) {
  709.         if (value == null) {
  710.             return;
  711.         }

  712.         setProperty(properties, name, value.getValue(), allowExponential);
  713.     }

  714.     /**
  715.      * setProperty for strings
  716.      * @param properties to add
  717.      * @param name of property
  718.      * @param value of property
  719.      */
  720.     public void setProperty(final Map<String, String> properties,
  721.             final String name, final String value) {
  722.         if (value == null) {
  723.             return;
  724.         }

  725.         properties.put(name, value);
  726.     }

  727.     /**
  728.      * setProperty for TimeQuantities
  729.      * @param properties to add
  730.      * @param name of property
  731.      * @param value of property
  732.      */
  733.     public void setProperty(final Map<String, String> properties,
  734.             final String name, final TimeQuantity value) {
  735.         if (value == null) {
  736.             return;
  737.         }

  738.         properties.put(name, XmlUtils.formatDate(value.getValue()));
  739.     }

  740.     /**
  741.      * setProperty taking in BigDecimals. No exponentials
  742.      * @param properties to add
  743.      * @param name of property
  744.      * @param value of property
  745.      */
  746.     public void setProperty(final Map<String, String> properties,
  747.             final String name, final BigDecimal value) {
  748.         setProperty(properties, name, value, false);
  749.     }

  750.     /**
  751.      * setProperty taking in BigDecimals
  752.      * @param properties to add
  753.      * @param name of property
  754.      * @param value of property
  755.      * @param allowExponential boolean
  756.      */
  757.     public void setProperty(final Map<String, String> properties,
  758.             final String name, final BigDecimal value,
  759.             final boolean allowExponential) {
  760.         if (value == null) {
  761.             return;
  762.         }

  763.         properties.put(name, allowExponential ? value.toString()
  764.                 : value.toPlainString());
  765.     }

  766.     /**
  767.      * setProperty taking in BigIntegers. No exponentials
  768.      * @param properties to add
  769.      * @param name of property
  770.      * @param value of property
  771.      */
  772.     public void setProperty(final Map<String, String> properties,
  773.             final String name, final BigInteger value) {
  774.         if (value == null) {
  775.             return;
  776.         }

  777.         properties.put(name, value.toString());
  778.     }

  779.     /**
  780.      * setProperty taking in Integers
  781.      * @param properties to add
  782.      * @param name of property
  783.      * @param value of property
  784.      */
  785.     public void setProperty(final Map<String, String> properties,
  786.             final String name, final Integer value) {
  787.         if (value == null) {
  788.             return;
  789.         }

  790.         properties.put(name, value.toString());
  791.     }

  792.     /** @param converter FileToQuakeml Converter to set */
  793.     public void setConverter(FileToQuakemlConverter converter) {
  794.         this.converter = converter;
  795.     }

  796.     /** @return FileToQuakeml converter */
  797.     public FileToQuakemlConverter getConverter() {
  798.         return converter;
  799.     }

  800.     @Override
  801.     public boolean isValidate() {
  802.         return validate;
  803.     }

  804.     @Override
  805.     public void setValidate(boolean validate) {
  806.         this.validate = validate;
  807.     }

  808.     /**
  809.      * Implement the ProductCreator interface.
  810.      */
  811.     @Override
  812.     public List<Product> getProducts(File file) throws Exception {
  813.         if (this.converter == null) {
  814.             // preserve quakeml input
  815.             String contents = new String(StreamUtils.readStream(file));
  816.             return this.getQuakemlProducts(contents);
  817.         } else {
  818.             Quakeml quakeml = this.converter.parseFile(file);
  819.             return this.getQuakemlProducts(quakeml);
  820.         }
  821.     }

  822.     /**
  823.      * @return XML contents
  824.      */
  825.     protected Content getContentsXML() {
  826.         StringBuffer buf = new StringBuffer();
  827.         buf.append("<?xml version=\"1.0\"?>\n");
  828.         buf.append("<contents xmlns=\"http://earthquake.usgs.gov/earthquakes/event/contents\">\n");
  829.         buf.append("<file title=\"Earthquake XML (Quakeml)\">\n");
  830.         buf.append("<format type=\"xml\" href=\"").append(QUAKEML_CONTENT_PATH)
  831.                 .append("\"/>\n");
  832.         buf.append("</file>\n");
  833.         buf.append("</contents>\n");

  834.         ByteContent content = new ByteContent(buf.toString().getBytes());
  835.         content.setContentType("application/xml");

  836.         return content;
  837.     }

  838.     /** @return boolean sendOriginWhenPhasesExist */
  839.     public boolean isSendOriginWhenPhasesExist() {
  840.         return sendOriginWhenPhasesExist;
  841.     }

  842.     /** @param sendOriginWhenPhasesExist boolean to set */
  843.     public void setSendOriginWhenPhasesExist(boolean sendOriginWhenPhasesExist) {
  844.         this.sendOriginWhenPhasesExist = sendOriginWhenPhasesExist;
  845.     }

  846.     /** @param sendMechanismWhenPhasesExist boolean to set */
  847.     public void setSendMechanismWhenPhasesExist(
  848.             boolean sendMechanismWhenPhasesExist) {
  849.         this.sendMechanismWhenPhasesExist = sendMechanismWhenPhasesExist;
  850.     }

  851.     /** @return sendMechanismWhenPhasesExist boolean */
  852.     public boolean isSendMechanismWhenPhasesExist() {
  853.         return sendMechanismWhenPhasesExist;
  854.     }

  855.     /** @param padForBase64Bug to set */
  856.     public void setPadForBase64Bug(boolean padForBase64Bug) {
  857.         this.padForBase64Bug = padForBase64Bug;
  858.     }

  859.     /** @return padForBase64Bug */
  860.     public boolean isPadForBase64Bug() {
  861.         return padForBase64Bug;
  862.     }

  863.     /**
  864.      * Utility function that converts quakeml to a string
  865.      *
  866.      * @param message
  867.      *                      The quakeml to be converted
  868.      *
  869.      * @return raw string
  870.      * @throws Exception if quakeml doesn't validate
  871.      */
  872.     private String convertQuakemlToString(Quakeml message) throws Exception{
  873.         return fixRawQuakeml(formatConverter.getString(message,validate));
  874.     }

  875.     /**
  876.      * Fixes base 64 bug: https://bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-8222187
  877.      *
  878.      * @param rawMessage
  879.      *                          the message to edit
  880.      * @return the fixed string
  881.      */
  882.     public String fixRawQuakeml(String rawMessage) {
  883.         if (padForBase64Bug && rawMessage.getBytes().length % 4096 == 1) {
  884.             rawMessage += ' ';
  885.         }
  886.         return rawMessage;
  887.     }

  888.     /**
  889.      * Convert quakeml files to products.
  890.      *
  891.      * @param args
  892.      *            a list of files to convert from quakeml to products.
  893.      * @throws Exception if error occurs
  894.      */
  895.     public static void main(final String[] args) throws Exception {
  896.         QuakemlProductCreator creator = new QuakemlProductCreator();
  897.         creator.setSendOriginWhenPhasesExist(true);
  898.         creator.setSendMechanismWhenPhasesExist(true);

  899.         if (args.length == 0) {
  900.             System.err
  901.                     .println("Quakeml to product converter utility.  "
  902.                             + "For visually inspecting products that would be created from quakeml files.");
  903.             System.err.println();
  904.             System.err.println("Usage: QuakemlProductCreator FILE [ FILE ]");
  905.             System.err
  906.                     .println("\tFILE - a quakeml file to convert to products, repeat as needed");
  907.             System.exit(1);
  908.         }

  909.         for (String arg : args) {
  910.             File quakeml = new File(arg);
  911.             System.err.println("reading quakeml from "
  912.                     + quakeml.getCanonicalPath());

  913.             Iterator<Product> iter = creator.getProducts(quakeml).iterator();
  914.             while (iter.hasNext()) {
  915.                 Product next = iter.next();
  916.                 new ObjectProductSource(next).streamTo(new XmlProductHandler(
  917.                         new StreamUtils.UnclosableOutputStream(System.err)));
  918.             }

  919.             System.err.println();
  920.         }
  921.     }

  922. }