def test_projection_across_international_date_line(self): # tests that given a polygon crossing the internation date line, # projecting its coordinates from spherical to cartesian and then back # to spherical gives the original values west = 179.7800 east = -179.8650 north = 51.4320 south = 50.8410 proj = utils.OrthographicProjection(west, east, north, south) lons = numpy.array([179.8960, 179.9500, -179.9930, -179.9120, -179.8650, -179.9380, 179.9130, 179.7800]) lats = numpy.array([50.8410, 50.8430, 50.8490, 50.8540, 51.4160, 51.4150, 51.4270, 51.4320]) xx, yy = proj(lons, lats) comp_lons, comp_lats = proj(xx, yy, reverse=True) numpy.testing.assert_allclose(lons, comp_lons) numpy.testing.assert_allclose(lats, comp_lats) west = 179. east = -179. north = 1 south = -1 proj = utils.OrthographicProjection(west, east, north, south) lons = numpy.array([179.0, -179.0]) lats = numpy.array([-1, 1]) xx, yy = proj(lons, lats) comp_lons, comp_lats = proj(xx, yy, reverse=True) numpy.testing.assert_allclose(lons, comp_lons) numpy.testing.assert_allclose(lats, comp_lats)
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 to_polygon(self, radius): """ Create a circular polygon with specified radius centered in the point. :param radius: Required radius of a new polygon, in km. :returns: Instance of :class:`~openquake.hazardlib.geo.polygon.Polygon` that approximates a circle around the point with specified radius. """ assert radius > 0 # avoid circular imports from openquake.hazardlib.geo.polygon import Polygon # get a projection that is centered in the point proj = geo_utils.OrthographicProjection(self.longitude, self.longitude, self.latitude, self.latitude) # create a shapely object from a projected point coordinates, # which are supposedly (0, 0) point = shapely.geometry.Point(*proj(self.longitude, self.latitude)) # extend the point to a shapely polygon using buffer() # and create openquake.hazardlib.geo.polygon.Polygon object from it return Polygon._from_2d(point.buffer(radius), proj)
def test_points_too_far(self): proj = utils.OrthographicProjection(180, 180, 45, 45) with self.assertRaises(ValueError) as ar: proj(90, -45) self.assertEqual(str(ar.exception), 'some points are too far from the projection ' 'center lon=180.0 lat=45.0')
def test_projection(self): # values verified against pyproj's implementation proj = utils.OrthographicProjection(10, 16, -2, 30) lons = numpy.array([10., 20., 30., 40.]) lats = numpy.array([-1., -2., -3., -4.]) xx, yy = proj(lons, lats) exx = [-309.89151465, 800.52541443, 1885.04014687, 2909.78079661] eyy = [-1650.93260348, -1747.79256663, -1797.62444771, -1802.28117183] aac(xx, exx, atol=0.01, rtol=0.005) aac(yy, eyy, atol=0.01, rtol=0.005)
def test_projecting_back_and_forth(self): lon0, lat0 = -10.4, 20.3 proj = utils.OrthographicProjection(lon0, lat0, lon0, lat0) lons = lon0 + (numpy.random.random((20, 10)) * 50 - 25) lats = lat0 + (numpy.random.random((20, 10)) * 50 - 25) xx, yy = proj(lons, lats, reverse=False) self.assertEqual(xx.shape, (20, 10)) self.assertEqual(yy.shape, (20, 10)) blons, blats = proj(xx, yy, reverse=True) aac(blons, lons) aac(blats, lats)
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 test(self): polygon2d = shapely.geometry.Polygon([(-12, 0), (0, 14.5), (17.1, 3), (18, 0), (16.5, -3), (0, -10)]) proj = geo_utils.OrthographicProjection(0, 0, 0, 0) poly = polygon.Polygon._from_2d(polygon2d, proj) elons = [-0.10791866, 0., 0.1537842, 0.1618781, 0.14838825, 0.] elats = [0., 0.13040175, 0.02697965, 0., -0.02697965, -0.0899322] ebbox = [-0.10791866, 0.1618781, 0.13040175, -0.0899322] numpy.testing.assert_allclose(poly.lons, elons) numpy.testing.assert_allclose(poly.lats, elats) numpy.testing.assert_allclose(poly._bbox, ebbox) self.assertIs(poly._polygon2d, polygon2d) self.assertIs(poly._projection, proj) poly = polygon.Polygon._from_2d(poly._polygon2d, poly._projection) numpy.testing.assert_allclose(poly.lons, elons) numpy.testing.assert_allclose(poly.lats, elats) numpy.testing.assert_allclose(poly._bbox, ebbox) self.assertIs(poly._polygon2d, polygon2d) self.assertIs(poly._projection, proj)
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 _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_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