def test_LAX_to_JFK(self): dist = geodetic.geodetic_distance(*(LAX + JFK)) self.assertAlmostEqual(dist, 0.623585 * geodetic.EARTH_RADIUS, delta=0.1) dist2 = geodetic.geodetic_distance(*(JFK + LAX)) self.assertAlmostEqual(dist, dist2)
def test_one_to_many(self): dist = geodetic.geodetic_distance(0, 0, [-1, 1], [0, 0]) self.assertTrue(numpy.allclose(dist, [111.195, 111.195]), str(dist)) dist = geodetic.geodetic_distance(0, 0, [[-1, 0], [1, 0]], [[0, 0], [0, 0]]) self.assertTrue(numpy.allclose(dist, [[111.195, 0], [111.195, 0]]), str(dist))
def get_distance_matrix(self): """ Compute and return distances between each pairs of points in the mesh. This method requires that all the points lie on Earth surface (have zero depth) and coordinate arrays are one-dimensional. .. warning:: Because of its quadratic space and time complexity this method is safe to use for meshes of up to several thousand points. For mesh of 10k points it needs ~800 Mb for just the resulting matrix and four times that much for intermediate storage. :returns: Two-dimensional numpy array, square matrix of distances. The matrix has zeros on main diagonal and positive distances in kilometers on all other cells. That is, value in cell (3, 5) is the distance between mesh's points 3 and 5 in km, and it is equal to value in cell (5, 3). Uses :func:`nhlib.geo.geodetic.geodetic_distance`. """ assert self.lons.ndim == 1 assert self.depths is None or (self.depths == 0).all() distances = geodetic.geodetic_distance( self.lons.reshape(self.lons.shape + (1, )), self.lats.reshape(self.lats.shape + (1, )), self.lons, self.lats) return numpy.matrix(distances, copy=False)
def distance_to_mesh(self, mesh, with_depths=True): """ Compute distance (in km) between this point and each point of ``mesh``. :param mesh: :class:`~nhlib.geo.mesh.Mesh` of points to calculate distance to. :param with_depths: If ``True`` (by default), distance is calculated between actual point and the mesh, geodetic distance of projections is combined with vertical distance (difference of depths). If this is set to ``False``, only geodetic distance between projections is calculated. :returns: Numpy array of floats of the same shape as ``mesh`` with distance values in km in respective indices. """ if with_depths: if mesh.depths is None: mesh_depths = numpy.zeros_like(mesh.lons) else: mesh_depths = mesh.depths return geodetic.distance(self.longitude, self.latitude, self.depth, mesh.lons, mesh.lats, mesh_depths) else: return geodetic.geodetic_distance(self.longitude, self.latitude, mesh.lons, mesh.lats)
def average_azimuth(self): """ Calculate and return weighted average azimuth of all line's segments in decimal degrees. Uses formula from http://en.wikipedia.org/wiki/Mean_of_circular_quantities >>> from nhlib.geo.point import Point as P >>> str(Line([P(0, 0), P(1e-5, 1e-5)]).average_azimuth()) '45.0' >>> str(Line([P(0, 0), P(0, 1e-5), P(1e-5, 1e-5)]).average_azimuth()) '45.0' >>> line = Line([P(0, 0), P(-2e-5, 0), P(-2e-5, 1.154e-5)]) >>> '%.1f' % line.average_azimuth() '300.0' """ if len(self.points) == 2: return self.points[0].azimuth(self.points[1]) lons = numpy.array([point.longitude for point in self.points]) lats = numpy.array([point.latitude for point in self.points]) azimuths = geodetic.azimuth(lons[:-1], lats[:-1], lons[1:], lats[1:]) distances = geodetic.geodetic_distance(lons[:-1], lats[:-1], lons[1:], lats[1:]) azimuths = numpy.radians(azimuths) # convert polar coordinates to Cartesian ones and calculate # the average coordinate of each component avg_x = numpy.mean(distances * numpy.sin(azimuths)) avg_y = numpy.mean(distances * numpy.cos(azimuths)) # find the mean azimuth from that mean vector azimuth = numpy.degrees(numpy.arctan2(avg_x, avg_y)) if azimuth < 0: azimuth += 360 return azimuth
def translate(self, p1, p2): """ Translate the surface for a specific distance along a specific azimuth direction. Parameters are two points (instances of :class:`nhlib.geo.point.Point`) representing the direction and an azimuth for translation. The resulting surface corner points will be that far along that azimuth from respective corner points of this surface as ``p2`` is located with respect to ``p1``. :returns: A new :class:`PlanarSurface` object with the same mesh spacing, dip, strike, width, length and depth but with corners longitudes and latitudes translated. """ azimuth = geodetic.azimuth(p1.longitude, p1.latitude, p2.longitude, p2.latitude) distance = geodetic.geodetic_distance(p1.longitude, p1.latitude, p2.longitude, p2.latitude) # avoid calling PlanarSurface's constructor nsurf = object.__new__(PlanarSurface) # but do call BaseSurface's one BaseSurface.__init__(nsurf) nsurf.mesh_spacing = self.mesh_spacing nsurf.dip = self.dip nsurf.strike = self.strike nsurf.corner_lons, nsurf.corner_lats = geodetic.point_at( self.corner_lons, self.corner_lats, azimuth, distance ) nsurf.corner_depths = self.corner_depths.copy() nsurf._init_plane() nsurf.width = self.width nsurf.length = self.length return nsurf
def get_distance_matrix(self): """ Compute and return distances between each pairs of points in the mesh. This method requires that all the points lie on Earth surface (have zero depth) and coordinate arrays are one-dimensional. .. warning:: Because of its quadratic space and time complexity this method is safe to use for meshes of up to several thousand points. For mesh of 10k points it needs ~800 Mb for just the resulting matrix and four times that much for intermediate storage. :returns: Two-dimensional numpy array, square matrix of distances. The matrix has zeros on main diagonal and positive distances in kilometers on all other cells. That is, value in cell (3, 5) is the distance between mesh's points 3 and 5 in km, and it is equal to value in cell (5, 3). Uses :func:`nhlib.geo.geodetic.geodetic_distance`. """ assert self.lons.ndim == 1 assert self.depths is None or (self.depths == 0).all() distances = geodetic.geodetic_distance( self.lons.reshape(self.lons.shape + (1, )), self.lats.reshape(self.lats.shape + (1, )), self.lons, self.lats ) return numpy.matrix(distances, copy=False)
def test_arrays(self): lons1 = numpy.array([[-50.03824533, -153.97808192], [-106.36068694, 47.06944014]]) lats1 = numpy.array([[52.20211151, 17.018482], [-26.06421527, -20.30383723]]) lons2 = numpy.array([[49.97448794, 52.3126795], [-165.13925579, 162.16421979]]) lats2 = numpy.array([[-27.66510775, -73.66591257], [-60.24498761, -9.92908014]]) edist = numpy.array([[13061.87935023, 13506.24000062], [5807.34519649, 12163.45759805]]) dist = geodetic.geodetic_distance(lons1, lats1, lons2, lats2) self.assertEqual(dist.shape, edist.shape) self.assertTrue(numpy.allclose(dist, edist))
def get_middle_point(lon1, lat1, lon2, lat2): """ Given two points return the point exactly in the middle lying on the same great circle arc. Parameters are point coordinates in degrees. :returns: Tuple of longitude and latitude of the point in the middle. """ if lon1 == lon2 and lat1 == lat2: return lon1, lat1 dist = geodetic.geodetic_distance(lon1, lat1, lon2, lat2) azimuth = geodetic.azimuth(lon1, lat1, lon2, lat2) return geodetic.point_at(lon1, lat1, azimuth, dist / 2.0)
def get_resampled_coordinates(lons, lats): """ Resample polygon line segments and return the coordinates of the new vertices. This limits distortions when projecting a polygon onto a spherical surface. Parameters define longitudes and latitudes of a point collection in the form of lists or numpy arrays. :return: A tuple of two numpy arrays: longitudes and latitudes of resampled vertices. """ num_coords = len(lons) assert num_coords == len(lats) lons1 = numpy.array(lons) lats1 = numpy.array(lats) lons2 = numpy.concatenate((lons1[1:], lons1[0:1])) lats2 = numpy.concatenate((lats1[1:], lats1[0:1])) distances = geodetic.geodetic_distance(lons1, lats1, lons2, lats2) resampled_lons = [lons[0]] resampled_lats = [lats[0]] for i in xrange(num_coords): next_point = (i + 1) % num_coords lon1, lat1 = lons[i], lats[i] lon2, lat2 = lons[next_point], lats[next_point] distance = distances[i] num_points = int(distance / UPSAMPLING_STEP_KM) + 1 if num_points >= 2: # We need to increase the resolution of this arc by adding new # points. new_lons, new_lats, _ = geodetic.npoints_between( lon1, lat1, 0, lon2, lat2, 0, num_points ) resampled_lons.extend(new_lons[1:]) resampled_lats.extend(new_lats[1:]) else: resampled_lons.append(lon2) resampled_lats.append(lat2) # we cut off the last point because it repeats the first one. return numpy.array(resampled_lons[:-1]), numpy.array(resampled_lats[:-1])
def get_resampled_coordinates(lons, lats): """ Resample polygon line segments and return the coordinates of the new vertices. This limits distortions when projecting a polygon onto a spherical surface. Parameters define longitudes and latitudes of a point collection in the form of lists or numpy arrays. :return: A tuple of two numpy arrays: longitudes and latitudes of resampled vertices. """ num_coords = len(lons) assert num_coords == len(lats) lons1 = numpy.array(lons) lats1 = numpy.array(lats) lons2 = numpy.concatenate((lons1[1:], lons1[0:1])) lats2 = numpy.concatenate((lats1[1:], lats1[0:1])) distances = geodetic.geodetic_distance(lons1, lats1, lons2, lats2) resampled_lons = [lons[0]] resampled_lats = [lats[0]] for i in xrange(num_coords): next_point = (i + 1) % num_coords lon1, lat1 = lons[i], lats[i] lon2, lat2 = lons[next_point], lats[next_point] distance = distances[i] num_points = int(distance / UPSAMPLING_STEP_KM) + 1 if num_points >= 2: # We need to increase the resolution of this arc by adding new # points. new_lons, new_lats, _ = geodetic.npoints_between( lon1, lat1, 0, lon2, lat2, 0, num_points) resampled_lons.extend(new_lons[1:]) resampled_lats.extend(new_lats[1:]) else: resampled_lons.append(lon2) resampled_lats.append(lat2) # we cut off the last point because it repeats the first one. return numpy.array(resampled_lons[:-1]), numpy.array(resampled_lats[:-1])
def test_one_point_on_pole(self): dist = geodetic.geodetic_distance(0, 90, 0, 88) self.assertAlmostEqual(dist, 222.3898533)
def test_opposite_points(self): dist = geodetic.geodetic_distance(110, -10, -70, 10) self.assertAlmostEqual(dist, geodetic.EARTH_RADIUS * numpy.pi, places=3)
def test_on_equator(self): dist = geodetic.geodetic_distance(0, 0, 1, 0) self.assertAlmostEqual(dist, 111.1949266) [dist2] = geodetic.geodetic_distance([-5], [0], [-6], [0]) self.assertAlmostEqual(dist, dist2)
def get_joyner_boore_distance(self, mesh): """ See :meth:`superclass' method <nhlib.geo.surface.base.BaseSurface.get_joyner_boore_distance>`. This is an optimized version specific to planar surface that doesn't make use of the mesh. """ # we define four great circle arcs that contain four sides # of projected planar surface: # # ↓ II ↓ # I ↓ ↓ I # ↓ + ↓ # →→→→→TL→→→→1→→→→TR→→→→→ → azimuth direction → # ↓ - ↓ # ↓ ↓ # III -3+ IV -4+ III ↓ # ↓ ↓ downdip direction # ↓ + ↓ ↓ # →→→→→BL→→→→2→→→→BR→→→→→ # ↓ - ↓ # I ↓ ↓ I # ↓ II ↓ # # arcs 1 and 2 are directed from left corners to right ones (the # direction has an effect on the sign of the distance to an arc, # as it shown on the figure), arcs 3 and 4 are directed from top # corners to bottom ones. # # then we measure distance from each of the points in a mesh # to each of those arcs and compare signs of distances in order # to find a relative positions of projections of points and # projection of a surface. # # then we consider four special cases (labeled with Roman numerals) # and either pick one of distances to arcs or a closest distance # to corner. # # indices 0, 2 and 1 represent corners TL, BL and TR respectively. arcs_lons = self.corner_lons.take([0, 2, 0, 1]) arcs_lats = self.corner_lats.take([0, 2, 0, 1]) downdip_azimuth = (self.strike + 90) % 360 arcs_azimuths = [self.strike, self.strike, downdip_azimuth, downdip_azimuth] mesh_lons = mesh.lons.reshape((-1, 1)) mesh_lats = mesh.lats.reshape((-1, 1)) # calculate distances from all the target points to all four arcs dists_to_arcs = geodetic.distance_to_arc( arcs_lons, arcs_lats, arcs_azimuths, mesh_lons, mesh_lats ) # ... and distances from all the target points to each of surface's # corners' projections (we might not need all of those but it's # better to do that calculation once for all). dists_to_corners = geodetic.geodetic_distance( self.corner_lons, self.corner_lats, mesh_lons, mesh_lats ).min(axis=-1) # extract from ``dists_to_arcs`` signs (represent relative positions # of an arc and a point: +1 means on the left hand side, 0 means # on arc and -1 means on the right hand side) and minimum absolute # values of distances to each pair of parallel arcs. ds1, ds2, ds3, ds4 = numpy.sign(dists_to_arcs).transpose() dists_to_arcs = numpy.abs(dists_to_arcs).reshape(-1, 2, 2).min(axis=-1) return numpy.select( # consider four possible relative positions of point and arcs: condlist=[ # signs of distances to both parallel arcs are the same # in both pairs, case "I" on a figure above (ds1 == ds2) & (ds3 == ds4), # sign of distances to two parallels is the same only # in one pair, case "II" ds1 == ds2, # ... or another (case "III") ds3 == ds4 # signs are different in both pairs (this is a "default"), # case "IV" ], choicelist=[ # case "I": closest distance is the closest distance to corners dists_to_corners, # case "II": closest distance is distance to arc "1" or "2", # whichever is closer dists_to_arcs[:, 0], # case "III": closest distance is distance to either # arc "3" or "4" dists_to_arcs[:, 1] ], # default -- case "IV" default=0 )
def test_small_distance(self): dist = geodetic.geodetic_distance(0, 0, 0, 1e-10) self.assertAlmostEqual(dist, 0) dist = geodetic.geodetic_distance(-1e-12, 0, 0, 0) self.assertAlmostEqual(dist, 0)
def test_along_meridian(self): coords = map(numpy.array, [(77.5, -150.), (-10., 15.), (77.5, -150.), (-20., 25.)]) dist = geodetic.geodetic_distance(*coords) self.assertTrue(numpy.allclose(dist, [1111.949, 1111.949]), str(dist))