def _Geodesic(points, closed, datum, line, wrap): # Compute the area or perimeter of a polygon/-line # using the GeographicLib package, iff installed try: from geographiclib.geodesic import Geodesic except ImportError: raise ImportError('no %s' % ('geographiclib', )) if not wrap: # capability LONG_UNROLL always set raise ValueError('%s invalid: %s' % ('wrap', wrap)) _, points = polygon(points, closed=closed) # base=LatLonEllipsoidalBase(0, 0) E = datum.ellipsoid g = Geodesic(E.a, E.f).Polygon(line) # note, lon deltas are unrolled, by default for p in points: g.AddPoint(p.lat, p.lon) if line and closed: p = points[0] g.AddPoint(p.lat, p.lon) # g.Compute returns (number_of_points, perimeter, signed area) return g.Compute(False, True)[1 if line else 2]
def get_gc_positions(start, end): """ Get positions along a Great Circle to enable plotting. I couldn't do this with Basemap as we have to instantiate first and we don't know the map boundaries until we have the GC positions. :param start: The start position :type start: tuple :param end: The end position :type end: tuple :return: list of positions :rtype: list of dict """ positions = [] spacing = 100000 # Positions 100km apart geoid = Geodesic(Constants.WGS84_a, Constants.WGS84_f) gc = geoid.InverseLine( start[0], start[1], end[0], end[1] ) n = math.ceil(gc.s13 / spacing) for i in range(n + 1): s = min(spacing * i, gc.s13) result = gc.Position(s, Geodesic.STANDARD | Geodesic.LONG_UNROLL) position = { 'Lat': result['lat2'], 'Lon': result['lon2'] } positions.append(position) return positions
def getEndpoint(lat1, lon1, d1, d2): bearing = random.uniform(0, 360) dist = random.uniform(d1, d2) radius = (d2-d1)/2 geod = Geodesic(Constants.WGS84_a, Constants.WGS84_f) d = geod.Direct(lat1, lon1, bearing, dist) return d['lon2'], d['lat2'], radius
def get_endpoint(lat1, lon1, bearing, d): geod = Geodesic(Constants.WGS84_a, Constants.WGS84_f) d = geod.Direct(lat1, lon1, bearing, d)#* 1852.0) return d['lat2'], d['lon2']
def test_GeodSolve12(self): # Check fix for inverse geodesics on extreme prolate/oblate # ellipsoids Reported 2012-08-29 Stefan Guenther # <*****@*****.**>; fixed 2012-10-07 geod = Geodesic(89.8, -1.83) inv = geod.Inverse(0, 0, -10, 160) self.assertAlmostEqual(inv["azi1"], 120.27, delta=1e-2) self.assertAlmostEqual(inv["azi2"], 105.15, delta=1e-2) self.assertAlmostEqual(inv["s12"], 266.7, delta=1e-1)
def calc_dist_azi(source_latitude_in_deg, source_longitude_in_deg, receiver_latitude_in_deg, receiver_longitude_in_deg, radius_of_planet_in_km, flattening_of_planet): """ Given the source and receiver location, calculate the azimuth from the source to the receiver at the source, the backazimuth from the receiver to the source at the receiver and distance between the source and receiver. :param source_latitude_in_deg: Source location latitude in degrees :type source_latitude_in_deg: float :param source_longitude_in_deg: Source location longitude in degrees :type source_longitude_in_deg: float :param receiver_latitude_in_deg: Receiver location latitude in degrees :type receiver_latitude_in_deg: float :param receiver_longitude_in_deg: Receiver location longitude in degrees :type receiver_longitude_in_deg: float :param radius_of_planet_in_km: Radius of the planet in km :type radius_of_planet_in_km: float :param flattening_of_planet: Flattening of planet (0 for a sphere) :type receiver_longitude_in_deg: float :returns: distance_in_deg (in degrees), source_receiver_azimuth (in degrees) and receiver_to_source_backazimuth (in degrees). :rtype: tuple of three floats """ if geodetics.HAS_GEOGRAPHICLIB: ellipsoid = Geodesic(a=radius_of_planet_in_km * 1000.0, f=flattening_of_planet) g = ellipsoid.Inverse(source_latitude_in_deg, source_longitude_in_deg, receiver_latitude_in_deg, receiver_longitude_in_deg) distance_in_deg = g['a12'] source_receiver_azimuth = g['azi1'] % 360 receiver_to_source_backazimuth = (g['azi2'] + 180) % 360 else: # geographiclib is not installed - use obspy/geodetics values = gps2dist_azimuth(source_latitude_in_deg, source_longitude_in_deg, receiver_latitude_in_deg, receiver_longitude_in_deg, a=radius_of_planet_in_km * 1000.0, f=flattening_of_planet) distance_in_km = values[0] / 1000.0 source_receiver_azimuth = values[1] % 360 receiver_to_source_backazimuth = values[2] % 360 # NB - km2deg assumes spherical planet... generate a warning if flattening_of_planet != 0.0: msg = "Assuming spherical planet when calculating epicentral " + \ "distance. Install the Python module 'geographiclib' " + \ "to solve this." warnings.warn(msg) distance_in_deg = kilometer2degrees(distance_in_km, radius=radius_of_planet_in_km) return (distance_in_deg, source_receiver_azimuth, receiver_to_source_backazimuth)
def test_GeodSolve2(self): # Check fix for antipodal prolate bug found 2010-09-04 geod = Geodesic(6.4e6, -1 / 150.0) inv = geod.Inverse(0.07476, 0, -0.07476, 180) self.assertAlmostEqual(inv["azi1"], 90.00078, delta=0.5e-5) self.assertAlmostEqual(inv["azi2"], 90.00078, delta=0.5e-5) self.assertAlmostEqual(inv["s12"], 20106193, delta=0.5) inv = geod.Inverse(0.1, 0, -0.1, 180) self.assertAlmostEqual(inv["azi1"], 90.00105, delta=0.5e-5) self.assertAlmostEqual(inv["azi2"], 90.00105, delta=0.5e-5) self.assertAlmostEqual(inv["s12"], 20106193, delta=0.5)
def ellipsoid(self, acronym): '''Return the ellipsoid associated with the acronym''' if acronym == 'WGS84': return (Geodesic.WGS84) elif acronym in self.acronymList: param = self.acronymList[acronym].parameters return(Geodesic(param.semiMajor, 1.0 / param.inverseFlattening)) elif acronym in historical_ellipsoids: return(Geodesic(historical_ellipsoids[acronym][1], 1.0 / historical_ellipsoids[acronym][2])) else: return(None)
def measure(self, a, b): a, b = Point(a), Point(b) lat1, lon1 = a.latitude, a.longitude lat2, lon2 = b.latitude, b.longitude if not (isinstance(self.geod, Geodesic) and self.geod.a == self.ELLIPSOID[0] and self.geod.f == self.ELLIPSOID[2]): self.geod = Geodesic(self.ELLIPSOID[0], self.ELLIPSOID[2]) s12 = self.geod.Inverse(lat1, lon1, lat2, lon2, Geodesic.DISTANCE)['s12'] return s12
def calc_dist(source_latitude_in_deg, source_longitude_in_deg, receiver_latitude_in_deg, receiver_longitude_in_deg, radius_of_earth_in_km, flattening_of_earth): """ Given the source and receiver location, calculate the azimuth and distance. :param source_latitude_in_deg: Source location latitude in degrees :type source_latitude_in_deg: float :param source_longitude_in_deg: Source location longitude in degrees :type source_longitude_in_deg: float :param receiver_latitude_in_deg: Receiver location latitude in degrees :type receiver_latitude_in_deg: float :param receiver_longitude_in_deg: Receiver location longitude in degrees :type receiver_longitude_in_deg: float :param radius_of_earth_in_km: Radius of the Earth in km :type radius_of_earth_in_km: float :param flattening_of_earth: Flattening of Earth (0 for a sphere) :type receiver_longitude_in_deg: float :return: distance_in_deg :rtype: float """ if geodetics.HAS_GEOGRAPHICLIB: ellipsoid = Geodesic(a=radius_of_earth_in_km*1000.0, f=flattening_of_earth) g = ellipsoid.Inverse(source_latitude_in_deg, source_longitude_in_deg, receiver_latitude_in_deg, receiver_longitude_in_deg) distance_in_deg = g['a12'] else: # geographiclib is not installed - use obspy/geodetics values = gps2dist_azimuth(source_latitude_in_deg, source_longitude_in_deg, receiver_latitude_in_deg, receiver_longitude_in_deg, a=radius_of_earth_in_km*1000.0, f=flattening_of_earth) distance_in_km = values[0]/1000.0 # NB - km2deg assumes spherical Earth... generate a warning if flattening_of_earth != 0.0: msg = "Assuming spherical Earth when calculating epicentral " + \ "distance. Install the Python module 'geographiclib' " + \ "to solve this." warnings.warn(msg) distance_in_deg = kilometer2degrees(distance_in_km, radius=radius_of_earth_in_km) return distance_in_deg
class MocLocalizer(GeodesicLocalizer): """ A localizer for the MOC observations (subclass of :py:class:`GeodesicLocalizer`) """ DEFAULT_RESOLUTION_M = 1e-3 """ Sets the default resolution for MOC localization """ BODY = Geodesic(MARS_RADIUS_M, 0.0) """ Uses a Geodesic model for MOC that assumes Mars is spherical, which seems to work better in practice. """ def __init__(self, metadata): """ :param metadata: "moc" :py:class:`~pdsc.metadata.PdsMetadata` object """ super(MocLocalizer, self).__init__( metadata.lines / 2.0, metadata.samples / 2.0, metadata.center_latitude, metadata.center_longitude, metadata.lines, metadata.samples, metadata.image_height / metadata.lines, metadata.image_width / metadata.samples, metadata.north_azimuth, 1 )
class CtxLocalizer(GeodesicLocalizer): """ A localizer for the CTX instrument (subclass of :py:class:`GeodesicLocalizer`) """ DEFAULT_RESOLUTION_M = 1e-3 """ Sets the default resolution for CTX localization """ BODY = Geodesic(MARS_RADIUS_M, 0.0) # Works better assuming sphere """ Uses a Geodesic model for CTX that assumes Mars is spherical, which seems to work better in practice. """ def __init__(self, metadata): """ :param metadata: "ctx" :py:class:`~pdsc.metadata.PdsMetadata` object """ flipped_na = (180 - metadata.north_azimuth if metadata.usage_note == 'F' else metadata.north_azimuth) super(CtxLocalizer, self).__init__( metadata.lines / 2.0, metadata.samples / 2.0, metadata.center_latitude, metadata.center_longitude, metadata.lines, metadata.samples, metadata.image_height / metadata.lines, metadata.image_width / metadata.samples, flipped_na, -1 )
def __init__(self): """Initiate all instances of the classes that are needed for the simulation, and start PyGame. Raises: Exception: If you fail to choose a valid simulation setting, an Exception is raised. """ while True: try: string = str( input('Do you want to take in live GNSS-signals? (y/n)\t')) if string in ('y', 'yes'): self.test_mode_on = False try: self.gps = serial.Serial('/dev/ttyACM0', baudrate=9600) except Exception: print( "The port cannot be opened. Check that your unit is connected to the port you're opening." ) exit() elif string in ('n', 'no'): self.test_mode_on = True else: raise Exception except Exception: print('Please type "y" or "n".') else: break self.clock = pg.time.Clock() self.the_arrow = Arrow() self.map = Map(60.75, 11.99) self.the_text = Text() self.earth = Geodesic(cf.R_E, 0) self.out_q = mp.Manager().Value('i', [0.0, 0.0]) self.NS = self.map.center_y self.EW = self.map.center_x self.the_background = pg.image.load('background.jpg') self.process = False self.background = False self.dot_x, self.dot_y = self.the_arrow.two_d_pos.x, self.the_arrow.two_d_pos.y self.pastNS, self.pastEW = self.NS, self.EW # Create a new log file for this session. with open("log.txt", "w") as log: log.write("") try: what_os = platform.system() if what_os == 'Linux': self.g_earth = subprocess.Popen('google-earth-pro') elif what_os == 'Darwin': subprocess.call([ "/usr/bin/open", "-n", "-a", "/Applications/Google Earth Pro.app" ]) except Exception: pass # Wait for Google Earth Pro to open so that the PyGame window opens as the top layer. time.sleep(5) pg.init() pg.display.set_caption("Real time spoofer") self.screen = pg.display.set_mode((cf.SCREEN_WIDTH, cf.SCREEN_HEIGHT))
def ArcPosition(self, a12, outmask = GeodesicCapability.LATITUDE | GeodesicCapability.LONGITUDE | GeodesicCapability.AZIMUTH | GeodesicCapability.DISTANCE): """Return the point a spherical arc length a12 along the geodesic line. Return a dictionary with (some) of the following entries: lat1 latitude of point 1 lon1 longitude of point 1 azi1 azimuth of line at point 1 lat2 latitude of point 2 lon2 longitude of point 2 azi2 azimuth of line at point 2 s12 distance from 1 to 2 a12 arc length on auxiliary sphere from 1 to 2 m12 reduced length of geodesic M12 geodesic scale 2 relative to 1 M21 geodesic scale 1 relative to 2 S12 area between geodesic and equator outmask determines which fields get included and if outmask is omitted, then only the basic geodesic fields are computed. The LONG_UNROLL bit unrolls the longitudes (instead of reducing them to the range [-180,180)). The mask is an or'ed combination of the following values Geodesic.LATITUDE Geodesic.LONGITUDE Geodesic.AZIMUTH Geodesic.DISTANCE Geodesic.REDUCEDLENGTH Geodesic.GEODESICSCALE Geodesic.AREA Geodesic.ALL (all of the above) Geodesic.LONG_UNROLL The default value of outmask is LATITUDE | LONGITUDE | AZIMUTH | DISTANCE. """ from geographiclib.geodesic import Geodesic Geodesic.CheckDistance(a12) result = {'lat1': self._lat1, 'lon1': self._lon1 if outmask & Geodesic.LONG_UNROLL else Math.AngNormalize(self._lon1), 'azi1': self._azi1, 'a12': a12} a12, lat2, lon2, azi2, s12, m12, M12, M21, S12 = self.GenPosition( True, a12, outmask) outmask &= Geodesic.OUT_MASK if outmask & Geodesic.DISTANCE: result['s12'] = s12 if outmask & Geodesic.LATITUDE: result['lat2'] = lat2 if outmask & Geodesic.LONGITUDE: result['lon2'] = lon2 if outmask & Geodesic.AZIMUTH: result['azi2'] = azi2 if outmask & Geodesic.REDUCEDLENGTH: result['m12'] = m12 if outmask & Geodesic.GEODESICSCALE: result['M12'] = M12; result['M21'] = M21 if outmask & Geodesic.AREA: result['S12'] = S12 return result
def gps2dist_azimuth(lat1, lon1, lat2, lon2, a=WGS84_A, f=WGS84_F): """ Computes the distance between two geographic points on the WGS84 ellipsoid and the forward and backward azimuths between these points. :param lat1: Latitude of point A in degrees (positive for northern, negative for southern hemisphere) :param lon1: Longitude of point A in degrees (positive for eastern, negative for western hemisphere) :param lat2: Latitude of point B in degrees (positive for northern, negative for southern hemisphere) :param lon2: Longitude of point B in degrees (positive for eastern, negative for western hemisphere) :param a: Radius of Earth in m. Uses the value for WGS84 by default. :param f: Flattening of Earth. Uses the value for WGS84 by default. :return: (Great circle distance in m, azimuth A->B in degrees, azimuth B->A in degrees) .. note:: This function will check if you have installed the Python module `geographiclib <http://geographiclib.sf.net>`_ - a very fast module for converting between geographic, UTM, UPS, MGRS, and geocentric coordinates, for geoid calculations, and for solving geodesic problems. Otherwise the locally implemented Vincenty's Inverse formulae (:func:`obspy.core.util.geodetics.calc_vincenty_inverse`) is used which has known limitations for two nearly antipodal points and is ca. 4x slower. """ if HAS_GEOGRAPHICLIB: if lat1 > 90 or lat1 < -90: msg = "Latitude of Point 1 out of bounds! (-90 <= lat1 <=90)" raise ValueError(msg) if lat2 > 90 or lat2 < -90: msg = "Latitude of Point 2 out of bounds! (-90 <= lat2 <=90)" raise ValueError(msg) result = Geodesic(a=a, f=f).Inverse(lat1, lon1, lat2, lon2) azim = result['azi1'] if azim < 0: azim += 360 bazim = result['azi2'] + 180 return (result['s12'], azim, bazim) else: try: values = calc_vincenty_inverse(lat1, lon1, lat2, lon2, a, f) if np.alltrue(np.isnan(values)): raise StopIteration return values except StopIteration: msg = ("Catching unstable calculation on antipodes. " "The currently used Vincenty's Inverse formulae " "has known limitations for two nearly antipodal points. " "Install the Python module 'geographiclib' to solve this " "issue.") warnings.warn(msg) return (20004314.5, 0.0, 0.0) except ValueError as e: raise e
def Position(self, s12, outmask=GeodesicCapability.LATITUDE | GeodesicCapability.LONGITUDE | GeodesicCapability.AZIMUTH): """ Return the point a distance s12 along the geodesic line. Return a dictionary with (some) of the following entries: lat1 latitude of point 1 lon1 longitude of point 1 azi1 azimuth of line at point 1 lat2 latitude of point 2 lon2 longitude of point 2 azi2 azimuth of line at point 2 s12 distance from 1 to 2 a12 arc length on auxiliary sphere from 1 to 2 m12 reduced length of geodesic M12 geodesic scale 2 relative to 1 M21 geodesic scale 1 relative to 2 S12 area between geodesic and equator outmask determines which fields get included and if outmask is omitted, then only the basic geodesic fields are computed. The mask is an or'ed combination of the following values Geodesic.LATITUDE Geodesic.LONGITUDE Geodesic.AZIMUTH Geodesic.DISTANCE Geodesic.REDUCEDLENGTH Geodesic.GEODESICSCALE Geodesic.AREA Geodesic.ALL """ from geographiclib.geodesic import Geodesic Geodesic.CheckDistance(s12) result = { 'lat1': self._lat1, 'lon1': self._lon1, 'azi1': self._azi1, 's12': s12 } a12, lat2, lon2, azi2, s12, m12, M12, M21, S12 = self.GenPosition( False, s12, outmask) outmask &= Geodesic.OUT_ALL result['a12'] = a12 if outmask & Geodesic.LATITUDE: result['lat2'] = lat2 if outmask & Geodesic.LONGITUDE: result['lon2'] = lon2 if outmask & Geodesic.AZIMUTH: result['azi2'] = azi2 if outmask & Geodesic.REDUCEDLENGTH: result['m12'] = m12 if outmask & Geodesic.GEODESICSCALE: result['M12'] = M12 result['M21'] = M21 if outmask & Geodesic.AREA: result['S12'] = S12 return result
def destination(self, point, bearing, distance=None): point = Point(point) lat1 = point.latitude lon1 = point.longitude azi1 = bearing if distance is None: distance = self if isinstance(distance, Distance): distance = distance.kilometers if not (isinstance(self.geod, Geodesic) and self.geod.a == self.ELLIPSOID[0] and self.geod.f == self.ELLIPSOID[2]): self.geod = Geodesic(self.ELLIPSOID[0], self.ELLIPSOID[2]) r = self.geod.Direct(lat1, lon1, azi1, distance, Geodesic.LATITUDE | Geodesic.LONGITUDE) return Point(r['lat2'], r['lon2'])
def calculate_distances(self): #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Calculates the distance between all neuron positions *along the surface* (using geodesic distances) ~ #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # The geographiclib.geodesic module is needed to calculate geodesic distance along the surface of a spheroid. # - Library is based on Karney 2013 (seemingly written by Karney himself) # - See https://geographiclib.sourceforge.io/1.48/python/ try: from geographiclib.geodesic import Geodesic except: exitOnNetworkGeometryError("The geographiclib.geodesic module is needed to calculate geodesic distance along the surface of a spheroid, but this module could not be imported.") # The Geodesic object constructor has two parameters: # - a: the equatorial (xy) radius of the ellipsoid # - f: the flattening of the ellipsoid. b = a(1-f) --> f = 1 - b/a (where b is the polar (z) axis) a = self.r_xy b = self.r_z f = 1.0 - b/a geodesic = Geodesic(a, f) # Convert parametric coordinates (theta(lon), phi(lat)) from radian to degree angles degreecoords = numpy.degrees(self.parametricCoords) # print self.coordinates # print "degrees" # print degreecoords for i in range(self.N): for j in range(self.N): # Geodesic.Inverse(...) calculates the geodesic distance between two points along a spheroid surface. # arguments: (lat1, lon1, lat2, lon2, outmask=1025) # - expects lat1, lon1, lat2, lon2 to be latitude(theta)/longitude(phi) angles in degrees # - outmask=1025 tells it to return the distance measure # returns: a Geodesic dictionary including key-value pair 's12', which is the calculated distance between the points. lat1 = degreecoords[i][1] lon1 = degreecoords[i][0] lat2 = degreecoords[j][1] lon2 = degreecoords[j][0] # print geodesic.Inverse(lat1, lon1, lat2, lon2, outmask=1025) # print "lat" + str(lat1) + " lon" + str(lon1) + " :: lat" + str(lat2) + " lon" + str(lon2) dist = geodesic.Inverse(lat1, lon1, lat2, lon2, outmask=1025)['s12'] self.distances[i,j] = dist
def find_corners(lat_lon_file, tolerance=1, min_angle=np.pi * 0.22, geoid=Geodesic(6371., 0.), lsz=None): """ Credit to unutbu from Stack Overflow (https://stackoverflow.com/questions/14631776/calculate-turning-points-pivot-points-in-trajectory-path) Modifed by PySkew author Kevin Gaastra to run on a sphere Runs the Ramer-Douglas-Peucker algorithm to simplify the lat, lon path stored in the specified lat_lon_file. It prints out the number of points in the origional path and the number of points in the simplified path. Then calculates the points in the simplified path that constitute a turn greater than min_angle. Parameters ---------- lat_lon_file - file containing lattitude and longitude in that order in columns seperated by whitespace tolerance - the tolerance of the RDP algorithm more tolerance=less points kept (default=1) min_angle - angle in radians that should constitute a "significant" change in heading Returns ------- Tuple - (numpy array number_of_points_in_origionalx2 containing the origional data, number_of_points_in_simplifiedx2 containing the simplified data, and an array containing the locations in the simplified data where turns exceeding min_angle are found) """ path = os.path.expanduser(lat_lon_file) points = np.genfromtxt(path) print("original number of points = %d" % len(points)) # Use the Ramer-Douglas-Peucker algorithm to simplify the path # http://en.wikipedia.org/wiki/Ramer-Douglas-Peucker_algorithm # Python implementation: https://github.com/sebleier/RDP/ simplified = np.array( rdp.rdp(points.tolist(), tolerance, dist=create_geo_gc_dist(geoid))) # simplified = np.array(kmg9_rdp(points.tolist(), tolerance, create_geo_gc_dist(geoid),lsz=lsz)) print("simplified number of points = %d" % len(simplified)) # compute the direction vectors on the simplified curve # directions = np.diff(simplified, axis=0) theta = geo_angle(simplified) # Select the index of the points with the greatest theta # Large theta is associated with greatest change in direction. idx = np.where(theta > np.rad2deg(min_angle))[0] + 1 print("number of significant turns = %d" % len(idx)) return points, simplified, idx
def geo_angle(points, geoid=Geodesic(6371., 0.)): """ Returns the angles between two great circles for a set of points Parameters ---------- points : 2D-Ndarray lat-lon array of points on a sphere between which to calculate angles Returns ---------- angles : 1D-Ndarray angles between the great circles defined by points of shape (N-1,), with each value between 0 and 180 degrees. """ return np.abs( np.diff( list( map(lambda a, b, c, d: geoid.Inverse(a, b, c, d)["azi1"], points[:-1, 0], points[:-1, 1], points[1:, 0], points[1:, 1]))))
def globe_distance( lat1: float, lon1: float, lat2: float, lon2: float, round_to: int = 5, ) -> float: """ Compute the great circle distance in km between two points on Earth. """ _check_latitude(lat1, 'lat1') _check_latitude(lat2, 'lat2') result = Geodesic(a=WGS84_A, f=WGS84_F).Inverse(lat1, lon1, lat2, lon2) # Used to cope with minor floating point differences between operating # systems that we don't care about for ESCI451 if round_to: return round(result['s12'] / 1000., round_to) else: return result['s12'] / 1000.
def __init__(self, geod, lat1, lon1, azi1, caps=GeodesicCapability.ALL): from geographiclib.geodesic import Geodesic self._a = geod._a self._f = geod._f self._b = geod._b self._c2 = geod._c2 self._f1 = geod._f1 self._caps = caps | Geodesic.LATITUDE | Geodesic.AZIMUTH # Guard against underflow in salp0 azi1 = Geodesic.AngRound(Math.AngNormalize(azi1)) lon1 = Math.AngNormalize(lon1) self._lat1 = lat1 self._lon1 = lon1 self._azi1 = azi1 # alp1 is in [0, pi] alp1 = azi1 * Math.degree # Enforce sin(pi) == 0 and cos(pi/2) == 0. Better to face the ensuing # problems directly than to skirt them. self._salp1 = 0 if azi1 == -180 else math.sin(alp1) self._calp1 = 0 if abs(azi1) == 90 else math.cos(alp1) # real cbet1, sbet1, phi phi = lat1 * Math.degree # Ensure cbet1 = +epsilon at poles sbet1 = self._f1 * math.sin(phi) cbet1 = Geodesic.tiny_ if abs(lat1) == 90 else math.cos(phi) sbet1, cbet1 = Geodesic.SinCosNorm(sbet1, cbet1) self._dn1 = math.sqrt(1 + geod._ep2 * Math.sq(sbet1)) # Evaluate alp0 from sin(alp1) * cos(bet1) = sin(alp0), self._salp0 = self._salp1 * cbet1 # alp0 in [0, pi/2 - |bet1|] # Alt: calp0 = hypot(sbet1, calp1 * cbet1). The following # is slightly better (consider the case salp1 = 0). self._calp0 = math.hypot(self._calp1, self._salp1 * sbet1) # Evaluate sig with tan(bet1) = tan(sig1) * cos(alp1). # sig = 0 is nearest northward crossing of equator. # With bet1 = 0, alp1 = pi/2, we have sig1 = 0 (equatorial line). # With bet1 = pi/2, alp1 = -pi, sig1 = pi/2 # With bet1 = -pi/2, alp1 = 0 , sig1 = -pi/2 # Evaluate omg1 with tan(omg1) = sin(alp0) * tan(sig1). # With alp0 in (0, pi/2], quadrants for sig and omg coincide. # No atan2(0,0) ambiguity at poles since cbet1 = +epsilon. # With alp0 = 0, omg1 = 0 for alp1 = 0, omg1 = pi for alp1 = pi. self._ssig1 = sbet1 self._somg1 = self._salp0 * sbet1 self._csig1 = self._comg1 = (cbet1 * self._calp1 if sbet1 != 0 or self._calp1 != 0 else 1) # sig1 in (-pi, pi] self._ssig1, self._csig1 = Geodesic.SinCosNorm(self._ssig1, self._csig1) # No need to normalize # self._somg1, self._comg1 = Geodesic.SinCosNorm(self._somg1, self._comg1) self._k2 = Math.sq(self._calp0) * geod._ep2 eps = self._k2 / (2 * (1 + math.sqrt(1 + self._k2)) + self._k2) if self._caps & Geodesic.CAP_C1: self._A1m1 = Geodesic.A1m1f(eps) self._C1a = range(Geodesic.nC1_ + 1) Geodesic.C1f(eps, self._C1a) self._B11 = Geodesic.SinCosSeries(True, self._ssig1, self._csig1, self._C1a, Geodesic.nC1_) s = math.sin(self._B11) c = math.cos(self._B11) # tau1 = sig1 + B11 self._stau1 = self._ssig1 * c + self._csig1 * s self._ctau1 = self._csig1 * c - self._ssig1 * s # Not necessary because C1pa reverts C1a # _B11 = -SinCosSeries(true, _stau1, _ctau1, _C1pa, nC1p_) if self._caps & Geodesic.CAP_C1p: self._C1pa = range(Geodesic.nC1p_ + 1) Geodesic.C1pf(eps, self._C1pa) if self._caps & Geodesic.CAP_C2: self._A2m1 = Geodesic.A2m1f(eps) self._C2a = range(Geodesic.nC2_ + 1) Geodesic.C2f(eps, self._C2a) self._B21 = Geodesic.SinCosSeries(True, self._ssig1, self._csig1, self._C2a, Geodesic.nC2_) if self._caps & Geodesic.CAP_C3: self._C3a = range(Geodesic.nC3_) geod.C3f(eps, self._C3a) self._A3c = -self._f * self._salp0 * geod.A3f(eps) self._B31 = Geodesic.SinCosSeries(True, self._ssig1, self._csig1, self._C3a, Geodesic.nC3_ - 1) if self._caps & Geodesic.CAP_C4: self._C4a = range(Geodesic.nC4_) geod.C4f(eps, self._C4a) # Multiplier = a^2 * e^2 * cos(alpha0) * sin(alpha0) self._A4 = Math.sq(self._a) * self._calp0 * self._salp0 * geod._e2 self._B41 = Geodesic.SinCosSeries(False, self._ssig1, self._csig1, self._C4a, Geodesic.nC4_)
def GenPosition(self, arcmode, s12_a12, outmask): from geographiclib.geodesic import Geodesic a12 = lat2 = lon2 = azi2 = s12 = m12 = M12 = M21 = S12 = Math.nan outmask &= self._caps & Geodesic.OUT_ALL if not (arcmode or (self._caps & Geodesic.DISTANCE_IN & Geodesic.OUT_ALL)): # Uninitialized or impossible distance calculation requested return a12, lat2, lon2, azi2, s12, m12, M12, M21, S12 # Avoid warning about uninitialized B12. B12 = 0 AB1 = 0 if arcmode: # Interpret s12_a12 as spherical arc length sig12 = s12_a12 * Math.degree s12a = abs(s12_a12) s12a -= 180 * math.floor(s12a / 180) ssig12 = 0 if s12a == 0 else math.sin(sig12) csig12 = 0 if s12a == 90 else math.cos(sig12) else: # Interpret s12_a12 as distance tau12 = s12_a12 / (self._b * (1 + self._A1m1)) s = math.sin(tau12) c = math.cos(tau12) # tau2 = tau1 + tau12 B12 = -Geodesic.SinCosSeries( True, self._stau1 * c + self._ctau1 * s, self._ctau1 * c - self._stau1 * s, self._C1pa, Geodesic.nC1p_) sig12 = tau12 - (B12 - self._B11) ssig12 = math.sin(sig12) csig12 = math.cos(sig12) if abs(self._f) > 0.01: # Reverted distance series is inaccurate for |f| > 1/100, so correct # sig12 with 1 Newton iteration. The following table shows the # approximate maximum error for a = WGS_a() and various f relative to # GeodesicExact. # erri = the error in the inverse solution (nm) # errd = the error in the direct solution (series only) (nm) # errda = the error in the direct solution (series + 1 Newton) (nm) # # f erri errd errda # -1/5 12e6 1.2e9 69e6 # -1/10 123e3 12e6 765e3 # -1/20 1110 108e3 7155 # -1/50 18.63 200.9 27.12 # -1/100 18.63 23.78 23.37 # -1/150 18.63 21.05 20.26 # 1/150 22.35 24.73 25.83 # 1/100 22.35 25.03 25.31 # 1/50 29.80 231.9 30.44 # 1/20 5376 146e3 10e3 # 1/10 829e3 22e6 1.5e6 # 1/5 157e6 3.8e9 280e6 ssig2 = self._ssig1 * csig12 + self._csig1 * ssig12 csig2 = self._csig1 * csig12 - self._ssig1 * ssig12 B12 = Geodesic.SinCosSeries(True, ssig2, csig2, self._C1a, Geodesic.nC1_) serr = ((1 + self._A1m1) * (sig12 + (B12 - self._B11)) - s12_a12 / self._b) sig12 = sig12 - serr / math.sqrt(1 + self._k2 * Math.sq(ssig2)) ssig12 = math.sin(sig12) csig12 = math.cos(sig12) # Update B12 below # real omg12, lam12, lon12 # real ssig2, csig2, sbet2, cbet2, somg2, comg2, salp2, calp2 # sig2 = sig1 + sig12 ssig2 = self._ssig1 * csig12 + self._csig1 * ssig12 csig2 = self._csig1 * csig12 - self._ssig1 * ssig12 dn2 = math.sqrt(1 + self._k2 * Math.sq(ssig2)) if outmask & (Geodesic.DISTANCE | Geodesic.REDUCEDLENGTH | Geodesic.GEODESICSCALE): if arcmode or abs(self._f) > 0.01: B12 = Geodesic.SinCosSeries(True, ssig2, csig2, self._C1a, Geodesic.nC1_) AB1 = (1 + self._A1m1) * (B12 - self._B11) # sin(bet2) = cos(alp0) * sin(sig2) sbet2 = self._calp0 * ssig2 # Alt: cbet2 = hypot(csig2, salp0 * ssig2) cbet2 = math.hypot(self._salp0, self._calp0 * csig2) if cbet2 == 0: # I.e., salp0 = 0, csig2 = 0. Break the degeneracy in this case cbet2 = csig2 = Geodesic.tiny_ # tan(omg2) = sin(alp0) * tan(sig2) somg2 = self._salp0 * ssig2 comg2 = csig2 # No need to normalize # tan(alp0) = cos(sig2)*tan(alp2) salp2 = self._salp0 calp2 = self._calp0 * csig2 # No need to normalize # omg12 = omg2 - omg1 omg12 = math.atan2(somg2 * self._comg1 - comg2 * self._somg1, comg2 * self._comg1 + somg2 * self._somg1) if outmask & Geodesic.DISTANCE: s12 = self._b * ( (1 + self._A1m1) * sig12 + AB1) if arcmode else s12_a12 if outmask & Geodesic.LONGITUDE: lam12 = omg12 + self._A3c * (sig12 + (Geodesic.SinCosSeries( True, ssig2, csig2, self._C3a, Geodesic.nC3_ - 1) - self._B31)) lon12 = lam12 / Math.degree # Use Math.AngNormalize2 because longitude might have wrapped # multiple times. lon12 = Math.AngNormalize2(lon12) lon2 = Math.AngNormalize(self._lon1 + lon12) if outmask & Geodesic.LATITUDE: lat2 = math.atan2(sbet2, self._f1 * cbet2) / Math.degree if outmask & Geodesic.AZIMUTH: # minus signs give range [-180, 180). 0- converts -0 to +0. azi2 = 0 - math.atan2(-salp2, calp2) / Math.degree if outmask & (Geodesic.REDUCEDLENGTH | Geodesic.GEODESICSCALE): B22 = Geodesic.SinCosSeries(True, ssig2, csig2, self._C2a, Geodesic.nC2_) AB2 = (1 + self._A2m1) * (B22 - self._B21) J12 = (self._A1m1 - self._A2m1) * sig12 + (AB1 - AB2) if outmask & Geodesic.REDUCEDLENGTH: # Add parens around (_csig1 * ssig2) and (_ssig1 * csig2) to ensure # accurate cancellation in the case of coincident points. m12 = self._b * ( (dn2 * (self._csig1 * ssig2) - self._dn1 * (self._ssig1 * csig2)) - self._csig1 * csig2 * J12) if outmask & Geodesic.GEODESICSCALE: t = (self._k2 * (ssig2 - self._ssig1) * (ssig2 + self._ssig1) / (self._dn1 + dn2)) M12 = csig12 + (t * ssig2 - csig2 * J12) * self._ssig1 / self._dn1 M21 = csig12 - (t * self._ssig1 - self._csig1 * J12) * ssig2 / dn2 if outmask & Geodesic.AREA: B42 = Geodesic.SinCosSeries(False, ssig2, csig2, self._C4a, Geodesic.nC4_) # real salp12, calp12 if self._calp0 == 0 or self._salp0 == 0: # alp12 = alp2 - alp1, used in atan2 so no need to normalized salp12 = salp2 * self._calp1 - calp2 * self._salp1 calp12 = calp2 * self._calp1 + salp2 * self._salp1 # The right thing appears to happen if alp1 = +/-180 and alp2 = 0, viz # salp12 = -0 and alp12 = -180. However this depends on the sign being # attached to 0 correctly. The following ensures the correct behavior. if salp12 == 0 and calp12 < 0: salp12 = Geodesic.tiny_ * self._calp1 calp12 = -1 else: # tan(alp) = tan(alp0) * sec(sig) # tan(alp2-alp1) = (tan(alp2) -tan(alp1)) / (tan(alp2)*tan(alp1)+1) # = calp0 * salp0 * (csig1-csig2) / (salp0^2 + calp0^2 * csig1*csig2) # If csig12 > 0, write # csig1 - csig2 = ssig12 * (csig1 * ssig12 / (1 + csig12) + ssig1) # else # csig1 - csig2 = csig1 * (1 - csig12) + ssig12 * ssig1 # No need to normalize salp12 = self._calp0 * self._salp0 * ( self._csig1 * (1 - csig12) + ssig12 * self._ssig1 if csig12 <= 0 else ssig12 * (self._csig1 * ssig12 / (1 + csig12) + self._ssig1)) calp12 = (Math.sq(self._salp0) + Math.sq(self._calp0) * self._csig1 * csig2) S12 = self._c2 * math.atan2(salp12, calp12) + self._A4 * (B42 - self._B41) a12 = s12_a12 if arcmode else sig12 / Math.degree return a12, lat2, lon2, azi2, s12, m12, M12, M21, S12
def Inverse(self, lat1, lon1, lat2, lon2, *outmask): '''Return the C{Inverse} result. ''' d = _Geodesic.Inverse(self, lat1, lon1, lat2, lon2, *outmask) return _Adict(d)
def Direct(self, lat1, lon1, azi1, s12, *outmask): '''Return the C{Direct} result. ''' d = _Geodesic.Direct(self, lat1, lon1, azi1, s12, *outmask) return _Adict(d)
class geodesic(Distance): """ Calculate the geodesic distance between two points. Set which ellipsoidal model of the earth to use by specifying an ``ellipsoid`` keyword argument. The default is 'WGS-84', which is the most globally accurate model. If ``ellipsoid`` is a string, it is looked up in the `ELLIPSOIDS` dictionary to obtain the major and minor semiaxes and the flattening. Otherwise, it should be a tuple with those values. See the comments above the `ELLIPSOIDS` dictionary for more information. Example:: >>> from geopy.distance import geodesic >>> newport_ri = (41.49008, -71.312796) >>> cleveland_oh = (41.499498, -81.695391) >>> print(geodesic(newport_ri, cleveland_oh).miles) 538.390445368 .. versionadded:: 1.13.0 """ ellipsoid_key = None ELLIPSOID = None geod = None def __init__(self, *args, **kwargs): self.set_ellipsoid(kwargs.pop('ellipsoid', 'WGS-84')) if 'iterations' in kwargs: warnings.warn( 'Ignoring unused `iterations` kwarg for geodesic ' 'distance.', UserWarning) kwargs.pop('iterations', 0) major, minor, f = self.ELLIPSOID super(geodesic, self).__init__(*args, **kwargs) def set_ellipsoid(self, ellipsoid): """ Change the ellipsoid used in the calculation. """ if not isinstance(ellipsoid, (list, tuple)): try: self.ELLIPSOID = ELLIPSOIDS[ellipsoid] self.ellipsoid_key = ellipsoid except KeyError: raise Exception( "Invalid ellipsoid. See geopy.distance.ELLIPSOIDS") else: self.ELLIPSOID = ellipsoid self.ellipsoid_key = None return # Call geographiclib routines for measure and destination def measure(self, a, b): a, b = Point(a), Point(b) lat1, lon1 = a.latitude, a.longitude lat2, lon2 = b.latitude, b.longitude if not (isinstance(self.geod, Geodesic) and self.geod.a == self.ELLIPSOID[0] and self.geod.f == self.ELLIPSOID[2]): self.geod = Geodesic(self.ELLIPSOID[0], self.ELLIPSOID[2]) s12 = self.geod.Inverse(lat1, lon1, lat2, lon2, Geodesic.DISTANCE)['s12'] return s12 def destination(self, point, bearing, distance=None): """ TODO docs. """ point = Point(point) lat1 = point.latitude lon1 = point.longitude azi1 = bearing if distance is None: distance = self if isinstance(distance, Distance): distance = distance.kilometers if not (isinstance(self.geod, Geodesic) and self.geod.a == self.ELLIPSOID[0] and self.geod.f == self.ELLIPSOID[2]): self.geod = Geodesic(self.ELLIPSOID[0], self.ELLIPSOID[2]) r = self.geod.Direct(lat1, lon1, azi1, distance, Geodesic.LATITUDE | Geodesic.LONGITUDE) return Point(r['lat2'], r['lon2'])
def test_GeodSolve33(self): # Check max(-0.0,+0.0) issues 2015-08-22 (triggered by bugs in # Octave -- sind(-0.0) = +0.0 -- and in some version of Visual # Studio -- fmod(-0.0, 360.0) = +0.0. inv = Geodesic.WGS84.Inverse(0, 0, 0, 179) self.assertAlmostEqual(inv["azi1"], 90.00000, delta=0.5e-5) self.assertAlmostEqual(inv["azi2"], 90.00000, delta=0.5e-5) self.assertAlmostEqual(inv["s12"], 19926189, delta=0.5) inv = Geodesic.WGS84.Inverse(0, 0, 0, 179.5) self.assertAlmostEqual(inv["azi1"], 55.96650, delta=0.5e-5) self.assertAlmostEqual(inv["azi2"], 124.03350, delta=0.5e-5) self.assertAlmostEqual(inv["s12"], 19980862, delta=0.5) inv = Geodesic.WGS84.Inverse(0, 0, 0, 180) self.assertAlmostEqual(inv["azi1"], 0.00000, delta=0.5e-5) self.assertAlmostEqual(abs(inv["azi2"]), 180.00000, delta=0.5e-5) self.assertAlmostEqual(inv["s12"], 20003931, delta=0.5) inv = Geodesic.WGS84.Inverse(0, 0, 1, 180) self.assertAlmostEqual(inv["azi1"], 0.00000, delta=0.5e-5) self.assertAlmostEqual(abs(inv["azi2"]), 180.00000, delta=0.5e-5) self.assertAlmostEqual(inv["s12"], 19893357, delta=0.5) geod = Geodesic(6.4e6, 0) inv = geod.Inverse(0, 0, 0, 179) self.assertAlmostEqual(inv["azi1"], 90.00000, delta=0.5e-5) self.assertAlmostEqual(inv["azi2"], 90.00000, delta=0.5e-5) self.assertAlmostEqual(inv["s12"], 19994492, delta=0.5) inv = geod.Inverse(0, 0, 0, 180) self.assertAlmostEqual(inv["azi1"], 0.00000, delta=0.5e-5) self.assertAlmostEqual(abs(inv["azi2"]), 180.00000, delta=0.5e-5) self.assertAlmostEqual(inv["s12"], 20106193, delta=0.5) inv = geod.Inverse(0, 0, 1, 180) self.assertAlmostEqual(inv["azi1"], 0.00000, delta=0.5e-5) self.assertAlmostEqual(abs(inv["azi2"]), 180.00000, delta=0.5e-5) self.assertAlmostEqual(inv["s12"], 19994492, delta=0.5) geod = Geodesic(6.4e6, -1 / 300.0) inv = geod.Inverse(0, 0, 0, 179) self.assertAlmostEqual(inv["azi1"], 90.00000, delta=0.5e-5) self.assertAlmostEqual(inv["azi2"], 90.00000, delta=0.5e-5) self.assertAlmostEqual(inv["s12"], 19994492, delta=0.5) inv = geod.Inverse(0, 0, 0, 180) self.assertAlmostEqual(inv["azi1"], 90.00000, delta=0.5e-5) self.assertAlmostEqual(inv["azi2"], 90.00000, delta=0.5e-5) self.assertAlmostEqual(inv["s12"], 20106193, delta=0.5) inv = geod.Inverse(0, 0, 0.5, 180) self.assertAlmostEqual(inv["azi1"], 33.02493, delta=0.5e-5) self.assertAlmostEqual(inv["azi2"], 146.97364, delta=0.5e-5) self.assertAlmostEqual(inv["s12"], 20082617, delta=0.5) inv = geod.Inverse(0, 0, 1, 180) self.assertAlmostEqual(inv["azi1"], 0.00000, delta=0.5e-5) self.assertAlmostEqual(abs(inv["azi2"]), 180.00000, delta=0.5e-5) self.assertAlmostEqual(inv["s12"], 20027270, delta=0.5)
def _GenPosition(self, arcmode, s12_a12, outmask): """Private: General solution of position along geodesic""" from geographiclib.geodesic import Geodesic a12 = lat2 = lon2 = azi2 = s12 = m12 = M12 = M21 = S12 = Math.nan outmask &= self.caps & Geodesic.OUT_MASK if not (arcmode or (self.caps & (Geodesic.OUT_MASK & Geodesic.DISTANCE_IN))): # Uninitialized or impossible distance calculation requested return a12, lat2, lon2, azi2, s12, m12, M12, M21, S12 # Avoid warning about uninitialized B12. B12 = 0.0; AB1 = 0.0 if arcmode: # Interpret s12_a12 as spherical arc length sig12 = math.radians(s12_a12) ssig12, csig12 = Math.sincosd(s12_a12) else: # Interpret s12_a12 as distance tau12 = s12_a12 / (self._b * (1 + self._A1m1)) s = math.sin(tau12); c = math.cos(tau12) # tau2 = tau1 + tau12 B12 = - Geodesic._SinCosSeries(True, self._stau1 * c + self._ctau1 * s, self._ctau1 * c - self._stau1 * s, self._C1pa) sig12 = tau12 - (B12 - self._B11) ssig12 = math.sin(sig12); csig12 = math.cos(sig12) if abs(self.f) > 0.01: # Reverted distance series is inaccurate for |f| > 1/100, so correct # sig12 with 1 Newton iteration. The following table shows the # approximate maximum error for a = WGS_a() and various f relative to # GeodesicExact. # erri = the error in the inverse solution (nm) # errd = the error in the direct solution (series only) (nm) # errda = the error in the direct solution (series + 1 Newton) (nm) # # f erri errd errda # -1/5 12e6 1.2e9 69e6 # -1/10 123e3 12e6 765e3 # -1/20 1110 108e3 7155 # -1/50 18.63 200.9 27.12 # -1/100 18.63 23.78 23.37 # -1/150 18.63 21.05 20.26 # 1/150 22.35 24.73 25.83 # 1/100 22.35 25.03 25.31 # 1/50 29.80 231.9 30.44 # 1/20 5376 146e3 10e3 # 1/10 829e3 22e6 1.5e6 # 1/5 157e6 3.8e9 280e6 ssig2 = self._ssig1 * csig12 + self._csig1 * ssig12 csig2 = self._csig1 * csig12 - self._ssig1 * ssig12 B12 = Geodesic._SinCosSeries(True, ssig2, csig2, self._C1a) serr = ((1 + self._A1m1) * (sig12 + (B12 - self._B11)) - s12_a12 / self._b) sig12 = sig12 - serr / math.sqrt(1 + self._k2 * Math.sq(ssig2)) ssig12 = math.sin(sig12); csig12 = math.cos(sig12) # Update B12 below # real omg12, lam12, lon12 # real ssig2, csig2, sbet2, cbet2, somg2, comg2, salp2, calp2 # sig2 = sig1 + sig12 ssig2 = self._ssig1 * csig12 + self._csig1 * ssig12 csig2 = self._csig1 * csig12 - self._ssig1 * ssig12 dn2 = math.sqrt(1 + self._k2 * Math.sq(ssig2)) if outmask & ( Geodesic.DISTANCE | Geodesic.REDUCEDLENGTH | Geodesic.GEODESICSCALE): if arcmode or abs(self.f) > 0.01: B12 = Geodesic._SinCosSeries(True, ssig2, csig2, self._C1a) AB1 = (1 + self._A1m1) * (B12 - self._B11) # sin(bet2) = cos(alp0) * sin(sig2) sbet2 = self._calp0 * ssig2 # Alt: cbet2 = hypot(csig2, salp0 * ssig2) cbet2 = math.hypot(self._salp0, self._calp0 * csig2) if cbet2 == 0: # I.e., salp0 = 0, csig2 = 0. Break the degeneracy in this case cbet2 = csig2 = Geodesic.tiny_ # tan(alp0) = cos(sig2)*tan(alp2) salp2 = self._salp0; calp2 = self._calp0 * csig2 # No need to normalize if outmask & Geodesic.DISTANCE: s12 = self._b * ((1 + self._A1m1) * sig12 + AB1) if arcmode else s12_a12 if outmask & Geodesic.LONGITUDE: # tan(omg2) = sin(alp0) * tan(sig2) somg2 = self._salp0 * ssig2; comg2 = csig2 # No need to normalize E = Math.copysign(1, self._salp0) # East or west going? # omg12 = omg2 - omg1 omg12 = (E * (sig12 - (math.atan2( ssig2, csig2) - math.atan2( self._ssig1, self._csig1)) + (math.atan2(E * somg2, comg2) - math.atan2(E * self._somg1, self._comg1))) if outmask & Geodesic.LONG_UNROLL else math.atan2(somg2 * self._comg1 - comg2 * self._somg1, comg2 * self._comg1 + somg2 * self._somg1)) lam12 = omg12 + self._A3c * ( sig12 + (Geodesic._SinCosSeries(True, ssig2, csig2, self._C3a) - self._B31)) lon12 = math.degrees(lam12) lon2 = (self.lon1 + lon12 if outmask & Geodesic.LONG_UNROLL else Math.AngNormalize(Math.AngNormalize(self.lon1) + Math.AngNormalize(lon12))) if outmask & Geodesic.LATITUDE: lat2 = Math.atan2d(sbet2, self._f1 * cbet2) if outmask & Geodesic.AZIMUTH: # minus signs give range [-180, 180). 0- converts -0 to +0. azi2 = Math.atan2d(salp2, calp2) if outmask & (Geodesic.REDUCEDLENGTH | Geodesic.GEODESICSCALE): B22 = Geodesic._SinCosSeries(True, ssig2, csig2, self._C2a) AB2 = (1 + self._A2m1) * (B22 - self._B21) J12 = (self._A1m1 - self._A2m1) * sig12 + (AB1 - AB2) if outmask & Geodesic.REDUCEDLENGTH: # Add parens around (_csig1 * ssig2) and (_ssig1 * csig2) to ensure # accurate cancellation in the case of coincident points. m12 = self._b * (( dn2 * (self._csig1 * ssig2) - self._dn1 * (self._ssig1 * csig2)) - self._csig1 * csig2 * J12) if outmask & Geodesic.GEODESICSCALE: t = (self._k2 * (ssig2 - self._ssig1) * (ssig2 + self._ssig1) / (self._dn1 + dn2)) M12 = csig12 + (t * ssig2 - csig2 * J12) * self._ssig1 / self._dn1 M21 = csig12 - (t * self._ssig1 - self._csig1 * J12) * ssig2 / dn2 if outmask & Geodesic.AREA: B42 = Geodesic._SinCosSeries(False, ssig2, csig2, self._C4a) # real salp12, calp12 if self._calp0 == 0 or self._salp0 == 0: # alp12 = alp2 - alp1, used in atan2 so no need to normalize salp12 = salp2 * self.calp1 - calp2 * self.salp1 calp12 = calp2 * self.calp1 + salp2 * self.salp1 else: # tan(alp) = tan(alp0) * sec(sig) # tan(alp2-alp1) = (tan(alp2) -tan(alp1)) / (tan(alp2)*tan(alp1)+1) # = calp0 * salp0 * (csig1-csig2) / (salp0^2 + calp0^2 * csig1*csig2) # If csig12 > 0, write # csig1 - csig2 = ssig12 * (csig1 * ssig12 / (1 + csig12) + ssig1) # else # csig1 - csig2 = csig1 * (1 - csig12) + ssig12 * ssig1 # No need to normalize salp12 = self._calp0 * self._salp0 * ( self._csig1 * (1 - csig12) + ssig12 * self._ssig1 if csig12 <= 0 else ssig12 * (self._csig1 * ssig12 / (1 + csig12) + self._ssig1)) calp12 = (Math.sq(self._salp0) + Math.sq(self._calp0) * self._csig1 * csig2) S12 = (self._c2 * math.atan2(salp12, calp12) + self._A4 * (B42 - self._B41)) a12 = s12_a12 if arcmode else math.degrees(sig12) return a12, lat2, lon2, azi2, s12, m12, M12, M21, S12
def __init__(self, geod, lat1, lon1, azi1, caps = GeodesicCapability.STANDARD | GeodesicCapability.DISTANCE_IN, salp1 = Math.nan, calp1 = Math.nan): """Construct a GeodesicLine object :param geod: a :class:`~geographiclib.geodesic.Geodesic` object :param lat1: latitude of the first point in degrees :param lon1: longitude of the first point in degrees :param azi1: azimuth at the first point in degrees :param caps: the :ref:`capabilities <outmask>` This creates an object allowing points along a geodesic starting at (*lat1*, *lon1*), with azimuth *azi1* to be found. The default value of *caps* is STANDARD | DISTANCE_IN. The optional parameters *salp1* and *calp1* should not be supplied; they are part of the private interface. """ from geographiclib.geodesic import Geodesic self.a = geod.a """The equatorial radius in meters (readonly)""" self.f = geod.f """The flattening (readonly)""" self._b = geod._b self._c2 = geod._c2 self._f1 = geod._f1 self.caps = (caps | Geodesic.LATITUDE | Geodesic.AZIMUTH | Geodesic.LONG_UNROLL) """the capabilities (readonly)""" # Guard against underflow in salp0 self.lat1 = Math.LatFix(lat1) """the latitude of the first point in degrees (readonly)""" self.lon1 = lon1 """the longitude of the first point in degrees (readonly)""" if Math.isnan(salp1) or Math.isnan(calp1): self.azi1 = Math.AngNormalize(azi1) self.salp1, self.calp1 = Math.sincosd(Math.AngRound(azi1)) else: self.azi1 = azi1 """the azimuth at the first point in degrees (readonly)""" self.salp1 = salp1 """the sine of the azimuth at the first point (readonly)""" self.calp1 = calp1 """the cosine of the azimuth at the first point (readonly)""" # real cbet1, sbet1 sbet1, cbet1 = Math.sincosd(Math.AngRound(lat1)); sbet1 *= self._f1 # Ensure cbet1 = +epsilon at poles sbet1, cbet1 = Math.norm(sbet1, cbet1); cbet1 = max(Geodesic.tiny_, cbet1) self._dn1 = math.sqrt(1 + geod._ep2 * Math.sq(sbet1)) # Evaluate alp0 from sin(alp1) * cos(bet1) = sin(alp0), self._salp0 = self.salp1 * cbet1 # alp0 in [0, pi/2 - |bet1|] # Alt: calp0 = hypot(sbet1, calp1 * cbet1). The following # is slightly better (consider the case salp1 = 0). self._calp0 = math.hypot(self.calp1, self.salp1 * sbet1) # Evaluate sig with tan(bet1) = tan(sig1) * cos(alp1). # sig = 0 is nearest northward crossing of equator. # With bet1 = 0, alp1 = pi/2, we have sig1 = 0 (equatorial line). # With bet1 = pi/2, alp1 = -pi, sig1 = pi/2 # With bet1 = -pi/2, alp1 = 0 , sig1 = -pi/2 # Evaluate omg1 with tan(omg1) = sin(alp0) * tan(sig1). # With alp0 in (0, pi/2], quadrants for sig and omg coincide. # No atan2(0,0) ambiguity at poles since cbet1 = +epsilon. # With alp0 = 0, omg1 = 0 for alp1 = 0, omg1 = pi for alp1 = pi. self._ssig1 = sbet1; self._somg1 = self._salp0 * sbet1 self._csig1 = self._comg1 = (cbet1 * self.calp1 if sbet1 != 0 or self.calp1 != 0 else 1) # sig1 in (-pi, pi] self._ssig1, self._csig1 = Math.norm(self._ssig1, self._csig1) # No need to normalize # self._somg1, self._comg1 = Math.norm(self._somg1, self._comg1) self._k2 = Math.sq(self._calp0) * geod._ep2 eps = self._k2 / (2 * (1 + math.sqrt(1 + self._k2)) + self._k2) if self.caps & Geodesic.CAP_C1: self._A1m1 = Geodesic._A1m1f(eps) self._C1a = list(range(Geodesic.nC1_ + 1)) Geodesic._C1f(eps, self._C1a) self._B11 = Geodesic._SinCosSeries( True, self._ssig1, self._csig1, self._C1a) s = math.sin(self._B11); c = math.cos(self._B11) # tau1 = sig1 + B11 self._stau1 = self._ssig1 * c + self._csig1 * s self._ctau1 = self._csig1 * c - self._ssig1 * s # Not necessary because C1pa reverts C1a # _B11 = -_SinCosSeries(true, _stau1, _ctau1, _C1pa) if self.caps & Geodesic.CAP_C1p: self._C1pa = list(range(Geodesic.nC1p_ + 1)) Geodesic._C1pf(eps, self._C1pa) if self.caps & Geodesic.CAP_C2: self._A2m1 = Geodesic._A2m1f(eps) self._C2a = list(range(Geodesic.nC2_ + 1)) Geodesic._C2f(eps, self._C2a) self._B21 = Geodesic._SinCosSeries( True, self._ssig1, self._csig1, self._C2a) if self.caps & Geodesic.CAP_C3: self._C3a = list(range(Geodesic.nC3_)) geod._C3f(eps, self._C3a) self._A3c = -self.f * self._salp0 * geod._A3f(eps) self._B31 = Geodesic._SinCosSeries( True, self._ssig1, self._csig1, self._C3a) if self.caps & Geodesic.CAP_C4: self._C4a = list(range(Geodesic.nC4_)) geod._C4f(eps, self._C4a) # Multiplier = a^2 * e^2 * cos(alpha0) * sin(alpha0) self._A4 = Math.sq(self.a) * self._calp0 * self._salp0 * geod._e2 self._B41 = Geodesic._SinCosSeries( False, self._ssig1, self._csig1, self._C4a) self.s13 = Math.nan """the distance between point 1 and point 3 in meters (readonly)""" self.a13 = Math.nan """the arc length between point 1 and point 3 in degrees (readonly)"""
def test_GeodSolve26(self): # Check 0/0 problem with area calculation on sphere 2015-09-08 geod = Geodesic(6.4e6, 0) inv = geod.Inverse(1, 2, 3, 4, Geodesic.AREA) self.assertAlmostEqual(inv["S12"], 49911046115.0, delta=0.5)
def add_geo_to_arrivals(arrivals, source_latitude_in_deg, source_longitude_in_deg, receiver_latitude_in_deg, receiver_longitude_in_deg, radius_of_earth_in_km, flattening_of_earth): """ Add geographical information to arrivals. :param arrivals: Set of taup arrivals :type: :class:`Arrivals` :param source_latitude_in_deg: Source location latitude in degrees :type source_latitude_in_deg: float :param source_longitude_in_deg: Source location longitude in degrees :type source_longitude_in_deg: float :param receiver_latitude_in_deg: Receiver location latitude in degrees :type receiver_latitude_in_deg: float :param receiver_longitude_in_deg: Receiver location longitude in degrees :type receiver_longitude_in_deg: float :param radius_of_earth_in_km: Radius of the Earth in km :type radius_of_earth_in_km: float :param flattening_of_earth: Flattening of Earth (0 for a sphere) :type receiver_longitude_in_deg: float :return: List of ``Arrival`` objects, each of which has the time, corresponding phase name, ray parameter, takeoff angle, etc. as attributes. :rtype: :class:`Arrivals` """ if geodetics.HAS_GEOGRAPHICLIB: ellipsoid = Geodesic(a=radius_of_earth_in_km * 1000.0, f=flattening_of_earth) g = ellipsoid.Inverse(source_latitude_in_deg, source_longitude_in_deg, receiver_latitude_in_deg, receiver_longitude_in_deg) azimuth = g['azi1'] line = ellipsoid.Line(source_latitude_in_deg, source_longitude_in_deg, azimuth) # We may need to update many arrival objects # and each could have pierce points and a # path for arrival in arrivals: if arrival.pierce is not None: geo_pierce = np.empty(arrival.pierce.shape, dtype=TimeDistGeo) for i, pierce_point in enumerate(arrival.pierce): pos = line.ArcPosition(np.degrees(pierce_point['dist'])) geo_pierce[i] = (pierce_point['p'], pierce_point['time'], pierce_point['dist'], pierce_point['depth'], pos['lat2'], pos['lon2']) arrival.pierce = geo_pierce if arrival.path is not None: geo_path = np.empty(arrival.path.shape, dtype=TimeDistGeo) for i, path_point in enumerate(arrival.path): pos = line.ArcPosition(np.degrees(path_point['dist'])) geo_path[i] = (path_point['p'], path_point['time'], path_point['dist'], path_point['depth'], pos['lat2'], pos['lon2']) arrival.path = geo_path else: # geographiclib is not installed ... # and obspy/geodetics does not help much msg = "You need to install the Python module 'geographiclib' in " + \ "order to add geographical information to arrivals." raise ImportError(msg) return arrivals
def test_GeodSolve28(self): # Check for bad placement of assignment of r.a12 with |f| > 0.01 (bug in # Java implementation fixed on 2015-05-19). geod = Geodesic(6.4e6, 0.1) dir = geod.Direct(1, 2, 10, 5e6) self.assertAlmostEqual(dir["a12"], 48.55570690, delta=0.5e-8)