def test_points_too_far(self): proj = utils.get_orthographic_projection(180, 180, 45, 45) with self.assertRaises(ValueError) as ar: proj(90, -45) self.assertEqual(ar.exception.message, 'some points are too far from the projection ' 'center lon=180.0 lat=45.0')
def get_convex_hull(self): """ Get a convex polygon object that contains projections of all the points of the mesh. :returns: Instance of :class:`nhlib.geo.polygon.Polygon` that is a convex hull around all the points in this mesh. If the original mesh had only one point, the resulting polygon has a square shape with a side length of 10 meters. If there were only two points, resulting polygon is a stripe 10 meters wide. """ # avoid circular imports from nhlib.geo.polygon import Polygon # create a projection centered in the center of points collection proj = geo_utils.get_orthographic_projection( *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, self.lats)).copy() multipoint = shapely.geometry.MultiPoint(coords) # create a 2d polygon from a convex hull around that multipoint polygon2d = multipoint.convex_hull # if mesh had only one point, the convex hull is a point. if there # were two, it is a line string. we need to return a convex polygon # object, so extend that area-less geometries by some arbitrarily # small distance, like five meters. if isinstance(polygon2d, (shapely.geometry.LineString, shapely.geometry.Point)): polygon2d = polygon2d.buffer(0.005, 1) return Polygon._from_2d(polygon2d, proj)
def test_projection(self): # values verified against pyproj's implementation proj = utils.get_orthographic_projection(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] self.assertTrue(numpy.allclose(xx, exx, atol=0.01, rtol=0.005)) self.assertTrue(numpy.allclose(yy, eyy, atol=0.01, rtol=0.005))
def test_projecting_back_and_forth(self): lon0, lat0 = -10.4, 20.3 proj = utils.get_orthographic_projection(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) self.assertTrue(numpy.allclose(blons, lons)) self.assertTrue(numpy.allclose(blats, lats))
def get_polygon_area(polygon): """ Compute polygon area in squared kilometers. """ lons = [lon for lon, lat in polygon] lats = [lat for lon, lat in polygon] west, east, north, south = _utils.get_spherical_bounding_box(lons, lats) proj = _utils.get_orthographic_projection(west, east, north, south) xx, yy = proj(lons, lats) polygon2d = shapely.geometry.Polygon(zip(xx, yy)) return polygon2d.area
def get_polygon_area(polygon): """ Compute polygon area in squared kilometers. """ lons = [lon for lon,lat in polygon] lats = [lat for lon,lat in polygon] west, east, north, south = _utils.get_spherical_bounding_box(lons, lats) proj = _utils.get_orthographic_projection(west, east, north, south) xx, yy = proj(lons, lats) polygon2d = shapely.geometry.Polygon(zip(xx, yy)) return polygon2d.area
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:`nhlib.geo.surface.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. """ bounding_mesh = self._get_bounding_mesh(with_depths=False) assert bounding_mesh.depths is None lons, lats = bounding_mesh.lons, bounding_mesh.lats depths = numpy.zeros_like(lons) proj = geo_utils.get_orthographic_projection( *geo_utils.get_spherical_bounding_box(lons, lats) ) xx, yy = proj(lons, lats) mesh_2d = numpy.array([xx, yy], dtype=float).transpose().copy() if len(xx) == 2: mesh_2d = shapely.geometry.LineString(mesh_2d) elif len(xx) == 1: mesh_2d = shapely.geometry.Point(*mesh_2d) elif len(xx) > 2: mesh_2d = shapely.geometry.Polygon(mesh_2d) mesh_lons, mesh_lats = mesh.lons.flatten(), mesh.lats.flatten() mesh_xx, mesh_yy = proj(mesh_lons, mesh_lats) distances = [] for i in xrange(len(mesh_lons)): point_2d = shapely.geometry.Point(mesh_xx[i], mesh_yy[i]) dist = mesh_2d.distance(point_2d) if dist < 500: # if the distance is below threshold of 500 kilometers, # consider the distance measured on the projection accurate # enough (an error doesn't exceed half km). distances.append(dist) else: # ... otherwise get the precise distance between bounding mesh # projection and the point projection using pure numerical way distances.append(geodetic.min_distance( lons, lats, depths, mesh_lons[i], mesh_lats[i], 0 )) return numpy.array(distances).reshape(mesh.shape)
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.get_orthographic_projection(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 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:`~nhlib.geo.polygon.Polygon` that approximates a circle around the point with specified radius. """ assert radius > 0 # avoid circular imports from nhlib.geo.polygon import Polygon # get a projection that is centered in the point proj = geo_utils.get_orthographic_projection( 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 nhlib.geo.polygon.Polygon object from it return Polygon._from_2d(point.buffer(radius), proj)
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))