package org.opensha2.calc; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; import static org.opensha2.data.Data.checkInRange; import static org.opensha2.internal.GeoJson.validateProperty; import org.opensha2.geo.Location; import org.opensha2.gmm.GroundMotionModel; import org.opensha2.internal.GeoJson; import org.opensha2.util.Named; import org.opensha2.util.NamedLocation; import com.google.common.base.Strings; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Range; import com.google.gson.JsonArray; import com.google.gson.JsonDeserializationContext; import com.google.gson.JsonDeserializer; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import java.lang.reflect.Type; import java.util.Set; /** * Site characteristics container. Take note of default values; the minimum * information required to create a {@code Site} is its location. Not all * {@link GroundMotionModel}s will use all fields and additional fields may be * added at any time in the future. * * <p>Terms:<ul> * * <li><b>{@code Vs30}:</b> Average shear wave velocity between down to a depth * of 30 m, in units of m/s. This value may be <i>inferred</i> or * <i>measured</i> (see {@link #vsInferred}).</li> * * <li><b>{@code z1.0}:</b> Depth to a shear wave velocity of 1.0 km/sec, in * units of km.</li> * * <li><b>{@code z2.5}:</b> Depth to a shear wave velocity of 2.5 km/sec, in * units of km.</li> * * </ul> * * <p>Both {@code z1.0} and {@code z2.5}, collectively referred to as * <i>basin-terms</i>, have default values of {@code Double.NaN}. When supplied * with the default, those {@link GroundMotionModel}s that support basin terms * will use an author defined model, typically based on {@code Vs30}, to compute * basin-amplifications. * * @author Peter Powers */ public class Site implements Named { /** The name used for a {@code Site} with no supplied name. */ public static final String NO_NAME = "Unnamed"; /** Default {@link #vs30} value: {@code 760 m/s}. */ public static final double VS_30_DEFAULT = 760.0; /** Supported {@link #vs30} values: {@code [150..2000] m/s}. */ public static final Range<Double> VS30_RANGE = Range.closed(150.0, 2000.0); /** Default {@link #vsInferred} inferred value: {@code true}. */ public static final boolean VS_INF_DEFAULT = true; /** * Default {@link #z1p0} value: {@code NaN} <br>({@link GroundMotionModel}s * will use a default value or model) */ public static final double Z1P0_DEFAULT = Double.NaN; /** Supported {@link #z1p0} values: {@code [0..2] km}. */ public static final Range<Double> Z1P0_RANGE = Range.closed(0.0, 2.0); /** * Default {@link #z2p5} value: {@code NaN} <br>({@link GroundMotionModel}s * will use a default value or model) */ public static final double Z2P5_DEFAULT = Double.NaN; /** Supported {@link #z2p5} values: {@code [0..5] km}. */ public static final Range<Double> Z2P5_RANGE = Range.closed(0.0, 5.0); /** The site name. */ public final String name; /** * The location of this site. This is the only field that must be explicitely * set in a {@link Builder}. */ public final Location location; /** * The average shear-wave velocity down to 30 meters depth. * * <p>Default: {@code 760.0 m/s} */ public final double vs30; /** * Whether Vs30 was inferred, {@code true}, or measured, {@code false}. * * <p>Default: {@code true} (inferred) */ public final boolean vsInferred; /** * Depth to the shear-wave velocity horizon of 1.0 km/s, in km. * * <p>Default: {@code NaN} <br>({@link GroundMotionModel}s will use a default * value or model) */ public final double z1p0; /** * Depth to the shear-wave velocity horizon of 2.5 km/s, in km. * * <p>Default: {@code NaN} <br>({@link GroundMotionModel}s will use a default * value or model) */ public final double z2p5; private Site(String name, Location location, double vs30, boolean vsInferred, double z1p0, double z2p5) { this.name = name; this.location = location; this.vs30 = vs30; this.vsInferred = vsInferred; this.z1p0 = z1p0; this.z2p5 = z2p5; } @Override public String toString() { return new StringBuilder(Strings.padEnd(name, 28, ' ')) .append(String.format("%.3f %.3f ", location.lon(), location.lat())) .append(paramString(vs30, vsInferred, z1p0, z2p5)) .toString(); } private static String paramString(double vs30, boolean vsInf, double z1p0, double z2p5) { return String.format( "Vs30=%s %s Z1.0=%s Z2.5=%s", vs30, vsInf ? "inferred " : "measured ", z1p0, z2p5); } @Override public String name() { return name; } /** * Return a fresh {@link Builder}. */ public static Builder builder() { return new Builder(); } /** * Return a fresh {@link Builder} configured with the supplied defaults. */ static Builder builder(CalcConfig defaults) { return new Builder(defaults); } /** * A reusable {@code Site} builder. In the absence of specifying any site * characteristics other than location (required), a default {@code Site} is * returned by {@code build()}. Builder instances may be obtained via * {@link Site#builder()}. * * @see Site for default values */ public static class Builder { private String name = NO_NAME; private Location location; private double vs30 = VS_30_DEFAULT; private boolean vsInferred = VS_INF_DEFAULT; private double z1p0 = Z1P0_DEFAULT; private double z2p5 = Z2P5_DEFAULT; private Builder() {} private Builder(CalcConfig config) { vs30(config.site.vs30); vsInferred(config.site.vsInferred); z1p0(config.site.z1p0); z2p5(config.site.z2p5); } /** * The name of the {@code Site}. Prior to setting, the supplied name is * stripped of commas and truncated at 72 characters. */ public Builder name(String name) { this.name = cleanName(checkNotNull(name)); return this; } /** The location of the {@code Site}. */ public Builder location(Location loc) { this.location = checkNotNull(loc); return this; } /** * The location of the {@code Site}; also sets the {@code Site} name. */ public Builder location(NamedLocation namedLoc) { this.location = checkNotNull(namedLoc).location(); this.name = namedLoc.toString(); return this; } /** The location of the {@code Site}. */ public Builder location(double lat, double lon) { this.location = Location.create(lat, lon); return this; } /** The average shear-wave velocity down to 30 meters depth. */ public Builder vs30(double vs30) { this.vs30 = checkInRange(VS30_RANGE, Site.Key.VS30, vs30); return this; } /** * Whether Vs30 was inferred, {@code true}, or measured {@code false}. */ public Builder vsInferred(boolean vsInferred) { this.vsInferred = vsInferred; return this; } /** Depth to the shear-wave velocity horizon of 1.0 km/s, in km. */ public Builder z1p0(double z1p0) { if (Double.isNaN(z1p0)) { this.z1p0 = z1p0; } else { this.z1p0 = checkInRange(Z1P0_RANGE, Site.Key.Z1P0, z1p0); } return this; } /** Depth to the shear-wave velocity horizon of 2.5 km/s, in km. */ public Builder z2p5(double z2p5) { if (Double.isNaN(z2p5)) { this.z2p5 = z2p5; } else { this.z2p5 = checkInRange(Z2P5_RANGE, Site.Key.Z2P5, z2p5); } return this; } /** * Return a string reflecting the current site parameter state of this * builder. */ String state() { return paramString(vs30, vsInferred, z1p0, z2p5); } /** * Build the {@code Site}. */ public Site build() { checkState(location != null, "Site location not set"); return new Site(name, location, vs30, vsInferred, z1p0, z2p5); } } private static final int MAX_NAME_LENGTH = 72; private static String cleanName(String name) { name = name.replaceAll(",", ""); return name.length() > MAX_NAME_LENGTH ? name.substring(0, MAX_NAME_LENGTH) : name; } /* Json and csv serialization keys */ static final class Key { static final String NAME = "name"; static final String LAT = "lat"; static final String LON = "lon"; static final String VS30 = "vs30"; static final String VS_INF = "vsInf"; static final String Z1P0 = "z1p0"; static final String Z2P5 = "z2p5"; } /* Json and csv serialization key set */ static final Set<String> KEYS = ImmutableSet.of( Key.NAME, Key.LAT, Key.LON, Key.VS30, Key.VS_INF, Key.Z1P0, Key.Z2P5); /* * Custom deserializer that takes care of several issues with sites. * Specifically, JSON prohibits the use of NaN, which is the default value for * z1p0 and z2p5, and so these two fields may not be set. Users have been * notified that as long as no z1p0 or z2p5 value has been supplied in any * JSON, the default will be used. */ static final class Deserializer implements JsonDeserializer<Site> { final CalcConfig defaults; Deserializer(CalcConfig defaults) { this.defaults = defaults; } @Override public Site deserialize( JsonElement json, Type type, JsonDeserializationContext context) { JsonObject feature = json.getAsJsonObject(); validateProperty(feature, GeoJson.Key.TYPE, GeoJson.Value.FEATURE); JsonObject geometry = feature.getAsJsonObject(GeoJson.Key.GEOMETRY); validateProperty(geometry, GeoJson.Key.TYPE, GeoJson.Value.POINT); JsonArray coordinates = feature .getAsJsonObject(GeoJson.Key.GEOMETRY) .getAsJsonArray(GeoJson.Key.COORDINATES); Builder builder = Site.builder(defaults).location( coordinates.get(1).getAsDouble(), coordinates.get(0).getAsDouble()); JsonObject properties = feature.getAsJsonObject(GeoJson.Key.PROPERTIES); JsonElement name = properties.get(GeoJson.Properties.Key.TITLE); if (name != null) { builder.name(name.getAsString()); } JsonElement vs30 = properties.get(Site.Key.VS30); if (vs30 != null) { builder.vs30(vs30.getAsDouble()); } JsonElement vsInf = properties.get(Site.Key.VS_INF); if (vsInf != null) { builder.vsInferred(vs30.getAsBoolean()); } JsonElement z1p0 = properties.get(Site.Key.Z1P0); if (z1p0 != null) { builder.z1p0(z1p0.getAsDouble()); } JsonElement z2p5 = properties.get(Site.Key.Z2P5); if (z2p5 != null) { builder.z2p5(z2p5.getAsDouble()); } return builder.build(); } } }