Coverage for /usr/local/lib/python3.11/dist-packages/pyrocko/model/location.py: 100%
95 statements
« prev ^ index » next coverage.py v6.5.0, created at 2023-10-04 09:52 +0000
« prev ^ index » next coverage.py v6.5.0, created at 2023-10-04 09:52 +0000
1# http://pyrocko.org - GPLv3
2#
3# The Pyrocko Developers, 21st Century
4# ---|P------/S----------~Lg----------
6'''
7Representation of a geographical location, base class for stations, events,
8etc.
9'''
11import numpy as num
12import math
14from pyrocko import orthodrome
15from pyrocko.guts import Object, Float
18guts_prefix = 'pf'
20d2r = math.pi / 180.
21r2d = 1.0 / d2r
22km = 1000.
25def latlondepth_to_cartesian(lat, lon, depth):
26 return orthodrome.geodetic_to_ecef(lat, lon, -depth)
29class Location(Object):
30 '''
31 Geographical location.
33 The location is given by a reference point at the earth's surface
34 (:py:attr:`lat`, :py:attr:`lon`, :py:attr:`elevation`) and a cartesian
35 offset from this point (:py:attr:`north_shift`, :py:attr:`east_shift`,
36 :py:attr:`depth`). The offset corrected lat/lon coordinates of the location
37 can be accessed though the :py:attr:`effective_latlon`,
38 :py:attr:`effective_lat`, and :py:attr:`effective_lon` properties.
39 '''
41 lat = Float.T(
42 default=0.0,
43 optional=True,
44 help='Latitude of reference point [deg].')
46 lon = Float.T(
47 default=0.0,
48 optional=True,
49 help='Longitude of reference point [deg].')
51 north_shift = Float.T(
52 default=0.,
53 optional=True,
54 help='Northward cartesian offset from reference point [m].')
56 east_shift = Float.T(
57 default=0.,
58 optional=True,
59 help='Eastward cartesian offset from reference point [m].')
61 elevation = Float.T(
62 default=0.0,
63 optional=True,
64 help='Surface elevation, above sea level [m].')
66 depth = Float.T(
67 default=0.0,
68 help='Depth, below surface [m].')
70 def __init__(self, **kwargs):
71 Object.__init__(self, **kwargs)
72 self._latlon = None
74 def __setattr__(self, name, value):
75 if name in ('lat', 'lon', 'north_shift', 'east_shift'):
76 self.__dict__['_latlon'] = None
78 Object.__setattr__(self, name, value)
80 @property
81 def effective_latlon(self):
82 '''
83 Property holding the offset-corrected lat/lon pair of the location.
84 '''
86 if self._latlon is None:
87 if self.north_shift == 0.0 and self.east_shift == 0.0:
88 self._latlon = self.lat, self.lon
89 else:
90 self._latlon = tuple(float(x) for x in orthodrome.ne_to_latlon(
91 self.lat, self.lon, self.north_shift, self.east_shift))
93 return self._latlon
95 @property
96 def effective_lat(self):
97 '''
98 Property holding the offset-corrected latitude of the location.
99 '''
101 return self.effective_latlon[0]
103 @property
104 def effective_lon(self):
105 '''
106 Property holding the offset-corrected longitude of the location.
107 '''
109 return self.effective_latlon[1]
111 def same_origin(self, other):
112 '''
113 Check whether other location object has the same reference location.
114 '''
116 return self.lat == other.lat and self.lon == other.lon
118 def distance_to(self, other):
119 '''
120 Compute surface distance [m] to other location object.
123 '''
125 if self.same_origin(other):
126 other_north_shift, other_east_shift = get_offset(other)
128 return math.sqrt((self.north_shift - other_north_shift)**2 +
129 (self.east_shift - other_east_shift)**2)
131 else:
132 slat, slon = self.effective_latlon
133 rlat, rlon = get_effective_latlon(other)
135 return float(orthodrome.distance_accurate50m_numpy(
136 slat, slon, rlat, rlon)[0])
138 def distance_3d_to(self, other):
139 '''
140 Compute 3D distance [m] to other location object.
142 All coordinates are transformed to cartesian coordinates if necessary
143 then distance is:
145 .. math::
147 \\Delta = \\sqrt{\\Delta {\\bf x}^2 + \\Delta {\\bf y}^2 + \
148 \\Delta {\\bf z}^2}
150 '''
152 if self.same_origin(other):
153 other_north_shift, other_east_shift = get_offset(other)
154 return math.sqrt((self.north_shift - other_north_shift)**2 +
155 (self.east_shift - other_east_shift)**2 +
156 (self.depth - other.depth)**2)
158 else:
159 slat, slon = self.effective_latlon
160 rlat, rlon = get_effective_latlon(other)
162 sx, sy, sz = latlondepth_to_cartesian(slat, slon, self.depth)
163 rx, ry, rz = latlondepth_to_cartesian(rlat, rlon, other.depth)
165 return math.sqrt((sx-rx)**2 + (sy-ry)**2 + (sz-rz)**2)
167 def offset_to(self, other):
168 if self.same_origin(other):
169 other_north_shift, other_east_shift = get_offset(other)
170 return (
171 other_north_shift - self.north_shift,
172 other_east_shift - self.east_shift)
174 else:
175 azi, bazi = self.azibazi_to(other)
176 dist = self.distance_to(other)
177 return dist*math.cos(azi*d2r), dist*math.sin(azi*d2r)
179 def azibazi_to(self, other):
180 '''
181 Compute azimuth and backazimuth to and from other location object.
182 '''
184 if self.same_origin(other):
185 other_north_shift, other_east_shift = get_offset(other)
186 azi = r2d * math.atan2(other_east_shift - self.east_shift,
187 other_north_shift - self.north_shift)
189 bazi = azi + 180.
190 else:
191 slat, slon = self.effective_latlon
192 rlat, rlon = get_effective_latlon(other)
193 azi, bazi = orthodrome.azibazi(slat, slon, rlat, rlon)
195 return float(azi), float(bazi)
197 def set_origin(self, lat, lon):
198 lat = float(lat)
199 lon = float(lon)
200 elat, elon = self.effective_latlon
201 n, e = orthodrome.latlon_to_ne(lat, lon, elat, elon)
202 self.lat = lat
203 self.lon = lon
204 self.north_shift = float(n)
205 self.east_shift = float(e)
206 self._latlon = elat, elon # unchanged
208 @property
209 def coords5(self):
210 return num.array([
211 self.lat, self.lon, self.north_shift, self.east_shift, self.depth])
214def get_offset(obj):
215 try:
216 return obj.north_shift, obj.east_shift
217 except AttributeError:
218 return 0.0, 0.0
221def get_effective_latlon(obj):
222 try:
223 return obj.effective_latlon
224 except AttributeError:
225 return obj.lat, obj.lon