ShakeMap.java

package gov.usgs.earthquake.shakemap;

import gov.usgs.earthquake.product.Content;
import gov.usgs.earthquake.product.Product;
import gov.usgs.util.StreamUtils;
import gov.usgs.util.XmlUtils;

import java.io.InputStream;
import java.math.BigDecimal;
import java.util.Date;
import java.util.HashMap;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * ShakeMap object to add additional Product properties based on contents.
 *
 * This subclass of Product provides access to additional ShakeMap-specific
 * attributes and loads these attributes, as well as additional Product
 * attributes from ShakeMap source XML files.
 */
public class ShakeMap extends Product {

  /** Property for event description */
  public static final String EVENT_DESCRIPTION_PROPERTY = "event-description";
  /** Property for event type */
  public static final String EVENT_TYPE_PROPERTY = "event-type";
  /** Property for map status */
  public static final String MAP_STATUS_PROPERTY = "map-status";
  /** Property for max latitude */
  public static final String MAXIMUM_LATITUDE_PROPERTY = "maximum-latitude";
  /** Property for max longitude */
  public static final String MAXIMUM_LONGITUDE_PROPERTY = "maximum-longitude";
  /** Property for min latitude */
  public static final String MINIMUM_LATITUDE_PROPERTY = "minimum-latitude";
  /** Property for min longitude */
  public static final String MINIMUM_LONGITUDE_PROPERTY = "minimum-longitude";
  /** Property for process timestamp */
  public static final String PROCESS_TIMESTAMP_PROPERTY = "process-timestamp";

  private static final Logger LOGGER = Logger.getLogger(ShakeMap.class.getName());

  /** References to file content in the Product */
  public static final String GRID_XML_ATTACHMENT = "download/grid.xml";
  /** References to file content in the Product */
  public static final String INFO_XML_ATTACHMENT = "download/info.xml";

  // The files below have been decided to be unsupported at this time to
  // encourage
  // adoption of grid.xml by all networks.
  // public static final String GRID_XYZ_ATTACHMENT = "download/grid.xyz.zip";
  // public static final String STATIONLIST_XML_ATTACHMENT =
  // "download/stationlist.xml";
  /** Invisible attachment */
  public static final String INVISIBLE_ATTACHMENT = ".invisible";

  /** A suffix added to all event codes for scenarios */
  public static final String SCENARIO_ID_SUFFIX = "_se";

  // Map types
  /** Map type - actual */
  public static final String ACTUAL = "ACTUAL";
  /** Map type - scenario */
  public static final String SCENARIO = "SCENARIO";
  /** Map type - test */
  public static final String TEST = "TEST";

  /** key in info.xml for maximum mmi */
  public static final String MAXIMUM_MMI_INFO_KEY = "mi_max";
  /** Property for max MMI */
  public static final String MAXIMUM_MMI_PROPERTY = "maxmmi";

  /**
   * @param product the base product to be converted to a ShakeMap product
   */
  public ShakeMap(final Product product) {
    super(product);

    // prefer grid attachment
    Content gridxml = product.getContents().get(GRID_XML_ATTACHMENT);
    if (gridxml != null) {
      InputStream gridXmlIn = null;
      try {
        // parse grid.xml
        GridXMLHandler gridxmlHandler = new GridXMLHandler();
        gridXmlIn = gridxml.getInputStream();
        HashMap<String, String> grid = gridxmlHandler.parse(gridXmlIn);
        // parse through hash maps to set shakemap properties
        this.setGridXMLProperties(grid);
      } catch (Exception e) {
        // error parsing grid
        LOGGER.log(Level.WARNING, "error parsing grid.xml", e);
      } finally {
        StreamUtils.closeStream(gridXmlIn);
      }
    }

    Content infoxml = product.getContents().get(INFO_XML_ATTACHMENT);
    if (infoxml != null) {
      InputStream infoXmlIn = null;
      try {
        // parse info.xml
        InfoXMLHandler infoxmlHandler = new InfoXMLHandler();
        infoXmlIn = infoxml.getInputStream();
        HashMap<String, String> info = infoxmlHandler.parse(infoXmlIn);
        // parse through hash maps to set shakemap properties
        this.setInfoXMLProperties(info);
      } catch (Exception e) {
        LOGGER.log(Level.WARNING, "error parsing info.xml", e);
      } finally {
        StreamUtils.closeStream(infoXmlIn);
      }
    }

    /*
     * else { // At this time we are disabling all non-grid.xml functionality // as
     * all shakemaps sent in should have a grid.xml file.
     *
     * //otherwise try gridXYZ (has most) + stationlist (has depth) source =
     * product.getContents().get(GRID_XYZ_ATTACHMENT); if (source != null) {
     * GridXYZHandler handler = new GridXYZHandler(this); try {
     * handler.parse(source.getInputStream()); } catch (Exception e) { //error
     * parsing gridxyz throw new IllegalArgumentException(e); } }
     *
     * source = product.getContents().get(STATIONLIST_XML_ATTACHMENT); if (source !=
     * null) { StationlistXMLHandler handler = new StationlistXMLHandler(this); try
     * { handler.parse(source.getInputStream()); } catch (Exception e) { //error
     * parsing stationlist throw new IllegalArgumentException(e); } } }
     */
  }

  /**
   * @param gridXML shakemap properties hash keyed by grid.xml attribute name
   */
  public void setGridXMLProperties(HashMap<String, String> gridXML) {
    String depth;
    String eventDescription;
    String eventId;
    String eventSource;
    String eventSourceCode;
    String eventTime;
    String eventType;
    String latitude;
    String longitude;
    String magnitude;
    String mapStatus;
    String maximumLatitude;
    String maximumLongitude;
    String minimumLatitude;
    String minimumLongitude;
    String processTimestamp;
    String version;

    // eventId
    eventSource = gridXML.get(GridXMLHandler.EVENT_NETWORK_XML);
    eventSourceCode = gridXML.get(GridXMLHandler.EVENT_ID_XML);
    eventId = eventSource + eventSourceCode;

    if (valueIsEmpty(getEventId(), eventId)) {
      setEventId(eventSource, eventSourceCode);
    }

    // less preferred eventId (if not already set)
    eventSource = gridXML.get(GridXMLHandler.SHAKEMAPGRID_ORIGINATOR_XML);
    eventSourceCode = gridXML.get(GridXMLHandler.SHAKEMAPGRID_ID_XML);
    eventId = eventSource + eventSourceCode;

    if (valueIsEmpty(getEventId(), eventId)) {
      setEventId(eventSource, eventSourceCode);
    }

    // ShakeMap Metadata
    processTimestamp = gridXML.get(GridXMLHandler.SHAKEMAPGRID_TIMESTAMP_XML);
    if (valueIsEmpty(XmlUtils.formatDate(getProcessTimestamp()), processTimestamp)) {
      setProcessTimestamp(XmlUtils.getDate(processTimestamp));
    }

    version = gridXML.get(GridXMLHandler.SHAKEMAPGRID_VERSION_XML);
    if (valueIsEmpty(getVersion(), version)) {
      setVersion(version);
    }

    eventType = gridXML.get(GridXMLHandler.SHAKEMAPGRID_EVENT_TYPE_XML);
    if (valueIsEmpty(getEventType(), eventType)) {
      setEventType(eventType);
    }

    mapStatus = gridXML.get(GridXMLHandler.SHAKEMAPGRID_EVENT_STATUS_XML);
    if (valueIsEmpty(getMapStatus(), mapStatus)) {
      setMapStatus(mapStatus);
    }

    // ShakeMap Grid
    minimumLongitude = gridXML.get(GridXMLHandler.GRIDSPEC_LONMIN_XML);
    if (valueIsEmpty(getString(getMinimumLongitude()), minimumLongitude)) {
      setMinimumLongitude(getBigDecimal(minimumLongitude));
    }

    maximumLongitude = gridXML.get(GridXMLHandler.GRIDSPEC_LONMAX_XML);
    if (valueIsEmpty(getString(getMaximumLongitude()), maximumLongitude)) {
      setMaximumLongitude(getBigDecimal(maximumLongitude));
    }

    minimumLatitude = gridXML.get(GridXMLHandler.GRIDSPEC_LATMIN_XML);
    if (valueIsEmpty(getString(getMinimumLatitude()), minimumLatitude)) {
      setMinimumLatitude(getBigDecimal(minimumLatitude));
    }

    maximumLatitude = gridXML.get(GridXMLHandler.GRIDSPEC_LATMAX_XML);
    if (valueIsEmpty(getString(getMaximumLatitude()), maximumLatitude)) {
      setMaximumLatitude(getBigDecimal(maximumLatitude));
    }

    // Event
    latitude = gridXML.get(GridXMLHandler.EVENT_LATITUDE_XML);
    if (valueIsEmpty(getString(getLatitude()), latitude)) {
      setLatitude(getBigDecimal(latitude));
    }

    longitude = gridXML.get(GridXMLHandler.EVENT_LONGITUDE_XML);
    if (valueIsEmpty(getString(getLongitude()), longitude)) {
      setLongitude(getBigDecimal(longitude));
    }

    magnitude = gridXML.get(GridXMLHandler.EVENT_MAGNITUDE_XML);
    if (valueIsEmpty(getString(getMagnitude()), magnitude)) {
      setMagnitude(getBigDecimal(magnitude));
    }

    depth = gridXML.get(GridXMLHandler.EVENT_DEPTH_XML);
    if (valueIsEmpty(getString(getDepth()), depth)) {
      setDepth(getBigDecimal(depth));
    }

    eventTime = gridXML.get(GridXMLHandler.EVENT_TIMESTAMP_XML).replace("GMT", "Z").replace("UTC", "Z");
    if (valueIsEmpty(XmlUtils.formatDate(getEventTime()), eventTime)) {
      setEventTime(XmlUtils.getDate(eventTime));
    }

    eventDescription = gridXML.get(GridXMLHandler.EVENT_DESCRIPTION_XML);
    if (valueIsEmpty(getEventDescription(), eventDescription)) {
      setEventDescription(eventDescription);
    }

  };

  /**
   * @param infoXML shakemap properties hash keyed by info.xml attribute name
   */
  public void setInfoXMLProperties(HashMap<String, String> infoXML) {
    // read maxmmi from info.xml
    if (infoXML.containsKey(MAXIMUM_MMI_INFO_KEY)) {
      this.getProperties().put(MAXIMUM_MMI_PROPERTY, infoXML.get(MAXIMUM_MMI_INFO_KEY));
    }
  };

  /**
   * @param productValue the value from the PDL object
   * @param xmlValue     the value from the XML document
   * @return if the shakemap property is already set
   */
  public boolean valueIsEmpty(String productValue, String xmlValue) {
    // nothing to be set
    if (xmlValue == null) {
      return false;
    }
    // no value has been set
    if (productValue == null) {
      return true;
    }
    // value is set and values are different, log warning
    if (!productValue.equals(xmlValue)) {
      LOGGER.log(Level.FINE, "The ShakeMap property value: \"" + xmlValue + "\""
          + " does not match the product value: \"" + productValue + "\".");
    }
    return false;
  }

  /**
   * @param mapStatus the map status to set
   */
  public void setMapStatus(String mapStatus) {
    getProperties().put(MAP_STATUS_PROPERTY, mapStatus);
  }

  /**
   * @return the status of this map
   */
  public String getMapStatus() {
    return getProperties().get(MAP_STATUS_PROPERTY);
  }

  /**
   * @param eventType the event type to set
   */
  public void setEventType(String eventType) {
    getProperties().put(EVENT_TYPE_PROPERTY, eventType);
  }

  /**
   * @return the event type of this product as defined in ShakeMap
   */
  public String getEventType() {
    return getProperties().get(EVENT_TYPE_PROPERTY);
  }

  /**
   * @param processTimestamp the process timestamp to set
   */
  public void setProcessTimestamp(Date processTimestamp) {
    getProperties().put(PROCESS_TIMESTAMP_PROPERTY, XmlUtils.formatDate(processTimestamp));
  }

  /**
   * @return the process timestamp of this ShakeMap
   */
  public Date getProcessTimestamp() {
    return XmlUtils.getDate(getProperties().get(PROCESS_TIMESTAMP_PROPERTY));
  }

  /**
   * @return the event description text for this ShakeMap
   */
  public String getEventDescription() {
    return getProperties().get(EVENT_DESCRIPTION_PROPERTY);
  }

  /**
   * @param eventDescription the event description to set
   */
  public void setEventDescription(String eventDescription) {
    getProperties().put(EVENT_DESCRIPTION_PROPERTY, eventDescription);
  }

  /**
   * @return the minimum longitude boundary of this ShakeMap
   */
  public BigDecimal getMinimumLongitude() {
    return getBigDecimal(getProperties().get(MINIMUM_LONGITUDE_PROPERTY));
  }

  /**
   * @param minimumLongitude the minimum longitude to set
   */
  public void setMinimumLongitude(BigDecimal minimumLongitude) {
    getProperties().put(MINIMUM_LONGITUDE_PROPERTY, minimumLongitude.toPlainString());
  }

  /**
   * @return the maximum longitude boundary of this ShakeMap
   */
  public BigDecimal getMaximumLongitude() {
    return getBigDecimal(getProperties().get(MAXIMUM_LONGITUDE_PROPERTY));
  }

  /**
   * @param maximumLongitude the maximum longitude to set
   */
  public void setMaximumLongitude(BigDecimal maximumLongitude) {
    getProperties().put(MAXIMUM_LONGITUDE_PROPERTY, maximumLongitude.toPlainString());
  }

  /**
   * @return the minimum latitude boundary of this ShakeMap
   */
  public BigDecimal getMinimumLatitude() {
    return getBigDecimal(getProperties().get(MINIMUM_LATITUDE_PROPERTY));
  }

  /**
   * @param minimumLatitude the minimum latitude to set
   */
  public void setMinimumLatitude(BigDecimal minimumLatitude) {
    getProperties().put(MINIMUM_LATITUDE_PROPERTY, minimumLatitude.toPlainString());
  }

  /**
   * @return the maximum latitude boundary of this ShakeMap
   */
  public BigDecimal getMaximumLatitude() {
    return getBigDecimal(getProperties().get(MAXIMUM_LATITUDE_PROPERTY));
  }

  /**
   * @param maximumLatitude the maximum latitude to set
   */
  public void setMaximumLatitude(BigDecimal maximumLatitude) {
    getProperties().put(MAXIMUM_LATITUDE_PROPERTY, maximumLatitude.toPlainString());
  }

  /**
   * Returns String value as BigDecimal
   *
   * @param value to return as BigDecimal
   * @return a BigDecimal
   */
  protected BigDecimal getBigDecimal(String value) {
    if (value == null) {
      return null;
    }
    return new BigDecimal(value);
  }

  /**
   * Returns BigDecimal value as String
   *
   * @param value a BigDecimal
   * @return a string
   */
  protected String getString(BigDecimal value) {
    if (value == null) {
      return null;
    }
    return value.toString();
  }

}