Example #1
0
def plot_rupture_wire3d(rupture, ax=None):
    """
    Method for making a simple representation of a Rupture instance.
    This method draws the outline of each quadrilateral in 3D.

    Args:
        rupture: A Rupture instance.
        ax: A matplotlib axis (optional).

    Returns:
        Matplotlib axis.
    """

    if ax is None:
        fig = plt.figure()
        ax = fig.add_subplot(111, projection='3d')
    else:
        if 'xlim3d' not in list(ax.properties().keys()):
            raise ShakeLibException(
                'Non-3d axes object passed to plot() method.')
    for quad in rupture.getQuadrilaterals():
        x = [p.longitude for p in quad]
        x.append(x[0])
        y = [p.latitude for p in quad]
        y.append(y[0])
        z = [-p.depth for p in quad]
        z.append(z[0])
        ax.plot(x, y, z, 'k')
        ax.set_xlabel('Longitude')
        ax.set_ylabel('Latitude')
        ax.set_zlabel('Depth')

    return ax
Example #2
0
def _rotate_polygon(p):
    """
    This is a method to rotate a polygon, which is used to try to
    fix incorrectly specified polygons.

    Args:
        p (list):
            A list of lon/lat/depth lists.

    Returns:
        list: Same as before but the points have been incremented
        by one in position.

    """
    # Convert to numpy array for better indexing support
    parray = np.array(p)

    # Ensure the polygon is closed
    if not np.array_equal(parray[-1, :], parray[0, :]):
        raise ShakeLibException('Rupture file has unclosed segments.')

    # Drop last point
    parray = parray[0:-1, :]

    # Rotate by putting first at end
    parray = np.append(parray[1:, :],
                       np.reshape(parray[0, :], (1, -1)),
                       axis=0)

    # Put new first point onto the end so that it is closed.
    parray = np.append(parray, np.reshape(parray[0, :], (1, -1)), axis=0)

    # Turn numpy array back into a list
    polygon = parray.tolist()
    return polygon
Example #3
0
    def _load(vs30File,
              samplegeodict=None,
              resample=False,
              method='linear',
              doPadding=False,
              padValue=np.nan):
        try:
            vs30grid = GMTGrid.load(vs30File,
                                    samplegeodict=samplegeodict,
                                    resample=resample,
                                    method=method,
                                    doPadding=doPadding,
                                    padValue=padValue)
        except Exception as msg1:
            try:
                vs30grid = GDALGrid.load(vs30File,
                                         samplegeodict=samplegeodict,
                                         resample=resample,
                                         method=method,
                                         doPadding=doPadding,
                                         padValue=padValue)
            except Exception as msg2:
                msg = 'Load failure of %s - error messages: "%s"\n "%s"' % (
                    vs30File, str(msg1), str(msg2))
                raise ShakeLibException(msg)

        if vs30grid.getData().dtype != np.float64:
            vs30grid.setData(vs30grid.getData().astype(np.float64))

        return vs30grid
Example #4
0
 def _getFileGeoDict(fname):
     geodict = None
     try:
         geodict = get_file_geodict(fname)
     except Exception as msg1:
         msg = 'File geodict failure with %s - error messages: '\
             '"%s"' % (fname, str(msg1))
         raise ShakeLibException(msg)
     return geodict
Example #5
0
 def _getFileGeoDict(fname):
     geodict = None
     try:
         geodict, t = GMTGrid.getFileGeoDict(fname)
     except Exception as msg1:
         try:
             geodict, t = GDALGrid.getFileGeoDict(fname)
         except Exception as msg2:
             msg = 'File geodict failure with %s - error messages: '\
                   '"%s"\n "%s"' % (fname, str(msg1), str(msg2))
             raise ShakeLibException(msg)
     return geodict
Example #6
0
    def fromFuncs(cls, gmpe, gmice):
        """
        Creates a new VirtualIPE object with the specified MultiGMPE and
        GMICE. There is no default constructor, you must use this method.

        Args:
            gmpe: An instance of the MultiGMPE object.
            gmice: An instance of a GMICE object.

        Returns:
            :class:`VirtualIPE`: A new instance of a VirtualIPE object.

        """
        self = cls()
        self.gmpe = gmpe
        self.gmice = gmice

        if (gmpe.ALL_GMPES_HAVE_PGV is True and
                PGV in gmice.DEFINED_FOR_INTENSITY_MEASURE_TYPES):
            self.imt = PGV()
        elif (PGA in gmpe.DEFINED_FOR_INTENSITY_MEASURE_TYPES and
              PGA in gmice.DEFINED_FOR_INTENSITY_MEASURE_TYPES):
            self.imt = PGA()
        elif (SA in gmpe.DEFINED_FOR_INTENSITY_MEASURE_TYPES and
              SA in gmice.DEFINED_FOR_INTENSITY_MEASURE_TYPES):
            self.imt = SA(1.0)
        else:
            raise ShakeLibException(
                'The supplied GMPE and GMICE do not have a common IMT'
            )

        self.DEFINED_FOR_STANDARD_DEVIATION_TYPES = \
            gmpe.DEFINED_FOR_STANDARD_DEVIATION_TYPES.copy()

        self.REQUIRES_DISTANCES = gmpe.REQUIRES_DISTANCES.copy()
        self.REQUIRES_RUPTURE_PARAMETERS = \
            gmpe.REQUIRES_RUPTURE_PARAMETERS.copy()
        self.REQUIRES_SITES_PARAMETERS = \
            gmpe.REQUIRES_SITES_PARAMETERS.copy()
        self.DEFINED_FOR_INTENSITY_MEASURE_COMPONENT = \
            copy.copy(gmpe.DEFINED_FOR_INTENSITY_MEASURE_COMPONENT)
        self.DEFINED_FOR_TECTONIC_REGION_TYPE = \
            copy.copy(gmpe.DEFINED_FOR_TECTONIC_REGION_TYPE)

        return self
Example #7
0
    def getSitesContext(self, lldict=None, rock_vs30=None):
        """
        Create a SitesContext object by sampling the current Sites object.

        Args:
            lldict: Either

                - None, in which case the SitesContext for the complete Sites
                  grid is returned, or
                - A location dictionary (elements are 'lats' and 'lons' and
                  each is a numpy array). Each element must have the same
                  shape. In this case the SitesContext for these locaitons is
                  returned.

            rock_vs30: Either

                - None, in which case the SitesContext will reflect the Vs30
                  grid in the Sites instance, or
                - A float for the rock Vs30 value, in which case the
                  SitesContext will be constructed for this constant Vs30
                  value.

        Returns:
            SitesContext object.

        Raises:
            ShakeLibException: When lat/lon input sequences do not share
                dimensionality.

        """  # noqa

        sctx = SitesContext()

        if lldict is not None:
            lats = lldict['lats']
            lons = lldict['lons']
            latshape = lats.shape
            lonshape = lons.shape
            if latshape != lonshape:
                msg = 'Input lat/lon arrays must have the same dimensions'
                raise ShakeLibException(msg)

            if rock_vs30 is not None:
                tmp = self._Vs30.getValue(
                    lats, lons, default=self._defaultVs30)
                sctx.vs30 = np.ones_like(tmp) * rock_vs30
            else:
                sctx.vs30 = self._Vs30.getValue(
                    lats, lons, default=self._defaultVs30)
            sctx.lats = lats
            sctx.lons = lons
        else:
            sctx.lats = self._lats.copy()
            sctx.lons = self._lons.copy()
            if rock_vs30 is not None:
                sctx.vs30 = np.full_like(self._Vs30.getData(), rock_vs30)
            else:
                sctx.vs30 = self._Vs30.getData().copy()

        sctx = Sites._addDepthParameters(sctx)

        # For ShakeMap purposes, vs30 measured is always Fales
        sctx.vs30measured = np.zeros_like(sctx.vs30, dtype=bool)

        # Backarc should be a numpy array
        if lldict is not None:
            backarcgrid = Grid2D(self._backarc, self._Vs30.getGeoDict())
            sctx.backarc = backarcgrid.getValue(lats, lons, default=False)
        else:
            sctx.backarc = self._backarc.copy()

        return sctx
Example #8
0
def text_to_json(file, new_format=True):
    """
    Read in old or new ShakeMap 3 textfile rupture format and convert to
    GeoJSON.

    This will handle ShakeMap3.5-style fault text files, which can have the
    following format:
     - # at the top indicates a reference.
     - Lines beginning with a > indicate the end of one segment and the
       beginning of another.
     - Coordinates are specified in lat,lon,depth order.
     - Coordinates can be separated by commas or spaces.
     - Vertices can be specified in top-edge or bottom-edge first order.

    Args:
        file (str):
            Path to rupture file OR file-like object in GMT
            psxy format, where:

                * Rupture vertices are space/comma separated lat, lon, depth
                  triplets on a single line.
                * Rupture groups are separated by lines containing ">"
                * Rupture groups must be closed.
                * Verticies within a rupture group must start along the top
                  edge and move in the strike direction then move to the bottom
                  edge and move back in the opposite direction.

        new_format (bool):
            Indicates whether text rupture format is
            "old" (lat, lon, depth) or "new" (lon, lat, depth) style.

    Returns:
        dict: GeoJSON rupture dictionary.

    """
    isfile = False
    if hasattr(file, 'read'):
        f = file
    else:
        f = open(file, 'rt', encoding="latin-1")
        isfile = True

    reference = ''
    polygons = []
    polygon = []
    for line in f.readlines():
        if not len(line.strip()):
            continue

        if line.strip().startswith('#'):
            # Get reference string
            reference += line.strip().replace('#', '')
            continue

        if line.strip().startswith('>'):
            if not len(polygon):
                continue
            polygons.append(polygon)
            polygon = []
            continue

        # first try to split on whitespace
        parts = line.split()
        if len(parts) == 1:
            if new_format:
                raise ShakeLibException(
                    'Rupture file %s has unspecified delimiters.' % file)
            parts = line.split(',')
            if len(parts) == 1:
                raise ShakeLibException(
                    'Rupture file %s has unspecified delimiters.' % file)

        if len(parts) != 3:
            msg = 'Rupture file %s is not in lat, lon, depth format.'
            if new_format:
                'Rupture file %s is not in lon, lat, depth format.'
            raise ShakeLibException(msg % file)

        parts = [float(p) for p in parts]
        if not new_format:
            old_parts = parts.copy()
            parts[0] = old_parts[1]
            parts[1] = old_parts[0]
        polygon.append(parts)

    if len(polygon):
        polygons.append(polygon)

    if isfile:
        f.close()

    # Try to fix polygons
    original_polygons = polygons.copy()
    fixed = []
    n_polygons = len(polygons)
    for i in range(n_polygons):
        n_verts = len(polygons[i])
        success = False
        for j in range(n_verts - 1):
            try:
                _check_polygon(polygons[i])
                success = True
                break
            except ValueError:
                polygons[i] = _rotate_polygon(polygons[i])
        if success:
            fixed.append(True)
        else:
            fixed.append(False)

    if not all(fixed):
        polygons = original_polygons

    json_dict = {
        "type":
        "FeatureCollection",
        "metadata": {
            'reference': reference
        },
        "features": [{
            "type": "Feature",
            "properties": {
                "rupture type": "rupture extent"
            },
            "geometry": {
                "type": "MultiPolygon",
                "coordinates": [polygons]
            }
        }]
    }
    validate_json(json_dict)

    return json_dict
Example #9
0
def test_exception():
    exception = ShakeLibException('Test exception')
    # Check __str__ override is correct
    assert str(exception) == "'Test exception'"
Example #10
0
def text_to_json(file):
    """
    Read in old ShakeMap 3 textfile rupture format and convert to GeoJSON.

    Args:
        rupturefile (srt): Path to rupture file OR file-like object in GMT
            psxy format, where:

                * Rupture vertices are space separated lat, lon, depth triplets
                  on a single line.
                * Rupture groups are separated by lines containing ">"
                * Rupture groups must be closed.
                * Verticies within a rupture group must start along the top
                  edge and move in the strike direction then move to the bottom
                  edge and move back in the opposite direction.

    Returns:
        dict: GeoJSON rupture dictionary.

    """

    # -------------------------------------------------------------------------
    # First read in the data
    # -------------------------------------------------------------------------
    x = []
    y = []
    z = []
    isFile = False
    if isinstance(file, str):
        isFile = True
        file = open(file, 'rt')
        lines = file.readlines()
    else:
        lines = file.readlines()
    reference = ''
    for line in lines:
        sline = line.strip()
        if sline.startswith('#'):
            reference += sline
            continue
        if sline.startswith('>'):
            if len(x):  # start of new line segment
                x.append(np.nan)
                y.append(np.nan)
                z.append(np.nan)
                continue
            else:  # start of file
                continue
        if not len(sline.strip()):
            continue
        parts = sline.split()
        if len(parts) < 3:
            raise ShakeLibException('Rupture file %s has no depth values.' %
                                    file)
        y.append(float(parts[0]))
        x.append(float(parts[1]))
        z.append(float(parts[2]))
    if isFile:
        file.close()

    # Construct GeoJSON dictionary

    coords = []
    poly = []
    for lon, lat, dep in zip(x, y, z):
        if np.isnan(lon):
            coords.append(poly)
            poly = []
        else:
            poly.append([lon, lat, dep])
    if poly != []:
        coords.append(poly)

    d = {
        "type":
        "FeatureCollection",
        "metadata": {
            'reference': reference
        },
        "features": [{
            "type": "Feature",
            "properties": {
                "rupture type": "rupture extent"
            },
            "geometry": {
                "type": "MultiPolygon",
                "coordinates": [coords]
            }
        }]
    }
    return d
Example #11
0
def get_distance(methods, lat, lon, dep, rupture, dx=0.5):
    """
    Calculate distance using any one of a number of distance measures.
    One of quadlist OR hypo must be specified. The following table gives
    the allowed distance strings and a description of each.

    +--------+----------------------------------------------------------+
    | String | Description                                              |
    +========+==========================================================+
    | repi   | Distance to epicenter.                                   |
    +--------+----------------------------------------------------------+
    | rhypo  | Distance to hypocenter.                                  |
    +--------+----------------------------------------------------------+
    | rjb    | Joyner-Boore distance; this is closest distance to the   |
    |        | surface projection of the rupture plane.                 |
    +--------+----------------------------------------------------------+
    | rrup   | Rupture distance; closest distance to the rupture plane. |
    +--------+----------------------------------------------------------+
    | rx     | Strike-normal distance; same as GC2 coordiante T.        |
    +--------+----------------------------------------------------------+
    | ry     | Strike-parallel distance; same as GC2 coordiante U, but  |
    |        | with a shift in origin definition. See Spudich and Chiou |
    |        | (2015) http://dx.doi.org/10.3133/ofr20151028.            |
    +--------+----------------------------------------------------------+
    | ry0    | Horizontal distance off the end of the rupture measured  |
    |        | parallel to strike. Can only be zero or positive. We     |
    |        | compute this as a function of GC2 coordinate U.          |
    +--------+----------------------------------------------------------+
    | U      | GC2 coordinate U.                                        |
    +--------+----------------------------------------------------------+
    | T      | GC2 coordinate T.                                        |
    +--------+----------------------------------------------------------+

    Args:
        methods (list): List of strings (or just a string) of distances to
            compute.
        lat (array): A numpy array of latitudes.
        lon (array): A numpy array of longidues.
        dep (array): A numpy array of depths (km).
        rupture (Rupture): A ShakeMap Rupture instance.
        dx (float): Mesh spacing for rupture; only used if rupture is an
            EdgeRupture subclass.

    Returns:
       dict: dictionary of numpy arrays of distances, size of lon.shape.
    """
    rupture._mesh_dx = dx

    # Dictionary for holding/returning the requested distances
    distdict = dict()

    # Coerce methods into list if it isn't
    if not isinstance(methods, list):
        methods = [methods]

    # Check that all requested distances are available
    methods_available = set(get_distance_measures())
    if not set(methods).issubset(methods_available):
        raise NotImplementedError(
            'One or more requested distance method is not '
            'valid or is not implemented yet')

    # Check dimensions of site coordinates
    if (lat.shape != lon.shape) or (lat.shape != dep.shape):
        raise ShakeLibException('lat, lon, and dep must have the same shape.')

    # -------------------------------------------------------------------------
    # Point distances
    # -------------------------------------------------------------------------
    if 'rhypo' in methods:
        distdict['rhypo'] = rupture.computeRhyp(lon, lat, dep)

    if 'repi' in methods:
        distdict['repi'] = rupture.computeRepi(lon, lat, dep)

    # -------------------------------------------------------------------------
    # Rupture distances
    # -------------------------------------------------------------------------
    gc2_distances = set(['rx', 'ry', 'ry0', 'U', 'T'])
    if 'rrup' in methods:
        distdict['rrup'] = rupture.computeRrup(lon, lat, dep)

    if 'rjb' in methods:
        distdict['rjb'] = rupture.computeRjb(lon, lat, dep)

    # If any of the GC2-related distances are requested, may as well do all
    if len(set(methods).intersection(gc2_distances)) > 0:
        distdict.update(rupture.computeGC2(lon, lat, dep))

    return distdict
Example #12
0
    def setMechanism(self, mech=None, rake=None, dip=None):
        """Set the earthquake mechanism manually (overriding any values read
        in from event.xml or source.txt. If rake and dip are not specified,
        they will be assigned by mechanism as follows:

        +-------+--------+-----+
        | Mech  |  Rake  | Dip |
        +=======+========+=====+
        | RS    |    90  |  40 |
        +-------+--------+-----+
        | NM    |   -90  |  50 |
        +-------+--------+-----+
        | SS    |     0  |  90 |
        +-------+--------+-----+
        | ALL   |    45  |  90 |
        +-------+--------+-----+

        Args:
            mech (str): One of 'RS' (reverse), 'NM' (normal), 'SS' (strike
                slip), or 'ALL' (unknown).
            rake (float): Value between -360 and 360 degrees. If set, will
                override default value for mechanism (see table above).
            dip (float): Value betweeen 0 and 90 degrees. If set, will override
                default value for mechanism (see table above). Value will be
                converted to range between -180 and 180 degrees.
        """
        mechs = {
            'RS': {
                'rake': 90.0,
                'dip': 40.0
            },
            'NM': {
                'rake': -90.0,
                'dip': 50.0
            },
            'SS': {
                'rake': 0.0,
                'dip': 90.0
            },
            'ALL': {
                'rake': 45.0,
                'dip': 90.0
            }
        }

        if mech not in list(mechs.keys()):
            raise ShakeLibException('Mechanism must be one of: %s' %
                                    str(list(mechs.keys())))

        if dip is not None:
            if dip < 0 or dip > 90:
                raise Exception('Dip must be in range 0-90 degrees.')
        else:
            dip = mechs[mech]['dip']

        if rake is not None:
            if rake < -180:
                rake += 360
            if rake > 180:
                rake -= 360
            if rake < -180 or rake > 180:
                raise Exception(
                    'Rake must be transformable to be in range -180 to 180 '
                    'degrees.')
        else:
            rake = mechs[mech]['rake']

        self.dip = dip
        self.rake = rake
        self.mech = mech
        return
Example #13
0
    def fromVertices(cls,
                     xp0,
                     yp0,
                     zp0,
                     xp1,
                     yp1,
                     zp1,
                     xp2,
                     yp2,
                     zp2,
                     xp3,
                     yp3,
                     zp3,
                     origin,
                     group_index=None,
                     reference=None):
        """
        Create a QuadDrupture instance from the vector of vertices that fully
        define the quadrilaterals. The points p0, ..., p3 are labeled below for
        a trapezoid:

        ::

              p0--------p1
             /          |
            /           |
           p3-----------p2

        All of the following vector arguments must have the same length.

        Args:
            xp0 (array): Array or list of longitudes (floats) of p0.
            yp0 (array): Array or list of latitudes (floats) of p0.
            zp0 (array): Array or list of depths (floats) of p0.
            xp1 (array): Array or list of longitudes (floats) of p1.
            yp1 (array): Array or list of latitudes (floats) of p1.
            zp1 (array): Array or list of depths (floats) of p1.
            xp2 (array): Array or list of longitudes (floats) of p2.
            yp2 (array): Array or list of latitudes (floats) of p2.
            zp2 (array): Array or list of depths (floats) of p2.
            xp3 (array): Array or list of longitudes (floats) of p3.
            yp3 (array): Array or list of latitudes (floats) of p3.
            zp3 (array): Array or list of depths (floats) of p3.
            origin (Origin): Reference to a ShakeMap Origin object.
            group_index (list): List of integers to indicate group index. If
                None then each quadrilateral is assumed to be in a different
                group since there is no guarantee that any of them are
                continuous.
            reference (str): String explaining where the rupture definition
                came from (publication style reference, etc.)

        Returns:
            QuadRupture object, where the rupture is modeled as a series of
            trapezoids.

        Raises:
            ShakeLibException: if the lengths of the inptu arrays are not
                all equal, or if the length of the group_index is not the
                same as the arrays (if group_index is supplied)..
        """
        if len(xp0) == len(yp0) == len(zp0) == len(xp1) == len(yp1) == \
           len(zp1) == len(xp2) == len(yp2) == len(zp2) == len(xp3) == \
           len(yp3) == len(zp3):
            pass
        else:
            raise ShakeLibException('All vectors specifying quadrilateral '
                                    'vertices must have the same length.')

        nq = len(xp0)
        if group_index is not None:
            if len(group_index) != nq:
                raise ShakeLibException(
                    "group_index must have same length as vertices.")
        else:
            group_index = np.array(range(nq))

        xp0 = np.array(xp0, dtype='d')
        yp0 = np.array(yp0, dtype='d')
        zp0 = np.array(zp0, dtype='d')
        xp1 = np.array(xp1, dtype='d')
        yp1 = np.array(yp1, dtype='d')
        zp1 = np.array(zp1, dtype='d')
        xp2 = np.array(xp2, dtype='d')
        yp2 = np.array(yp2, dtype='d')
        zp2 = np.array(zp2, dtype='d')
        xp3 = np.array(xp3, dtype='d')
        yp3 = np.array(yp3, dtype='d')
        zp3 = np.array(zp3, dtype='d')

        # ---------------------------------------------------------------------
        # Create GeoJSON object
        # ---------------------------------------------------------------------

        coords = []
        u_groups = np.unique(group_index)
        n_groups = len(u_groups)
        for i in range(n_groups):
            ind = np.where(u_groups[i] == group_index)[0]
            lons = np.concatenate([
                xp0[ind[0]].reshape((1, )), xp1[ind], xp2[ind][::-1],
                xp3[ind][::-1][-1].reshape((1, )), xp0[ind[0]].reshape((1, ))
            ])
            lats = np.concatenate([
                yp0[ind[0]].reshape((1, )), yp1[ind], yp2[ind][::-1],
                yp3[ind][::-1][-1].reshape((1, )), yp0[ind[0]].reshape((1, ))
            ])
            deps = np.concatenate([
                zp0[ind[0]].reshape((1, )), zp1[ind], zp2[ind][::-1],
                zp3[ind][::-1][-1].reshape((1, )), zp0[ind[0]].reshape((1, ))
            ])

            poly = []
            for lon, lat, dep in zip(lons, lats, deps):
                poly.append([lon, lat, dep])
            coords.append(poly)

        d = {
            "type":
            "FeatureCollection",
            "metadata": {
                "reference": reference
            },
            "features": [{
                "type": "Feature",
                "properties": {
                    "rupture type": "rupture extent"
                },
                "geometry": {
                    "type": "MultiPolygon",
                    "coordinates": [coords]
                }
            }]
        }

        # Add origin information to metadata
        odict = origin.__dict__
        for k, v in odict.items():
            if isinstance(v, HistoricTime):
                d['metadata'][k] = v.strftime(constants.TIMEFMT)
            else:
                d['metadata'][k] = v
        if hasattr(origin, 'id'):
            d['metadata']['eventid'] = origin.id

        return cls(d, origin)
Example #14
0
    def fromOrientation(cls, px, py, pz, dx, dy, length, width, strike, dip,
                        origin):
        """
        Create a QuadRupture instance from a known point, shape, and
        orientation.

        A point is defined as a set of latitude, longitude, and depth, which
        is located in the corner between the tail of the vector pointing in
        the strike direction and the dip direction (nearest to the surface).
        The shape is defined by length, width, dx, and dy. The length is the
        measurement of the quadrilateral in the direction of strike, and
        width is the measurement of quadrilateral in the direction of dip.
        Dx is the measurement on the plane in the strike direction between
        the known point and the corner between the tail of the vector pointing
        in the strike direction and the dip direction (nearest to the surface).
        Dy is the measurement on the plane in the dip direction between
        the known point and the corner between the tail of the vector pointing
        in the strike direction and the dip direction (nearest to the surface).
        The orientation is defined by azimuth and angle from
        horizontal, strike and dip respectively. For example in plane view:
            ::
                            strike direction
                        p1*------------------->>p2
                        *        | dy           |
                 dip    |--------o              |
              direction |   dx    known point   | Width
                        V                       |
                        V                       |
                        p4----------------------p3
                                Length

        Args:
            px (array): Array or list of longitudes (floats) of the known
                point.
            py (array): Array or list of latitudes (floats) of the known point.
            pz (array): Array or list of depths (floats) of the known point.
            dx (array): Array or list of distances (floats), in the strike
                direction, between the known point and P1. dx must be less than
                length.
            dy (array): Array or list of distances (floats), in the dip
                direction, between the known point and P1. dy must be less than
                width.
            length (array): Array or list of widths (floats) of the plane in
                the strike direction.
            width (array): Array or list of widths (floats) of the plane in the
                dip direction.
            strike (array): Array or list of strike angles (floats).
            dip (array): Array or list of dip angles (floats).
            origin (Origin): Reference to a ShakeMap Origin object.
        Returns:
            QuadRupture instance.

        Raises:
            ShakeLibException: if the lengths of the points arrays are not
                all equal.
        """
        # Verify that arrays are of equal length
        if len(px) == len(py) == len(pz) == len(dx) == len(dy) == len(
                length) == len(width) == len(strike) == len(dip):
            pass
        else:
            raise ShakeLibException(
                'Number of px, py, pz, dx, dy, length, width, '
                'strike, dip points must be '
                'equal.')

        # Verify that all are numpy arrays
        px = np.array(px, dtype='d')
        py = np.array(py, dtype='d')
        pz = np.array(pz, dtype='d')
        dx = np.array(dx, dtype='d')
        dy = np.array(dy, dtype='d')
        length = np.array(length, dtype='d')
        width = np.array(width, dtype='d')
        strike = np.array(strike, dtype='d')
        dip = np.array(dip, dtype='d')

        # Get P1 and P2 (top horizontal points)
        theta = np.rad2deg(np.arctan2(dy * np.cos(np.deg2rad(dip)), dx))
        P1_direction = strike + 180 + theta
        P1_distance = np.sqrt(dx**2 + (dy * np.cos(np.deg2rad(dip)))**2)
        P2_direction = strike
        P2_distance = length
        P1_lon = []
        P1_lat = []
        P2_lon = []
        P2_lat = []
        for idx, value in enumerate(px):
            P1_points = point_at(px[idx], py[idx], P1_direction[idx],
                                 P1_distance[idx])
            P1_lon += [P1_points[0]]
            P1_lat += [P1_points[1]]
            P2_points = point_at(P1_points[0], P1_points[1], P2_direction[idx],
                                 P2_distance[idx])
            P2_lon += [P2_points[0]]
            P2_lat += [P2_points[1]]

        # Get top depth
        top_horizontal_depth = pz - np.abs(dy * np.sin(np.deg2rad(dip)))

        # Get QuadRupture object
        quad = QuadRupture.fromTrace(P1_lon,
                                     P1_lat,
                                     P2_lon,
                                     P2_lat,
                                     top_horizontal_depth,
                                     width,
                                     dip,
                                     origin,
                                     strike=strike)
        return quad
Example #15
0
    def fromTrace(cls,
                  xp0,
                  yp0,
                  xp1,
                  yp1,
                  zp,
                  widths,
                  dips,
                  origin,
                  strike=None,
                  group_index=None,
                  reference=""):
        """
        Create a QuadRupture instance from a set of vertices that define the
        top of the rupture, and an array of widths/dips.

        Each rupture quadrilaterial is defined by specifying the latitude,
        longitude, and depth of the two vertices on the top edges, which must
        have the dame depths. The other verticies are then constructed from
        the top edges and the width and dip of the quadrilateral.

        Args:
            xp0 (array): Array or list of longitudes (floats) of p0.
            yp0 (array): Array or list of latitudes (floats) of p0.
            xp1 (array): Array or list of longitudes (floats) of p1.
            yp1 (array): Array or list of latitudes (floats) of p1.
            zp (array): Array or list of depths for each of the top of rupture
                rectangles (km).
            widths (array): Array of widths for each of rectangle (km).
            dips (array): Array of dips for each of rectangle (degrees).
            origin (Origin): Reference to a ShakeMap origin object.
            strike (array): If None then strike is computed from verticies of
                top edge of each quadrilateral. If the array has only a
                single value, then all
                quadrilaterals are constructed assuming this strike direction.
                If an array with the same length as the trace coordinates then
                it specifies the strike for each quadrilateral.
            group_index (list): List of integers to indicate group index. If
                None then each quadrilateral is assumed to be in a different
                group since there is no guarantee that any of them are
                continuous.
            reference (str): String explaining where the rupture definition
                came from (publication style reference, etc.).

        Returns:
            QuadRupture instance.

        Raises:
            ShakeLibException: if the input arrays are not all the same
                length, or if the strike array is not length 1 or the same
                length as the input arrays.

        """
        if not (len(xp0) == len(yp0) == len(xp1) == len(yp1) == len(zp) ==
                len(dips) == len(widths)):
            raise ShakeLibException(
                'Number of xp0,yp0,xp1,yp1,zp,widths,dips points must be '
                'equal.')
        if strike is not None and len(xp0) != len(strike) and len(strike) != 1:
            raise ShakeLibException(
                'Strike must be None or an array of one value or the '
                'same length as trace coordinates.')

        if group_index is None:
            group_index = np.array(range(len(xp0)))

        # Convert dips to radians
        dips = np.radians(dips)

        # Ensure that all input sequences are numpy arrays
        xp0 = np.array(xp0, dtype='d')
        xp1 = np.array(xp1, dtype='d')
        yp0 = np.array(yp0, dtype='d')
        yp1 = np.array(yp1, dtype='d')
        zp = np.array(zp, dtype='d')
        widths = np.array(widths, dtype='d')
        dips = np.array(dips, dtype='d')

        # Get a projection object
        west = np.min((xp0.min(), xp1.min()))
        east = np.max((xp0.max(), xp1.max()))
        south = np.min((yp0.min(), yp1.min()))
        north = np.max((yp0.max(), yp1.max()))

        # Projected coordinates are in km
        proj = OrthographicProjection(west, east, north, south)
        xp2 = np.zeros_like(xp0)
        xp3 = np.zeros_like(xp0)
        yp2 = np.zeros_like(xp0)
        yp3 = np.zeros_like(xp0)
        zpdown = np.zeros_like(zp)
        for i in range(0, len(xp0)):
            # Project the top edge coordinates
            p0x, p0y = proj(xp0[i], yp0[i])
            p1x, p1y = proj(xp1[i], yp1[i])

            # Get the rotation angle defined by these two points
            if strike is None:
                dx = p1x - p0x
                dy = p1y - p0y
                theta = np.arctan2(dx, dy)  # theta is angle from north
            elif len(strike) == 1:
                theta = np.radians(strike[0])
            else:
                theta = np.radians(strike[i])

            R = np.array([[np.cos(theta), -np.sin(theta)],
                          [np.sin(theta), np.cos(theta)]])

            # Rotate the top edge points into a new coordinate system (vertical
            # line)
            p0 = np.array([p0x, p0y])
            p1 = np.array([p1x, p1y])
            p0p = np.dot(R, p0)
            p1p = np.dot(R, p1)

            # Get right side coordinates in project, rotated system
            dz = np.sin(dips[i]) * widths[i]
            dx = np.cos(dips[i]) * widths[i]
            p3xp = p0p[0] + dx
            p3yp = p0p[1]
            p2xp = p1p[0] + dx
            p2yp = p1p[1]

            # Get right side coordinates in un-rotated projected system
            p3p = np.array([p3xp, p3yp])
            p2p = np.array([p2xp, p2yp])
            Rback = np.array([[np.cos(-theta), -np.sin(-theta)],
                              [np.sin(-theta), np.cos(-theta)]])
            p3 = np.dot(Rback, p3p)
            p2 = np.dot(Rback, p2p)
            p3x = np.array([p3[0]])
            p3y = np.array([p3[1]])
            p2x = np.array([p2[0]])
            p2y = np.array([p2[1]])

            # project lower edge points back to lat/lon coordinates
            lon3, lat3 = proj(p3x, p3y, reverse=True)
            lon2, lat2 = proj(p2x, p2y, reverse=True)

            xp2[i] = lon2
            xp3[i] = lon3
            yp2[i] = lat2
            yp3[i] = lat3
            zpdown[i] = zp[i] + dz

        # ---------------------------------------------------------------------
        # Create GeoJSON object
        # ---------------------------------------------------------------------

        coords = []
        u_groups = np.unique(group_index)
        n_groups = len(u_groups)
        for i in range(n_groups):
            ind = np.where(u_groups[i] == group_index)[0]
            lons = np.concatenate([
                xp0[ind[0]].reshape((1, )), xp1[ind], xp2[ind][::-1],
                xp3[ind][::-1][-1].reshape((1, )), xp0[ind[0]].reshape((1, ))
            ])
            lats = np.concatenate([
                yp0[ind[0]].reshape((1, )), yp1[ind], yp2[ind][::-1],
                yp3[ind][::-1][-1].reshape((1, )), yp0[ind[0]].reshape((1, ))
            ])
            deps = np.concatenate([
                zp[ind[0]].reshape((1, )), zp[ind], zpdown[ind][::-1],
                zpdown[ind][::-1][-1].reshape((1, )), zp[ind[0]].reshape((1, ))
            ])

            poly = []
            for lon, lat, dep in zip(lons, lats, deps):
                poly.append([lon, lat, dep])
            coords.append(poly)

        d = {
            "type":
            "FeatureCollection",
            "metadata": {
                "reference": reference
            },
            "features": [{
                "type": "Feature",
                "properties": {
                    "rupture type": "rupture extent"
                },
                "geometry": {
                    "type": "MultiPolygon",
                    "coordinates": [coords]
                }
            }]
        }

        # Add origin information to metadata
        odict = origin.__dict__
        for k, v in odict.items():
            if isinstance(v, HistoricTime):
                d['metadata'][k] = v.strftime(constants.TIMEFMT)
            else:
                d['metadata'][k] = v

        return cls(d, origin)
Example #16
0
def _quad_distance(q, points, horizontal=False):
    """
    Calculate the shortest distance from a set of points to a rupture surface.

    Args:
        q (list): A quadrilateral; list of four points.
        points (array): Numpy array Nx3 of points (ECEF) to calculate distance
            from.
        horizontal:  Boolean indicating whether to treat points inside quad as
            0 distance.

    Returns:
        float: Array of size N of distances (in km) from input points to
            rupture surface.
    """
    P0, P1, P2, P3 = q

    if horizontal:
        # project this quad to the surface
        P0 = Point(P0.x, P0.y, 0)
        P1 = Point(P1.x, P1.y, 0)
        P2 = Point(P2.x, P2.y, 0)
        P3 = Point(P3.x, P3.y, 0)

    # Convert to ecef
    p0 = Vector.fromPoint(P0)
    p1 = Vector.fromPoint(P1)
    p2 = Vector.fromPoint(P2)
    p3 = Vector.fromPoint(P3)

    # Make a unit vector normal to the plane
    normalVector = (p1 - p0).cross(p2 - p0).norm()

    dist = np.ones_like(points[:, 0]) * np.nan

    p0d = p0.getArray() - points
    p1d = p1.getArray() - points
    p2d = p2.getArray() - points
    p3d = p3.getArray() - points

    # Create 4 planes with normals pointing outside rectangle
    n0 = (p1 - p0).cross(normalVector).getArray()
    n1 = (p2 - p1).cross(normalVector).getArray()
    n2 = (p3 - p2).cross(normalVector).getArray()
    n3 = (p0 - p3).cross(normalVector).getArray()

    sgn0 = np.signbit(np.sum(n0 * p0d, axis=1))
    sgn1 = np.signbit(np.sum(n1 * p1d, axis=1))
    sgn2 = np.signbit(np.sum(n2 * p2d, axis=1))
    sgn3 = np.signbit(np.sum(n3 * p3d, axis=1))

    inside_idx = (sgn0 == sgn1) & (sgn1 == sgn2) & (sgn2 == sgn3)
    if horizontal:
        dist[inside_idx] = 0.0
    else:
        dist[inside_idx] = np.power(np.abs(
            np.sum(p0d[inside_idx, :] * normalVector.getArray(), axis=1)), 2)

    outside_idx = np.logical_not(inside_idx)
    s0 = _distance_sq_to_segment(p0d, p1d)
    s1 = _distance_sq_to_segment(p1d, p2d)
    s2 = _distance_sq_to_segment(p2d, p3d)
    s3 = _distance_sq_to_segment(p3d, p0d)

    smin = np.minimum(np.minimum(s0, s1), np.minimum(s2, s3))
    dist[outside_idx] = smin[outside_idx]
    dist = np.sqrt(dist) / 1000.0
    shp = dist.shape
    if len(shp) == 1:
        dist.shape = (shp[0], 1)
    if np.any(np.isnan(dist)):
        raise ShakeLibException("Could not calculate some distances!")
    dist = np.fliplr(dist)
    return dist