QuakemlUtils.java

package gov.usgs.earthquake.eids;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Date;
import java.util.Iterator;
import java.util.List;

import org.quakeml_1_2.CreationInfo;
import org.quakeml_1_2.Event;
import org.quakeml_1_2.IntegerQuantity;
import org.quakeml_1_2.InternalEvent;
import org.quakeml_1_2.Magnitude;
import org.quakeml_1_2.MomentTensor;
import org.quakeml_1_2.Origin;
import org.quakeml_1_2.Quakeml;
import org.quakeml_1_2.RealQuantity;
import org.quakeml_1_2.ScenarioEvent;
import org.quakeml_1_2.TimeQuantity;
import org.quakeml_1_2.FocalMechanism;
import org.quakeml_1_2.EventParameters;

/**
 * Utility methods for ANSS Quakeml objects.
 */
public class QuakemlUtils {

  /**
   * Find the first event in a message.
   *
   * If a quakeml event object does not exist, check for the first internal or
   * scenario event.
   *
   * @param eventParameters to find event from
   * @return first event
   */
  public static Event getFirstEvent(final EventParameters eventParameters) {
    // only process first event
    if (eventParameters.getEvents().size() > 0) {
      // found actual event
      return eventParameters.getEvents().get(0);
    } else {
      // check for internal/scenario events
      List<Object> anies = eventParameters.getAnies();
      Iterator<Object> anyIter = anies.iterator();
      while (anyIter.hasNext()) {
        Object next = anyIter.next();
        if (next instanceof InternalEvent) {
          // found internal event
          return (InternalEvent) next;
        } else if (next instanceof ScenarioEvent) {
          // found scenario event
          return (ScenarioEvent) next;
        }
      }
    }
    // no event found
    return null;
  }

  /**
   * Find the preferred Origin in an Event.
   *
   * @param event event
   * @return Origin with publicID equal to event.getPreferredOriginID(), or null
   *         if not found.
   */
  public static Origin getPreferredOrigin(final Event event) {
    return getOrigin(event, event.getPreferredOriginID());
  }

  /**
   * Find a specific Origin in an Event.
   *
   * @param event event to search
   * @param id    publicID to find
   * @return Origin with publicID equal to id, or null if not found.
   */
  public static Origin getOrigin(final Event event, final String id) {
    if (id != null) {
      Iterator<Origin> iter = event.getOrigins().iterator();
      while (iter.hasNext()) {
        Origin next = iter.next();
        String originId = next.getPublicID();
        if (originId != null && originId.equals(id)) {
          return next;
        }
      }
    }
    return null;
  }

  /**
   * Find the preferred Magnitude in an Event.
   *
   * @param event event to search
   * @return Magnitude with publicID equal to event.getPreferredMagnitudeID(), or
   *         null if not found.
   */
  public static Magnitude getPreferredMagnitude(final Event event) {
    return getMagnitude(event, event.getPreferredMagnitudeID());
  }

  /**
   * Find a specific Magnitude in an event.
   *
   * @param event event to search
   * @param id    publicID to find.
   * @return Magnitude with publicID equal to id, or null if not found.
   */
  public static Magnitude getMagnitude(final Event event, final String id) {
    if (id != null) {
      Iterator<Magnitude> iter = event.getMagnitudes().iterator();
      while (iter.hasNext()) {
        Magnitude next = iter.next();
        String magnitudeId = next.getPublicID();
        if (magnitudeId != null && magnitudeId.equals(id)) {
          return next;
        }
      }
    }
    return null;
  }

  /**
   * Find a specific FocalMechanism in an event.
   *
   * @param event event to search
   * @param id    publicID to find.
   * @return FocalMechanism with publicID equal to id, or null if not found.
   */
  public static FocalMechanism getFocalMechanism(final Event event, final String id) {
    if (id != null) {
      Iterator<FocalMechanism> iter = event.getFocalMechanisms().iterator();
      while (iter.hasNext()) {
        FocalMechanism next = iter.next();
        String mechId = next.getPublicID();
        if (mechId != null && mechId.equals(id)) {
          return next;
        }
      }
    }
    return null;
  }

  /**
   * Flatten multiple creation info objects, but using the most specific (at end
   * of list) value that is not null.
   *
   * @param infos to flatten
   * @return a CreationInfo object with the most specific properties (later in
   *         arguments list), which may be null.
   */
  public static CreationInfo getCreationInfo(final CreationInfo... infos) {
    CreationInfo info = new CreationInfo();

    for (int i = 0, len = infos.length; i < len; i++) {
      CreationInfo next = infos[i];
      if (next == null) {
        continue;
      }

      if (next.getAgencyID() != null) {
        // set agencyid and agency uri at same time
        info.setAgencyID(next.getAgencyID());
        info.setAgencyURI(next.getAgencyURI());
      }

      if (next.getAuthor() != null) {
        // author and author uri at same time
        info.setAuthor(next.getAuthor());
        info.setAuthorURI(next.getAuthorURI());
      }

      if (next.getCreationTime() != null) {
        info.setCreationTime(next.getCreationTime());
      }

      if (next.getVersion() != null) {
        info.setVersion(next.getVersion());
      }
    }

    return info;
  }

  /**
   * @param value RealQuantity
   * @return value.getValue(), or null if value == null.
   */
  public static BigDecimal getValue(final RealQuantity value) {
    if (value == null) {
      return null;
    } else {
      return value.getValue();
    }
  }

  /**
   * @param value IntegerQuantity
   * @return value.getValue(), or null if value == null.
   */
  public static BigInteger getValue(final IntegerQuantity value) {
    if (value == null) {
      return null;
    } else {
      return value.getValue();
    }
  }

  /**
   * @param value TimeQuantity
   * @return value.getValue(), or null if value == null.
   */
  public static Date getValue(final TimeQuantity value) {
    if (value == null) {
      return null;
    } else {
      return value.getValue();
    }
  }

  /**
   * @param magnitudeType to return
   * @return MagntitudeType string
   */
  public static String getMagnitudeType(final String magnitudeType) {
    if (magnitudeType == null) {
      return null;
    }

    return magnitudeType.toLowerCase();
  }

  /**
   * @return true if list is not null and not empty.
   */
  private static boolean listHasData(final List<?> list) {
    if (list != null && list.size() > 0) {
      return true;
    }
    return false;
  }

  /**
   * Clear a list if it is not null and not empty.
   */
  private static void listRemoveData(final List<?> list) {
    if (list != null && list.size() > 0) {
      list.clear();
    }
  }

  /**
   * Check if an event has phase data.
   *
   * <ul>
   * <li>event.getPicks()</li>
   * <li>event.getAmplitudes()</li>
   * <li>event.getStationMagnitudes()</li>
   * <li>event.getOrigins()
   * <ul>
   * <li>origin.getArrivals()</li>
   * </ul>
   * </li>
   * <li>event.getMagnitudes()
   * <ul>
   * <li>magnitude.getStationMagnitudeContributions()</li>
   * </ul>
   * </li>
   * </ul>
   *
   * @param event event to search.
   * @return true if phase data found, false otherwise.
   */
  public static boolean hasPhaseData(final Event event) {
    // event level phase data
    if (listHasData(event.getPicks()) || listHasData(event.getAmplitudes())
        || listHasData(event.getStationMagnitudes())) {
      return true;
    }

    // origin level phase data
    if (event.getOrigins() != null) {
      Iterator<Origin> iter = event.getOrigins().iterator();
      while (iter.hasNext()) {
        Origin next = iter.next();
        if (listHasData(next.getArrivals())) {
          return true;
        }
      }
    }

    // magnitude level phase data
    if (event.getMagnitudes() != null) {
      Iterator<Magnitude> iter = event.getMagnitudes().iterator();
      while (iter.hasNext()) {
        Magnitude next = iter.next();
        if (listHasData(next.getStationMagnitudeContributions())) {
          return true;
        }
      }
    }

    // haven't found phase data
    return false;
  }

  /**
   * Similar to {@link #hasPhaseData(Event)}, but empties any lists that have
   * phase data. Also removes &lt;waveformID&gt; elements from focalMechanism.
   *
   * @param event event to clear.
   */
  public static void removePhaseData(final Event event) {
    // event level phase data
    listRemoveData(event.getPicks());
    listRemoveData(event.getAmplitudes());
    listRemoveData(event.getStationMagnitudes());

    // origin level phase data
    if (event.getOrigins() != null) {
      Iterator<Origin> iter = event.getOrigins().iterator();
      while (iter.hasNext()) {
        Origin next = iter.next();
        listRemoveData(next.getArrivals());
      }
    }

    // magnitude level phase data
    if (event.getMagnitudes() != null) {
      Iterator<Magnitude> iter = event.getMagnitudes().iterator();
      while (iter.hasNext()) {
        Magnitude next = iter.next();
        listRemoveData(next.getStationMagnitudeContributions());
      }
    }

    // additionally, remove waveformIDs from focal mechanisms
    if (event.getFocalMechanisms() != null) {
      Iterator<FocalMechanism> iter = event.getFocalMechanisms().iterator();
      while (iter.hasNext()) {
        FocalMechanism next = iter.next();
        listRemoveData(next.getWaveformIDs());
      }
    }
  }

  /**
   * Extract the preferred origin and magnitude from the first event in a quakeml
   * message.
   *
   * @param q the quakeml message with a preferred origin.
   * @return a new Quakeml object.
   */
  public static Quakeml getLightweightOrigin(final Quakeml q) {
    Quakeml quakeml = new Quakeml();

    EventParameters oldEventParameters = q.getEventParameters();
    EventParameters eventParameters = shallowClone(oldEventParameters);
    quakeml.setEventParameters(eventParameters);

    Event oldEvent = getFirstEvent(q.getEventParameters());
    Event event = shallowClone(oldEvent);
    eventParameters.getEvents().add(event);

    Origin oldOrigin = getPreferredOrigin(oldEvent);
    if (oldOrigin == null) {
      return null;
    }
    Origin origin = shallowClone(oldOrigin);
    event.getOrigins().add(origin);
    event.setPreferredOriginID(origin.getPublicID());

    Magnitude oldMagnitude = getPreferredMagnitude(oldEvent);
    if (oldMagnitude != null) {
      // magnitude is not required for origin
      Magnitude magnitude = shallowClone(oldMagnitude);
      event.getMagnitudes().add(magnitude);
      event.setPreferredMagnitudeID(magnitude.getPublicID());
    }

    return quakeml;
  }

  /**
   * Extract a focalMechanism, triggering origin, derived origin, and derived
   * magnitude from the first event in a quakeml message.
   *
   * @param q                the quakeml message with a focalMechanism
   * @param focalMechanismId the focalMechanism to extract.
   * @return a new Quakeml object.
   */
  public static Quakeml getLightweightFocalMechanism(final Quakeml q, final String focalMechanismId) {
    Quakeml quakeml = new Quakeml();

    EventParameters oldEventParameters = q.getEventParameters();
    EventParameters eventParameters = shallowClone(oldEventParameters);
    quakeml.setEventParameters(eventParameters);

    Event oldEvent = getFirstEvent(q.getEventParameters());
    Event event = shallowClone(oldEvent);
    eventParameters.getEvents().add(event);

    FocalMechanism oldMech = getFocalMechanism(oldEvent, focalMechanismId);
    event.getFocalMechanisms().add(shallowClone(oldMech));

    // add triggering origin
    Origin triggeringOrigin = getOrigin(oldEvent, oldMech.getTriggeringOriginID());
    if (triggeringOrigin != null) {
      event.getOrigins().add(shallowClone(triggeringOrigin));
    }

    // pull in derived origin and magnitude if present
    MomentTensor tensor = oldMech.getMomentTensor();
    if (tensor != null) {
      Origin derivedOrigin = getOrigin(oldEvent, tensor.getDerivedOriginID());
      if (derivedOrigin != null) {
        event.getOrigins().add(shallowClone(derivedOrigin));
      }
      Magnitude momentMagnitude = getMagnitude(oldEvent, tensor.getMomentMagnitudeID());
      if (momentMagnitude != null) {
        event.getMagnitudes().add(shallowClone(momentMagnitude));
      }
    }

    return quakeml;
  }

  /**
   * Create a copy of an event parameters object.
   *
   * omits anies, events, and other attributes.
   *
   * @param oldEventParameters to copy
   * @return a new EventParameters object.
   */
  public static EventParameters shallowClone(final EventParameters oldEventParameters) {
    EventParameters eventParameters = new EventParameters();
    eventParameters.getComments().addAll(oldEventParameters.getComments());
    eventParameters.setCreationInfo(oldEventParameters.getCreationInfo());
    eventParameters.setDescription(oldEventParameters.getDescription());
    eventParameters.setPublicID(oldEventParameters.getPublicID());
    return eventParameters;
  }

  /**
   * Create a copy of an event object.
   *
   * omits amplitudes, anies, event descriptions, mechanisms, magnitudes, origins,
   * other attributes, picks, preferred*ID, and station magnitudes.
   *
   * @param oldEvent to copy
   * @return a new Event object.
   */
  public static Event shallowClone(final Event oldEvent) {
    Event event = new Event();
    event.getComments().addAll(oldEvent.getComments());
    event.setCreationInfo(oldEvent.getCreationInfo());
    event.setDataid(oldEvent.getDataid());
    event.setDatasource(oldEvent.getDatasource());
    event.setEventid(oldEvent.getEventid());
    event.setEventsource(oldEvent.getEventsource());
    event.setPublicID(oldEvent.getPublicID());
    event.setType(oldEvent.getType());
    event.setTypeCertainty(oldEvent.getTypeCertainty());
    return event;
  }

  /**
   * Create a copy of an origin object.
   *
   * omits anies, arrivals, and other attributes.
   *
   * @param oldOrigin to copy
   * @return a new Origin object.
   */
  public static Origin shallowClone(final Origin oldOrigin) {
    Origin origin = new Origin();
    origin.getComments().addAll(oldOrigin.getComments());
    origin.getCompositeTimes().addAll(oldOrigin.getCompositeTimes());
    origin.setCreationInfo(oldOrigin.getCreationInfo());
    origin.setDataid(oldOrigin.getDataid());
    origin.setDatasource(oldOrigin.getDatasource());
    origin.setDepth(oldOrigin.getDepth());
    origin.setDepthType(oldOrigin.getDepthType());
    origin.setEarthModelID(oldOrigin.getEarthModelID());
    origin.setEpicenterFixed(oldOrigin.getEpicenterFixed());
    origin.setEvaluationMode(oldOrigin.getEvaluationMode());
    origin.setEvaluationStatus(oldOrigin.getEvaluationStatus());
    origin.setEventid(oldOrigin.getEventid());
    origin.setEventsource(oldOrigin.getEventsource());
    origin.setLatitude(oldOrigin.getLatitude());
    origin.setLongitude(oldOrigin.getLongitude());
    origin.setMethodID(oldOrigin.getMethodID());
    origin.setOriginUncertainty(oldOrigin.getOriginUncertainty());
    origin.setPublicID(oldOrigin.getPublicID());
    origin.setQuality(oldOrigin.getQuality());
    origin.setReferenceSystemID(oldOrigin.getReferenceSystemID());
    origin.setRegion(oldOrigin.getRegion());
    origin.setTime(oldOrigin.getTime());
    origin.setTimeFixed(oldOrigin.getTimeFixed());
    origin.setType(oldOrigin.getType());
    return origin;
  }

  /**
   * Create a copy of a magnitude object.
   *
   * omits anies, other attributes, and station magnitude contributions.
   *
   * @param oldMagnitude to copy
   * @return a new Magnitude object.
   */
  public static Magnitude shallowClone(final Magnitude oldMagnitude) {
    Magnitude magnitude = new Magnitude();
    magnitude.setAzimuthalGap(oldMagnitude.getAzimuthalGap());
    magnitude.getComments().addAll(oldMagnitude.getComments());
    magnitude.setCreationInfo(getCreationInfo(oldMagnitude.getCreationInfo()));
    magnitude.setDataid(oldMagnitude.getDataid());
    magnitude.setDatasource(oldMagnitude.getDatasource());
    magnitude.setEvaluationMode(oldMagnitude.getEvaluationMode());
    magnitude.setEvaluationStatus(oldMagnitude.getEvaluationStatus());
    magnitude.setEventid(oldMagnitude.getEventid());
    magnitude.setEventsource(oldMagnitude.getEventsource());
    magnitude.setMag(oldMagnitude.getMag());
    magnitude.setMethodID(oldMagnitude.getMethodID());
    magnitude.setOriginID(oldMagnitude.getOriginID());
    magnitude.setPublicID(oldMagnitude.getPublicID());
    magnitude.setStationCount(oldMagnitude.getStationCount());
    magnitude.setType(oldMagnitude.getType());
    return magnitude;
  }

  /**
   * Create a copy of a focal mechanism object.
   *
   * omits anies, other attributes.
   *
   * @param oldMech to copy
   * @return a new FocalMechanism object.
   */
  public static FocalMechanism shallowClone(final FocalMechanism oldMech) {
    FocalMechanism mech = new FocalMechanism();
    mech.setAzimuthalGap(oldMech.getAzimuthalGap());
    mech.getComments().addAll(oldMech.getComments());
    mech.setCreationInfo(oldMech.getCreationInfo());
    mech.setDataid(oldMech.getDataid());
    mech.setDatasource(oldMech.getDatasource());
    mech.setEvaluationMode(oldMech.getEvaluationMode());
    mech.setEvaluationStatus(oldMech.getEvaluationStatus());
    mech.setEventid(oldMech.getEventid());
    mech.setEventsource(oldMech.getEventsource());
    mech.setMethodID(oldMech.getMethodID());
    mech.setMisfit(oldMech.getMisfit());
    mech.setMomentTensor(oldMech.getMomentTensor());
    mech.setNodalPlanes(oldMech.getNodalPlanes());
    mech.setPrincipalAxes(oldMech.getPrincipalAxes());
    mech.setPublicID(oldMech.getPublicID());
    mech.setStationDistributionRatio(oldMech.getStationDistributionRatio());
    mech.setStationPolarityCount(oldMech.getStationPolarityCount());
    mech.setTriggeringOriginID(oldMech.getTriggeringOriginID());
    mech.getWaveformIDs().addAll(oldMech.getWaveformIDs());
    return mech;
  }

}