def _get_cartesian_edge_set(self): """ For the GC2 calculations a set of cartesian representations of the fault edges are needed. In this present case we use a common cartesian framework for all edges, as opposed to defining a separate orthographic projection per edge """ # Get projection space for cartesian projection edge_sets = numpy.vstack(self.edge_set) west, east, north, south = utils.get_spherical_bounding_box( edge_sets[:, 0], edge_sets[:, 1]) self.proj = utils.OrthographicProjection(west, east, north, south) for edges in self.edge_set: # Project edges into cartesian space px, py = self.proj(edges[:, 0], edges[:, 1]) # Store the two end-points of the trace self.cartesian_endpoints.append( numpy.array([[px[0], py[0], edges[0, 2]], [px[-1], py[-1], edges[-1, 2]]])) self.cartesian_edges.append(numpy.column_stack([px, py, edges[:, 2]])) # Get surface length vector for the trace - easier in cartesian lengths = numpy.sqrt((px[:-1] - px[1:]) ** 2. + (py[:-1] - py[1:]) ** 2.) self.length_set.append(lengths) # Get cumulative surface length vector self.cum_length_set.append( numpy.hstack([0., numpy.cumsum(lengths)])) return edge_sets
def _define_bins(bins_data, mag_bin_width, dist_bin_width, coord_bin_width, truncation_level, n_epsilons): """ Define bin edges for disaggregation histograms. Given bins data as provided by :func:`_collect_bins_data`, this function finds edges of histograms, taking into account maximum and minimum values of magnitude, distance and coordinates as well as requested sizes/numbers of bins. """ mags, dists, lons, lats, tect_reg_types, trt_bins, _ = bins_data mag_bins = mag_bin_width * numpy.arange( int(numpy.floor(mags.min() / mag_bin_width)), int(numpy.ceil(mags.max() / mag_bin_width) + 1) ) dist_bins = dist_bin_width * numpy.arange( int(numpy.floor(dists.min() / dist_bin_width)), int(numpy.ceil(dists.max() / dist_bin_width) + 1) ) west, east, north, south = get_spherical_bounding_box(lons, lats) west = numpy.floor(west / coord_bin_width) * coord_bin_width east = numpy.ceil(east / coord_bin_width) * coord_bin_width lon_extent = get_longitudinal_extent(west, east) lon_bins, _, _ = npoints_between(west, 0, 0, east, 0, 0, numpy.round(lon_extent / coord_bin_width + 1)) lat_bins = coord_bin_width * numpy.arange( int(numpy.floor(south / coord_bin_width)), int(numpy.ceil(north / coord_bin_width) + 1) ) eps_bins = numpy.linspace(-truncation_level, truncation_level, n_epsilons + 1) return mag_bins, dist_bins, lon_bins, lat_bins, eps_bins, trt_bins
def _from_2d(cls, polygon2d, proj): """ Create a polygon object from a 2d polygon and a projection. :param polygon2d: Instance of ``shapely.geometry.Polygon``. :param proj: Projection object created by :class:`~openquake.hazardlib.geo.utils.OrthographicProjection` that was used to project ``polygon2d``. That projection will be used for projecting it back to get spherical coordinates from Cartesian ones. :returns: New :class:`Polygon` object. Note that spherical coordinates of that polygon do not get upsampled even for longer edges. """ # avoid calling class' constructor polygon = object.__new__(cls) # project polygon2d back on the sphere # NOTE(LB): We use 'exterior' here in case the `polygon2d` has # interiors (holes) defined. In our use cases, we don't care about # polygon interiors, so we simply discard these exteriors. xx, yy = numpy.transpose(polygon2d.exterior.coords) # need to cut off the last point -- it repeats the first one polygon.lons, polygon.lats = proj(xx[:-1], yy[:-1], reverse=True) # initialize the instance (as constructor would do) polygon._bbox = utils.get_spherical_bounding_box( polygon.lons, polygon.lats) polygon._polygon2d = polygon2d polygon._projection = proj return polygon
def _get_proj_enclosing_polygon(self): """ See :meth:`Mesh._get_proj_enclosing_polygon`. :class:`RectangularMesh` contains an information about relative positions of points, so it allows to define the minimum polygon, containing the projection of the mesh, which doesn't necessarily have to be convex (in contrast to :class:`Mesh` implementation). :returns: Same structure as :meth:`Mesh._get_proj_convex_hull`. """ if self.lons.size < 4: # the mesh doesn't contain even a single cell, use :class:`Mesh` # method implementation (which would dilate the point or the line) return super(RectangularMesh, self)._get_proj_enclosing_polygon() proj = geo_utils.get_orthographic_projection( *geo_utils.get_spherical_bounding_box(self.lons.flatten(), self.lats.flatten()) ) mesh2d = numpy.array(proj(self.lons.transpose(), self.lats.transpose())).transpose() lines = iter(mesh2d) # we iterate over horizontal stripes, keeping the "previous" # line of points. we keep it reversed, such that together # with the current line they define the sequence of points # around the stripe. prev_line = next(lines)[::-1] polygons = [] for i, line in enumerate(lines): coords = numpy.concatenate((prev_line, line, prev_line[0:1])) # create the shapely polygon object from the stripe # coordinates and simplify it (remove redundant points, # if there are any lying on the straight line). stripe = shapely.geometry.LineString(coords) \ .simplify(self.DIST_TOLERANCE) \ .buffer(self.DIST_TOLERANCE, 2) polygons.append(shapely.geometry.Polygon(stripe.exterior)) prev_line = line[::-1] try: # create a final polygon as the union of all the stripe ones polygon = shapely.ops.cascaded_union(polygons) \ .simplify(self.DIST_TOLERANCE) except ValueError: # NOTE(larsbutler): In some rare cases, we've observed ValueErrors # ("No Shapely geometry can be created from null value") with very # specific sets of polygons such that there are two unique # and many duplicates of one. # This bug is very difficult to reproduce consistently (except on # specific platforms) so the work around here is to remove the # duplicate polygons. In fact, we only observed this error on our # CI/build machine. None of our dev environments or production # machines has encountered this error, at least consistently. >:( polygons = [shapely.wkt.loads(x) for x in list(set(p.wkt for p in polygons))] polygon = shapely.ops.cascaded_union(polygons) \ .simplify(self.DIST_TOLERANCE) return proj, polygon
def get_bounding_box(self): """ Compute surface geographical bounding box. :return: A tuple of four items. These items represent western, eastern, northern and southern borders of the bounding box respectively. Values are floats in decimal degrees. """ return utils.get_spherical_bounding_box(self.mesh.lons, self.mesh.lats)
def get_bounding_box(self): """ Compute surface bounding box from plane's corners coordinates. Calls :meth:`openquake.hazardlib.geo.utils.get_spherical_bounding_box` :return: A tuple of four items. These items represent western, eastern, northern and southern borders of the bounding box respectively. Values are floats in decimal degrees. """ return geo_utils.get_spherical_bounding_box(self.corner_lons, self.corner_lats)
def get_bounding_box(self): """ Compute surface bounding box from surface mesh representation. That is extract longitudes and latitudes of mesh points and calls: :meth:`openquake.hazardlib.geo.utils.get_spherical_bounding_box` :return: A tuple of four items. These items represent western, eastern, northern and southern borders of the bounding box respectively. Values are floats in decimal degrees. """ mesh = self.get_mesh() return utils.get_spherical_bounding_box(mesh.lons, mesh.lats)
def get_bounding_box(self): """ Compute bounding box for each surface element, and then return the bounding box of all surface elements' bounding boxes. :return: A tuple of four items. These items represent western, eastern, northern and southern borders of the bounding box respectively. Values are floats in decimal degrees. """ lons = [] lats = [] for surf in self.surfaces: west, east, north, south = surf.get_bounding_box() lons.extend([west, east]) lats.extend([north, south]) return utils.get_spherical_bounding_box(lons, lats)
def _get_proj_enclosing_polygon(self): """ See :meth:`Mesh._get_proj_enclosing_polygon`. :class:`RectangularMesh` contains an information about relative positions of points, so it allows to define the minimum polygon, containing the projection of the mesh, which doesn't necessarily have to be convex (in contrast to :class:`Mesh` implementation). :returns: Same structure as :meth:`Mesh._get_proj_convex_hull`. """ if self.lons.size < 4: # the mesh doesn't contain even a single cell, use :class:`Mesh` # method implementation (which would dilate the point or the line) return super(RectangularMesh, self)._get_proj_enclosing_polygon() proj = geo_utils.get_orthographic_projection( *geo_utils.get_spherical_bounding_box(self.lons, self.lats) ) mesh2d = numpy.array(proj(self.lons.transpose(), self.lats.transpose())).transpose() lines = iter(mesh2d) # we iterate over horizontal stripes, keeping the "previous" # line of points. we keep it reversed, such that together # with the current line they define the sequence of points # around the stripe. prev_line = lines.next()[::-1] polygons = [] for i, line in enumerate(lines): coords = numpy.concatenate((prev_line, line, prev_line[0:1])) # create the shapely polygon object from the stripe # coordinates and simplify it (remove redundant points, # if there are any lying on the straight line). stripe = shapely.geometry.LineString(coords) \ .simplify(self.DIST_TOLERANCE) \ .buffer(self.DIST_TOLERANCE, 2) polygons.append(shapely.geometry.Polygon(stripe.exterior)) prev_line = line[::-1] # create a final polygon as the union of all the stripe ones polygon = shapely.ops.cascaded_union(polygons) \ .simplify(self.DIST_TOLERANCE) return proj, polygon
def get_joyner_boore_distance(self, mesh): # Get indexes of the finite points composing the edges iupp = np.nonzero(np.isfinite(self.mesh.lons[0, :]))[0] ilow = np.flipud(np.nonzero(np.isfinite(self.mesh.lons[-1, :]))[0]) irig = np.nonzero(np.isfinite(self.mesh.lons[:, -1]))[0] ilef = np.flipud(np.nonzero(np.isfinite(self.mesh.lons[:, 0]))[0]) # Building the polygon pnts = [] for corner in [(0, iupp), (irig, -1), (-1, ilow), (ilef, 0)]: pnts.extend( zip(self.mesh.lons[corner], self.mesh.lats[corner], self.mesh.depths[corner])) perimeter = np.array(pnts) distances = geodetic.min_geodetic_distance( (perimeter[:, 0], perimeter[:, 1]), (mesh.lons, mesh.lats)) idxs = (distances < 40).nonzero()[0] # indices on the first dimension if not len(idxs): # no point is close enough, return distances as they are return distances # Get the projection proj = geo_utils.OrthographicProjection( *geo_utils.get_spherical_bounding_box(perimeter[:, 0], perimeter[:, 1])) # Mesh projected coordinates mesh_xx, mesh_yy = proj(mesh.lons[idxs], mesh.lats[idxs]) # Create the shapely Polygon using projected coordinates xp, yp = proj(perimeter[:, 0], perimeter[:, 1]) polygon = Polygon([[x, y] for x, y in zip(xp, yp)]) # Calculate the distances distances[idxs] = geo_utils.point_to_polygon_distance( polygon, mesh_xx, mesh_yy) return distances
def update(self, dists, lons, lats): """ Compare the current bounding box with the value in the arrays dists, lons, lats and enlarge it if needed. :param dists: a sequence of distances :param lons: a sequence of longitudes :param lats: a sequence of latitudes """ if self.min_dist is not None: dists = [self.min_dist, self.max_dist] + dists if self.west is not None: lons = [self.west, self.east] + lons if self.south is not None: lats = [self.south, self.north] + lats self.min_dist, self.max_dist = min(dists), max(dists) self.west, self.east, self.north, self.south = \ get_spherical_bounding_box(lons, lats)
def _get_proj_convex_hull(self): """ Create a projection centered in the center of this mesh and define a convex polygon in that projection, enveloping all the points of the mesh. :returns: Tuple of two items: projection function and shapely 2d polygon. Note that the result geometry can be line or point depending on number of points in the mesh and their arrangement. """ # create a projection centered in the center of points collection proj = geo_utils.OrthographicProjection( *geo_utils.get_spherical_bounding_box(self.lons, self.lats)) # project all the points and create a shapely multipoint object. # need to copy an array because otherwise shapely misinterprets it coords = numpy.transpose(proj(self.lons.flat, self.lats.flat)).copy() multipoint = shapely.geometry.MultiPoint(coords) # create a 2d polygon from a convex hull around that multipoint return proj, multipoint.convex_hull
def update(self, dists, lons, lats): """ Compare the current bounding box with the value in the arrays dists, lons, lats and enlarge it if needed. :param dists: a sequence of distances :param lons: a sequence of longitudes :param lats: a sequence of latitudes """ if self.min_dist: dists = [self.min_dist, self.max_dist] + dists if self.west: lons = [self.west, self.east] + lons if self.south: lats = [self.south, self.north] + lats self.min_dist, self.max_dist = min(dists), max(dists) self.west, self.east, self.north, self.south = \ get_spherical_bounding_box(lons, lats)
def _init_polygon2d(self): """ Spherical bounding box, projection, and Cartesian polygon are all cached to prevent redundant computations. If any of them are `None`, recalculate all of them. """ if self._polygon2d is None or self._projection is None or self._bbox is None: # resample polygon line segments: lons, lats = get_resampled_coordinates(self.lons, self.lats) # find the bounding box of a polygon in spherical coordinates: self._bbox = utils.get_spherical_bounding_box(lons, lats) # create a projection that is centered in a polygon center: self._projection = utils.get_orthographic_projection(*self._bbox) # project polygon vertices to the Cartesian space and create # a shapely polygon object: xx, yy = self._projection(lons, lats) self._polygon2d = shapely.geometry.Polygon(zip(xx, yy))
def _define_bins(bins_data, mag_bin_width, dist_bin_width, coord_bin_width, truncation_level, n_epsilons): """ Define bin edges for disaggregation histograms. Given bins data as provided by :func:`_collect_bins_data`, this function finds edges of histograms, taking into account maximum and minimum values of magnitude, distance and coordinates as well as requested sizes/numbers of bins. """ mags, dists, lons, lats, tect_reg_types, trt_bins, _ = bins_data mag_bins = mag_bin_width * numpy.arange( int(numpy.floor(mags.min() / mag_bin_width)), int(numpy.ceil(mags.max() / mag_bin_width) + 1) ) dist_bins = dist_bin_width * numpy.arange( int(numpy.floor(dists.min() / dist_bin_width)), int(numpy.ceil(dists.max() / dist_bin_width) + 1) ) west, east, north, south = get_spherical_bounding_box(lons, lats) west = numpy.floor(west / coord_bin_width) * coord_bin_width east = numpy.ceil(east / coord_bin_width) * coord_bin_width lon_extent = get_longitudinal_extent(west, east) lon_bins, _, _ = npoints_between( west, 0, 0, east, 0, 0, numpy.round(lon_extent / coord_bin_width + 1) ) lat_bins = coord_bin_width * numpy.arange( int(numpy.floor(south / coord_bin_width)), int(numpy.ceil(north / coord_bin_width) + 1) ) eps_bins = numpy.linspace(-truncation_level, truncation_level, n_epsilons + 1) return mag_bins, dist_bins, lon_bins, lat_bins, eps_bins, trt_bins
def _init_polygon2d(self): """ Spherical bounding box, projection, and Cartesian polygon are all cached to prevent redundant computations. If any of them are `None`, recalculate all of them. """ if (self._polygon2d is None or self._projection is None or self._bbox is None): # resample polygon line segments: lons, lats = get_resampled_coordinates(self.lons, self.lats) # find the bounding box of a polygon in spherical coordinates: self._bbox = utils.get_spherical_bounding_box(lons, lats) # create a projection that is centered in a polygon center: self._projection = utils.OrthographicProjection(*self._bbox) # project polygon vertices to the Cartesian space and create # a shapely polygon object: xx, yy = self._projection(lons, lats) self._polygon2d = shapely.geometry.Polygon(list(zip(xx, yy)))
def _get_cartesian_edge_set(self): """ For the GC2 calculations a set of cartesian representations of the fault edges are needed. In this present case we use a common cartesian framework for all edges, as opposed to defining a separate orthographic projection per edge """ # Get projection space for cartesian projection edge_sets = numpy.vstack(self.edge_set) idx = numpy.isfinite(edge_sets[:, 0]) edge_sets = edge_sets[idx, :] # Get bounding box west, east, north, south = utils.get_spherical_bounding_box( edge_sets[:, 0], edge_sets[:, 1]) self.proj = utils.OrthographicProjection(west, east, north, south) for edges in self.edge_set: idx = numpy.isfinite(edges[:, 0]) edges = edges[idx, :] # Project edges into cartesian space px, py = self.proj(edges[:, 0], edges[:, 1]) # Store the two end-points of the trace self.cartesian_endpoints.append( numpy.array([[px[0], py[0], edges[0, 2]], [px[-1], py[-1], edges[-1, 2]]])) self.cartesian_edges.append(numpy.column_stack([px, py, edges[:, 2]])) # Get surface length vector for the trace - easier in cartesian lengths = numpy.sqrt((px[:-1] - px[1:]) ** 2. + (py[:-1] - py[1:]) ** 2.) self.length_set.append(lengths) # Get cumulative surface length vector self.cum_length_set.append( numpy.hstack([0., numpy.cumsum(lengths)])) return edge_sets
def get_joyner_boore_distance(self, mesh) -> np.ndarray: """ Computes the Rjb distance between the rupture and the points included in the mesh provided. :param mesh: An instance of :class:`openquake.hazardlib.geo.mesh.Mesh` :returns: A :class:`numpy.ndarray` instance with the Rjb values """ blo, bla = self._get_external_boundary() distances = geodetic.min_geodetic_distance((blo, bla), (mesh.lons, mesh.lats)) idxs = (distances < 40).nonzero()[0] # indices on the first dimension if len(idxs) < 1: # no point is close enough, return distances as they are return distances # Get the projection proj = geo_utils.OrthographicProjection( *geo_utils.get_spherical_bounding_box(blo, bla)) # Mesh projected coordinates mesh_xx, mesh_yy = proj(mesh.lons[idxs], mesh.lats[idxs]) # Create the shapely Polygon using projected coordinates xp, yp = proj(blo, bla) polygon = Polygon([[x, y] for x, y in zip(xp, yp)]) # Calculate the distances distances[idxs] = geo_utils.point_to_polygon_distance( polygon, mesh_xx, mesh_yy) return distances
def _get_proj_enclosing_polygon(self): """ See :meth:`Mesh._get_proj_enclosing_polygon`. :class:`RectangularMesh` contains an information about relative positions of points, so it allows to define the minimum polygon, containing the projection of the mesh, which doesn't necessarily have to be convex (in contrast to :class:`Mesh` implementation). :returns: Same structure as :meth:`Mesh._get_proj_convex_hull`. """ if self.lons.size < 4: # the mesh doesn't contain even a single cell return self._get_proj_convex_hull() proj = geo_utils.OrthographicProjection( *geo_utils.get_spherical_bounding_box(self.lons, self.lats)) if len(self.lons.shape) == 1: # 1D mesh lons = self.lons.reshape(len(self.lons), 1) lats = self.lats.reshape(len(self.lats), 1) else: # 2D mesh lons = self.lons.T lats = self.lats.T mesh2d = numpy.array(proj(lons, lats)).T lines = iter(mesh2d) # we iterate over horizontal stripes, keeping the "previous" # line of points. we keep it reversed, such that together # with the current line they define the sequence of points # around the stripe. prev_line = next(lines)[::-1] polygons = [] for i, line in enumerate(lines): coords = numpy.concatenate((prev_line, line, prev_line[0:1])) # create the shapely polygon object from the stripe # coordinates and simplify it (remove redundant points, # if there are any lying on the straight line). stripe = shapely.geometry.LineString(coords) \ .simplify(self.DIST_TOLERANCE) \ .buffer(self.DIST_TOLERANCE, 2) polygons.append(shapely.geometry.Polygon(stripe.exterior)) prev_line = line[::-1] try: # create a final polygon as the union of all the stripe ones polygon = shapely.ops.cascaded_union(polygons) \ .simplify(self.DIST_TOLERANCE) except ValueError: # NOTE(larsbutler): In some rare cases, we've observed ValueErrors # ("No Shapely geometry can be created from null value") with very # specific sets of polygons such that there are two unique # and many duplicates of one. # This bug is very difficult to reproduce consistently (except on # specific platforms) so the work around here is to remove the # duplicate polygons. In fact, we only observed this error on our # CI/build machine. None of our dev environments or production # machines has encountered this error, at least consistently. >:( polygons = [ shapely.wkt.loads(x) for x in list(set(p.wkt for p in polygons)) ] polygon = shapely.ops.cascaded_union(polygons) \ .simplify(self.DIST_TOLERANCE) return proj, polygon