def test_nonconvex_polygon(self): coords = [(0, 0), (0, 3), (2, 2), (1, 2), (1, 1), (1, 0), (0, 0)] for polygon_coords in (coords, list(reversed(coords))): polygon = shapely.geometry.Polygon(polygon_coords) pxx = numpy.array([0.5, 0.5, 0.5, 0.5, 0.5]) pyy = numpy.array([0.0, 0.5, 1.0, 2.0, 2.5]) dist = utils.point_to_polygon_distance(polygon, pxx, pyy) numpy.testing.assert_equal(dist, 0) pxx = numpy.array([1.5, 3.0, -2.0]) pyy = numpy.array([1.5, 2.0, 2.0]) dist = utils.point_to_polygon_distance(polygon, pxx, pyy) numpy.testing.assert_almost_equal(dist, [0.5, 1, 2])
def intersects(self, mesh): """ Check for intersection with each point of the ``mesh``. Mesh coordinate values are in decimal degrees. :param mesh: :class:`openquake.hazardlib.geo.mesh.Mesh` instance. :returns: Numpy array of `bool` values in the same shapes in the input coordinate arrays with ``True`` on indexes of points that lie inside the polygon or on one of its edges and ``False`` for points that neither lie inside nor touch the boundary. """ self._init_polygon2d() pxx, pyy = self._projection(mesh.lons, mesh.lats) return utils.point_to_polygon_distance(self._polygon2d, pxx, pyy) == 0
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 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_joyner_boore_distance(self, mesh): """ Compute and return Joyner-Boore distance to each point of ``mesh``. Point's depth is ignored. See :meth:`openquake.hazardlib.geo.surface.base.BaseQuadrilateralSurface.get_joyner_boore_distance` for definition of this distance. :returns: numpy array of distances in km of the same shape as ``mesh``. Distance value is considered to be zero if a point lies inside the polygon enveloping the projection of the mesh or on one of its edges. """ # we perform a hybrid calculation (geodetic mesh-to-mesh distance # and distance on the projection plane for close points). first, # we find the closest geodetic distance for each point of target # mesh to this one. in general that distance is greater than # the exact distance to enclosing polygon of this mesh and it # depends on mesh spacing. but the difference can be neglected # if calculated geodetic distance is over some threshold. distances = geodetic.min_geodetic_distance(self.lons, self.lats, mesh.lons.flatten(), mesh.lats.flatten()) # here we find the points for which calculated mesh-to-mesh # distance is below a threshold. this threshold is arbitrary: # lower values increase the maximum possible error, higher # values reduce the efficiency of that filtering. the maximum # error is equal to the maximum difference between a distance # from site to two adjacent points of the mesh and distance # from site to the line connecting them. thus the error is # a function of distance threshold and mesh spacing. the error # is maximum when the site lies on a perpendicular to the line # connecting points of the mesh and that passes the middle # point between them. the error then can be calculated as # ``err = trsh - d = trsh - \sqrt(trsh^2 - (ms/2)^2)``, where # ``trsh`` and ``d`` are distance to mesh points (the one # we found on the previous step) and distance to the line # connecting them (the actual distance) and ``ms`` is mesh # spacing. the threshold of 40 km gives maximum error of 314 # meters for meshes with spacing of 10 km and 5.36 km for # meshes with spacing of 40 km. if mesh spacing is over # ``(trsh / \sqrt(2)) * 2`` then points lying in the middle # of mesh cells (that is inside the polygon) will be filtered # out by the threshold and have positive distance instead of 0. # so for threshold of 40 km mesh spacing should not be more # than 56 km (typical values are 5 to 10 km). [idxs] = (distances < 40).nonzero() if not len(idxs): # no point is close enough, return distances as they are return distances # for all the points that are closer than the threshold we need # to recalculate the distance and set it to zero, if point falls # inside the enclosing polygon of the mesh. for doing that we # project both this mesh and the points of the second mesh--selected # by distance threshold--to the same Cartesian space, define # minimum shapely polygon enclosing the mesh and calculate point # to polygon distance, which gives the most accurate value # of distance in km (and that value is zero for points inside # the polygon). proj, polygon = self._get_proj_enclosing_polygon() if not isinstance(polygon, shapely.geometry.Polygon): # either line or point is our enclosing polygon. draw # a square with side of 10 m around in order to have # a proper polygon instead. polygon = polygon.buffer(self.DIST_TOLERANCE, 1) mesh_lons, mesh_lats = mesh.lons.take(idxs), mesh.lats.take(idxs) mesh_xx, mesh_yy = proj(mesh_lons, mesh_lats) distances_2d = geo_utils.point_to_polygon_distance( polygon, mesh_xx, mesh_yy) # replace geodetic distance values for points-closer-than-the-threshold # by more accurate point-to-polygon distance values. distances.put(idxs, distances_2d) return distances.reshape(mesh.shape)
def test_2d_array_of_points(self): pxx = [[-1., 0.3], [-0.25, 0.5]] pyy = [[2., 1.1], [3.9, -0.3]] dist = utils.point_to_polygon_distance(self.polygon, pxx, pyy) numpy.testing.assert_almost_equal(dist, [[1.4142135, 0.1], [2.9107559, 0.3]])
def test_list_of_points(self): pxx = [-1., 0.3, -0.25] pyy = [2., 1.1, 3.9] dist = utils.point_to_polygon_distance(self.polygon, pxx, pyy) numpy.testing.assert_almost_equal(dist, [1.4142135, 0.1, 2.9107559])
def test_one_point(self): dist = utils.point_to_polygon_distance(self.polygon, 0.5, 0.5) self.assertEqual(dist, 0) dist = utils.point_to_polygon_distance(self.polygon, 0.5, 1.5) self.assertAlmostEqual(dist, 0.5)
def get_joyner_boore_distance(self, mesh): """ Compute and return Joyner-Boore distance to each point of ``mesh``. Point's depth is ignored. See :meth:`openquake.hazardlib.geo.surface.base.BaseSurface.get_joyner_boore_distance` for definition of this distance. :returns: numpy array of distances in km of the same shape as ``mesh``. Distance value is considered to be zero if a point lies inside the polygon enveloping the projection of the mesh or on one of its edges. """ # we perform a hybrid calculation (geodetic mesh-to-mesh distance # and distance on the projection plane for close points). first, # we find the closest geodetic distance for each point of target # mesh to this one. in general that distance is greater than # the exact distance to enclosing polygon of this mesh and it # depends on mesh spacing. but the difference can be neglected # if calculated geodetic distance is over some threshold. # get the highest slice from the 3D mesh distances = geodetic.min_geodetic_distance( (self.lons, self.lats), (mesh.lons, mesh.lats)) # here we find the points for which calculated mesh-to-mesh # distance is below a threshold. this threshold is arbitrary: # lower values increase the maximum possible error, higher # values reduce the efficiency of that filtering. the maximum # error is equal to the maximum difference between a distance # from site to two adjacent points of the mesh and distance # from site to the line connecting them. thus the error is # a function of distance threshold and mesh spacing. the error # is maximum when the site lies on a perpendicular to the line # connecting points of the mesh and that passes the middle # point between them. the error then can be calculated as # ``err = trsh - d = trsh - \sqrt(trsh^2 - (ms/2)^2)``, where # ``trsh`` and ``d`` are distance to mesh points (the one # we found on the previous step) and distance to the line # connecting them (the actual distance) and ``ms`` is mesh # spacing. the threshold of 40 km gives maximum error of 314 # meters for meshes with spacing of 10 km and 5.36 km for # meshes with spacing of 40 km. if mesh spacing is over # ``(trsh / \sqrt(2)) * 2`` then points lying in the middle # of mesh cells (that is inside the polygon) will be filtered # out by the threshold and have positive distance instead of 0. # so for threshold of 40 km mesh spacing should not be more # than 56 km (typical values are 5 to 10 km). 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 # for all the points that are closer than the threshold we need # to recalculate the distance and set it to zero, if point falls # inside the enclosing polygon of the mesh. for doing that we # project both this mesh and the points of the second mesh--selected # by distance threshold--to the same Cartesian space, define # minimum shapely polygon enclosing the mesh and calculate point # to polygon distance, which gives the most accurate value # of distance in km (and that value is zero for points inside # the polygon). proj, polygon = self._get_proj_enclosing_polygon() if not isinstance(polygon, shapely.geometry.Polygon): # either line or point is our enclosing polygon. draw # a square with side of 10 m around in order to have # a proper polygon instead. polygon = polygon.buffer(self.DIST_TOLERANCE, 1) mesh_xx, mesh_yy = proj(mesh.lons[idxs], mesh.lats[idxs]) # replace geodetic distance values for points-closer-than-the-threshold # by more accurate point-to-polygon distance values. distances[idxs] = geo_utils.point_to_polygon_distance( polygon, mesh_xx, mesh_yy) return distances