package org.opensha2.eq.model;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static java.util.logging.Level.FINE;
import static org.opensha2.eq.model.SourceType.AREA;
import static org.opensha2.internal.Parsing.readDouble;
import static org.opensha2.internal.Parsing.readEnum;
import static org.opensha2.internal.Parsing.readInt;
import static org.opensha2.internal.Parsing.readString;
import static org.opensha2.internal.Parsing.stringToEnumWeightMap;
import static org.opensha2.internal.Parsing.stringToValueValueWeightMap;
import static org.opensha2.internal.SourceAttribute.FOCAL_MECH_MAP;
import static org.opensha2.internal.SourceAttribute.ID;
import static org.opensha2.internal.SourceAttribute.MAG_DEPTH_MAP;
import static org.opensha2.internal.SourceAttribute.MAX_DEPTH;
import static org.opensha2.internal.SourceAttribute.NAME;
import static org.opensha2.internal.SourceAttribute.RUPTURE_SCALING;
import static org.opensha2.internal.SourceAttribute.STRIKE;
import static org.opensha2.internal.SourceAttribute.TYPE;
import static org.opensha2.internal.SourceAttribute.WEIGHT;
import org.opensha2.data.Data;
import org.opensha2.eq.fault.FocalMech;
import org.opensha2.eq.fault.surface.RuptureScaling;
import org.opensha2.geo.LocationList;
import org.opensha2.internal.SourceElement;
import org.opensha2.mfd.IncrementalMfd;
import org.opensha2.mfd.MfdType;
import org.opensha2.mfd.Mfds;
import org.xml.sax.Attributes;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.helpers.DefaultHandler;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
import java.util.NavigableMap;
import java.util.logging.Logger;
import javax.xml.parsers.SAXParser;
/*
* Non-validating area source parser. SAX parser 'Attributes' are stateful and
* cannot be stored. This class is not thread safe.
*
* @author Peter Powers
*/
@SuppressWarnings("incomplete-switch")
class AreaParser extends DefaultHandler {
private final Logger log = Logger.getLogger(AreaParser.class.getName());
private final SAXParser sax;
private boolean used = false;
private Locator locator;
// Default MFD data
private boolean parsingDefaultMFDs = false;
private MfdHelper.Builder mfdHelperBuilder;
private MfdHelper mfdHelper;
private GmmSet gmmSet;
private ModelConfig config;
private AreaSourceSet sourceSet;
private AreaSourceSet.Builder sourceSetBuilder;
private AreaSource.Builder sourceBuilder;
// Node locations are the only text content in source files
private boolean readingBorder = false;
private StringBuilder borderBuilder = null;
// Used to when validating depths in magDepthMap
SourceType type = AREA;
private AreaParser(SAXParser sax) {
this.sax = checkNotNull(sax);
}
static AreaParser create(SAXParser sax) {
return new AreaParser(sax);
}
AreaSourceSet parse(InputStream in, GmmSet gmmSet, ModelConfig config) throws SAXException,
IOException {
checkState(!used, "This parser has expired");
this.gmmSet = gmmSet;
this.config = config;
sax.parse(in, this);
used = true;
return sourceSet;
}
@Override
public void startElement(String uri, String localName, String qName, Attributes atts)
throws SAXException {
SourceElement e = null;
try {
e = SourceElement.fromString(qName);
} catch (IllegalArgumentException iae) {
throw new SAXParseException("Invalid element <" + qName + ">", locator, iae);
}
switch (e) {
case AREA_SOURCE_SET:
String name = readString(NAME, atts);
int id = readInt(ID, atts);
double weight = readDouble(WEIGHT, atts);
sourceSetBuilder = new AreaSourceSet.Builder()
.name(name)
.id(id)
.weight(weight);
sourceSetBuilder.gmms(gmmSet);
if (log.isLoggable(FINE)) {
log.fine("");
log.fine(" Name: " + name);
log.fine(" Weight: " + weight);
}
mfdHelperBuilder = MfdHelper.builder();
mfdHelper = mfdHelperBuilder.build(); // dummy; usually overwritten
break;
case DEFAULT_MFDS:
parsingDefaultMFDs = true;
break;
case SOURCE:
String srcName = readString(NAME, atts);
int srcId = readInt(ID, atts);
sourceBuilder = new AreaSource.Builder()
.name(srcName)
.id(srcId)
.gridScaling(config.areaGridScaling);
log.fine(" Source: " + srcName);
break;
case SOURCE_PROPERTIES:
// identical to shared source properties in grid sources
// but with one element for each area source
String depthMapStr = readString(MAG_DEPTH_MAP, atts);
NavigableMap<Double, Map<Double, Double>> depthMap =
stringToValueValueWeightMap(depthMapStr);
double maxDepth = readDouble(MAX_DEPTH, atts);
String mechMapStr = readString(FOCAL_MECH_MAP, atts);
Map<FocalMech, Double> mechMap = stringToEnumWeightMap(mechMapStr, FocalMech.class);
RuptureScaling rupScaling = readEnum(RUPTURE_SCALING, atts, RuptureScaling.class);
double strike = readDouble(STRIKE, atts);
sourceBuilder
.depthMap(depthMap, type)
.maxDepth(maxDepth, type)
.mechs(mechMap)
.ruptureScaling(rupScaling);
// first validate strike by setting it in builder
sourceBuilder.strike(strike);
// then possibly override type if strike is set
PointSourceType type = config.pointSourceType;
if (!Double.isNaN(strike)) {
type = PointSourceType.FIXED_STRIKE;
}
sourceBuilder.sourceType(type);
if (log.isLoggable(FINE)) {
log.fine(" Depths: " + depthMap);
log.fine(" Max depth: " + maxDepth);
log.fine("Focal mechs: " + mechMap);
log.fine("Rup scaling: " + rupScaling);
log.fine(" Strike: " + strike);
String typeOverride = (type != config.pointSourceType) ? " (" +
config.pointSourceType + " overridden)" : "";
log.fine("Source type: " + type + typeOverride);
}
break;
case INCREMENTAL_MFD:
if (parsingDefaultMFDs) {
mfdHelperBuilder.addDefault(atts);
break;
}
sourceBuilder.mfd(buildMfd(atts));
break;
case BORDER:
readingBorder = true;
borderBuilder = new StringBuilder();
break;
}
}
@Override
public void endElement(String uri, String localName, String qName)
throws SAXException {
SourceElement e = null;
try {
e = SourceElement.fromString(qName);
} catch (IllegalArgumentException iae) {
throw new SAXParseException("Invalid element <" + qName + ">", locator, iae);
}
switch (e) {
case DEFAULT_MFDS:
parsingDefaultMFDs = false;
mfdHelper = mfdHelperBuilder.build();
break;
case BORDER:
readingBorder = false;
sourceBuilder.border(LocationList.fromString(borderBuilder.toString()));
break;
case SOURCE:
AreaSource source = sourceBuilder.build();
sourceSetBuilder.source(source);
if (log.isLoggable(FINE)) {
log.fine(" Size: " + source.size());
log.finer(" Mag count: " + source.depthModel.magMaster.size());
log.finer(" Mag master: " + source.depthModel.magMaster);
log.finer(" MFD index: " + source.depthModel.magDepthIndices);
log.finer(" Depths: " + source.depthModel.magDepthDepths);
log.finer(" Weights: " + source.depthModel.magDepthWeights);
log.fine("");
}
break;
case AREA_SOURCE_SET:
sourceSet = sourceSetBuilder.build();
break;
}
}
@Override
public void characters(char ch[], int start, int length) throws SAXException {
if (readingBorder) {
borderBuilder.append(ch, start, length);
}
}
@Override
public void setDocumentLocator(Locator locator) {
this.locator = locator;
}
/*
* TODO Area sources should support multiple different mfds TODO Currently
* taking only the first entry in defaults
*/
private IncrementalMfd buildMfd(Attributes atts) {
MfdType type = readEnum(TYPE, atts, MfdType.class);
switch (type) {
case GR:
MfdHelper.GR_Data grData = mfdHelper.grData(atts).get(0);
int nMagGR = Mfds.magCount(grData.mMin, grData.mMax, grData.dMag);
IncrementalMfd mfdGR = Mfds.newGutenbergRichterMFD(grData.mMin, grData.dMag,
nMagGR, grData.b, 1.0);
mfdGR.scaleToIncrRate(grData.mMin, Mfds.incrRate(grData.a, grData.b, grData.mMin) *
grData.weight);
return mfdGR;
case INCR:
MfdHelper.IncrData incrData = mfdHelper.incrementalData(atts).get(0);
IncrementalMfd mfdIncr = Mfds.newIncrementalMFD(incrData.mags,
Data.multiply(incrData.weight, incrData.rates));
return mfdIncr;
case SINGLE:
MfdHelper.SingleData singleData = mfdHelper.singleData(atts).get(0);
return Mfds.newSingleMFD(singleData.m, singleData.rate * singleData.weight,
singleData.floats);
case GR_TAPER:
MfdHelper.TaperData taperData = mfdHelper.taperData(atts).get(0);
int nMagTaper = Mfds.magCount(taperData.mMin, taperData.mMax, taperData.dMag);
IncrementalMfd mfdTaper = Mfds.newTaperedGutenbergRichterMFD(taperData.mMin,
taperData.dMag, nMagTaper, taperData.a, taperData.b, taperData.cMag,
taperData.weight);
return mfdTaper;
default:
throw new IllegalStateException(type + " not yet implemented");
}
}
}