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();
	}

}