Coverage for /usr/local/lib/python3.11/dist-packages/pyrocko/gui/snuffler/snufflings/map/__init__.py: 29%
236 statements
« prev ^ index » next coverage.py v6.5.0, created at 2024-03-07 11:54 +0000
« prev ^ index » next coverage.py v6.5.0, created at 2024-03-07 11:54 +0000
1# https://pyrocko.org - GPLv3
2#
3# The Pyrocko Developers, 21st Century
4# ---|P------/S----------~Lg----------
6from pyrocko.gui.qt_compat import qc, qg
7import os
8from os import path as op
9import sys
11import numpy as num
12import logging
13import posixpath
14import shutil
15import socket
17if sys.version_info >= (3, 0): # noqa
18 from http.server import HTTPServer, SimpleHTTPRequestHandler
19else: # noqa
20 from BaseHTTPServer import HTTPServer
21 from SimpleHTTPServer import SimpleHTTPRequestHandler
23from pyrocko.gui.snuffler.snuffling import Snuffling, Switch, Choice, \
24 NoViewerSet
25from pyrocko.guts import dump_xml
26from pyrocko import util, model, orthodrome as ortho
27from pyrocko.gui import util as gui_util
28from pyrocko import moment_tensor
29from pyrocko.plot.automap import Map
30from pyrocko.util import unquote
31from .xmlMarker import XMLEventMarker, EventMarkerList, XMLStationMarker
32from .xmlMarker import StationMarkerList, MarkerLists
35g_counter = 0
37logger = logging.getLogger('pyrocko.snuffling.map')
40def port_in_use(port):
41 with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
42 return s.connect_ex(('localhost', port)) == 0
45def get_port():
46 port = 9998
47 while port < 11000:
48 if port_in_use(port):
49 port += 1
50 else:
51 break
52 else:
53 raise Exception('No free port found')
55 return port
58def get_magnitude(event):
59 if event.magnitude:
60 mag = event.magnitude
61 elif event.moment_tensor:
62 mag = event.moment_tensor.moment_magnitude()
63 else:
64 mag = 0.
65 return float(mag)
68def convert_event_marker(marker):
69 ev = marker.get_event()
70 depth = None
71 if ev is not None:
72 depth = ev.depth
73 else:
74 return None
76 if depth is None:
77 depth = 0.0
78 ev_name = ev.name if ev.name else '(Event)'
79 xmleventmarker = XMLEventMarker(
80 eventname=ev_name,
81 longitude=float(ev.effective_lon),
82 latitude=float(ev.effective_lat),
83 origintime=util.time_to_str(ev.time),
84 depth=float(depth),
85 magnitude=float(get_magnitude(ev)),
86 active=['no', 'yes'][marker.active])
88 return xmleventmarker
91class RootedHTTPServer(HTTPServer):
93 def __init__(self, base_path, *args, **kwargs):
94 HTTPServer.__init__(self, *args, **kwargs)
95 self.RequestHandlerClass.base_path = base_path
98class RootedHTTPRequestHandler(SimpleHTTPRequestHandler):
100 def log_message(self, format, *args):
101 logger.debug('%s - - [%s] %s\n' % (
102 self.client_address[0],
103 self.log_date_time_string(),
104 format % args))
106 def translate_path(self, path):
107 path = posixpath.normpath(unquote(path))
108 words = path.split('/')
109 words = filter(None, words)
110 path = self.base_path
111 for word in words:
112 drive, word = os.path.splitdrive(word)
113 head, word = os.path.split(word)
114 if word in (os.curdir, os.pardir):
115 continue
116 path = os.path.join(path, word)
117 return path
120class FileServingWorker(qc.QObject):
122 def __init__(self, dirname, *args, **kwargs):
123 super(FileServingWorker, self).__init__(*args, **kwargs)
124 self.dirname = dirname
125 self.httpd = None
127 @qc.pyqtSlot(int)
128 def run(self, port):
129 server_address = ('', port)
130 self.httpd = RootedHTTPServer(
131 self.dirname, server_address, RootedHTTPRequestHandler)
132 sa = self.httpd.socket.getsockname()
133 logger.debug('Serving on port %s' % sa[1])
134 self.httpd.serve_forever()
136 @qc.pyqtSlot()
137 def stop(self):
138 if self.httpd:
139 logger.debug('Shutdown server')
140 self.httpd.shutdown()
141 self.httpd.socket.close()
144class SignalProxy(qc.QObject):
145 content_to_serve = qc.pyqtSignal(int)
148class MapMaker(Snuffling):
150 def help(self):
151 return '''
152<html>
153<body>
154<h1>Map event and stations with OpenStreetMap or Google Maps</h1>
155<p>
156Invokes the standard browser if "Open in external browser" is selected.
157Some browsers do not allow javascript to open and read the xml-file
158containing the necessary information due to the "Same-Origin-Policy".
159In that case you need to reset your standard browser. I.e.: Firefox on
160Linux do: <tt>xdg-settings set default-web-browser firefox.desktop</tt>
161</p>
162<p>
163Clicking one of the plate boundary lines shows a reference regarding that
164plate boundary.
165</p>
166<p>
167The plate boundary database is based on the work done by Peter Bird, who
168kindly permitted usage. <br>
169<p>
170This snuffling can also be called from the command line, if it is stored in
171the default pyrocko location under $HOME/.snufflings<br>
172e.g.:
173<p>
174<code>
175python $HOME/.snufflings/map/snuffling.py --stations=stations.pf
176--events=events_test.pf
177</code>
178<h2>References</h2>
179<i>50. Bird, P. (2003) An updated digital model of plate
180boundaries, Geochemistry Geophysics Geosystems, 4(3), 1027,
181doi:10.1029/2001GC000252.
182</i>
183</p>
184<br>
185Also available at
186<a href="http://peterbird.name/publications/2003_PB2002/2003_PB2002.htm">
187 http://www.peterbird.name</a>
188<p>
189Please note, that in the current implementation the orogens (cross-hatched
190areas in
191<a href="http://peterbird.name/publications/2003_PB2002/Figure_01.gif">
192figure 1</a>)
193are not distinguished from plate boundaries. The orogens are meant to
194mark areas where the plate model is known to be incomplete
195(and/or inapplicable).<br>
196This matter will be pointed out in future releases of this snuffling.
197</body>
198</html>
199'''
201 def setup(self):
202 self.set_name('Map')
203 self.add_parameter(Switch('Only active event', 'only_active', False))
204 self.add_parameter(Switch('Open in external browser',
205 'open_external', False))
206 self.add_parameter(Choice('Provider', 'map_kind', 'OpenStreetMap',
207 ['OpenStreetMap', 'GMT', 'Google Maps']))
209 self.set_live_update(False)
210 self.figcount = 0
211 self.thread = qc.QThread()
212 self.marker_tempdir = self.tempdir()
214 self.file_serving_worker = FileServingWorker(self.marker_tempdir)
215 self.file_serving_worker.moveToThread(self.thread)
216 self.data_proxy = SignalProxy()
217 self.data_proxy.content_to_serve.connect(
218 self.file_serving_worker.run)
219 self.thread.start()
220 self.port = None
222 self.viewer_connected = False
224 def call(self):
225 if self.port is None:
226 self.port = get_port()
228 if not self.viewer_connected:
229 self.get_viewer().about_to_close.connect(
230 self.file_serving_worker.stop)
231 self.viewer_connected = True
233 self.cleanup()
235 try:
236 viewer = self.get_viewer()
237 cli_mode = False
238 except NoViewerSet:
239 viewer = None
240 cli_mode = True
242 if not cli_mode:
243 if self.only_active:
244 _, active_stations = \
245 self.get_active_event_and_stations()
246 else:
247 active_stations = viewer.stations.values()
248 elif cli_mode:
249 active_stations = self.stations
251 station_list = []
252 if active_stations:
253 for stat in active_stations:
254 is_blacklisted = util.match_nslc(viewer.blacklist, stat.nsl())
255 if (viewer and not is_blacklisted) or cli_mode:
256 xml_station_marker = XMLStationMarker(
257 nsl='.'.join(stat.nsl()),
258 longitude=float(stat.effective_lon),
259 latitude=float(stat.effective_lat),
260 active='yes')
262 station_list.append(xml_station_marker)
264 active_station_list = StationMarkerList(stations=station_list)
266 if self.only_active:
267 markers = [viewer.get_active_event_marker()]
268 else:
269 if cli_mode:
270 markers = self.markers
271 else:
272 markers = self.get_selected_markers()
273 if len(markers) == 0:
274 tmin, tmax = self.get_selected_time_range(fallback=True)
275 markers = [m for m in viewer.get_markers()
276 if isinstance(m, gui_util.EventMarker) and
277 m.tmin >= tmin and m.tmax <= tmax]
279 ev_marker_list = []
280 for m in markers:
281 if not isinstance(m, gui_util.EventMarker):
282 continue
283 xmleventmarker = convert_event_marker(m)
284 if xmleventmarker is None:
285 continue
286 ev_marker_list.append(xmleventmarker)
288 event_list = EventMarkerList(events=ev_marker_list)
289 event_station_list = MarkerLists(
290 station_marker_list=active_station_list,
291 event_marker_list=event_list)
293 event_station_list.validate()
294 if self.map_kind != 'GMT':
295 tempdir = self.marker_tempdir
296 if self.map_kind == 'Google Maps':
297 map_fn = 'map_googlemaps.html'
298 elif self.map_kind == 'OpenStreetMap':
299 map_fn = 'map_osm.html'
301 url = 'http://localhost:' + str(self.port) + '/%s' % map_fn
303 files = ['loadxmldoc.js', 'map_util.js', 'plates.kml', map_fn]
304 snuffling_dir = op.dirname(op.abspath(__file__))
305 for entry in files:
306 shutil.copy(os.path.join(snuffling_dir, entry),
307 os.path.join(tempdir, entry))
308 logger.debug('copied data to %s' % tempdir)
309 markers_fn = os.path.join(self.marker_tempdir, 'markers.xml')
310 self.data_proxy.content_to_serve.emit(self.port)
311 dump_xml(event_station_list, filename=markers_fn)
313 if self.open_external:
314 qg.QDesktopServices.openUrl(qc.QUrl(url))
315 else:
316 global g_counter
317 g_counter += 1
318 self.web_frame(
319 url,
320 name='Map %i (%s)' % (g_counter, self.map_kind))
321 else:
322 lats_all = []
323 lons_all = []
325 slats = []
326 slons = []
327 slabels = []
328 for s in active_stations:
329 slats.append(s.effective_lat)
330 slons.append(s.effective_lon)
331 slabels.append('.'.join(s.nsl()))
333 elats = []
334 elons = []
335 elats = []
336 elons = []
337 psmeca_input = []
338 markers = self.get_selected_markers()
339 for m in markers:
340 if isinstance(m, gui_util.EventMarker):
341 e = m.get_event()
342 elats.append(e.effective_lat)
343 elons.append(e.effective_lon)
344 if e.moment_tensor is not None:
345 mt = e.moment_tensor.m6()
346 psmeca_input.append(
347 (e.effective_lon, e.effective_lat,
348 e.depth/1000., mt[0], mt[1],
349 mt[2], mt[3], mt[4], mt[5],
350 1., e.effective_lon, e.effective_lat, e.name))
351 else:
352 if e.magnitude is None:
353 moment = -1.
354 else:
355 moment = moment_tensor.magnitude_to_moment(
356 e.magnitude)
357 psmeca_input.append(
358 (e.effective_lon, e.effective_lat,
359 e.depth/1000.,
360 moment/3., moment/3., moment/3.,
361 0., 0., 0., 1.,
362 e.effective_lon, e.effective_lat, e.name))
364 lats_all.extend(elats)
365 lons_all.extend(elons)
366 lats_all.extend(slats)
367 lons_all.extend(slons)
369 lats_all = num.array(lats_all)
370 lons_all = num.array(lons_all)
372 if len(lats_all) == 0:
373 return
375 center_lat, center_lon = ortho.geographic_midpoint(
376 lats_all, lons_all)
377 ntotal = len(lats_all)
378 clats = num.ones(ntotal) * center_lat
379 clons = num.ones(ntotal) * center_lon
380 dists = ortho.distance_accurate50m_numpy(
381 clats, clons, lats_all, lons_all)
383 maxd = num.max(dists) or 0.
384 m = Map(
385 lat=center_lat, lon=center_lon,
386 radius=max(10000., maxd) * 1.1,
387 width=35, height=25,
388 show_grid=True,
389 show_topo=True,
390 color_dry=(238, 236, 230),
391 topo_cpt_wet='light_sea_uniform',
392 topo_cpt_dry='light_land_uniform',
393 illuminate=True,
394 illuminate_factor_ocean=0.15,
395 show_rivers=False,
396 show_plates=False)
398 m.gmt.psxy(in_columns=(slons, slats), S='t15p', G='black', *m.jxyr)
399 for i in range(len(active_stations)):
400 m.add_label(slats[i], slons[i], slabels[i])
402 m.gmt.psmeca(
403 in_rows=psmeca_input, S='m1.0', G='red', C='5p,0/0/0', *m.jxyr)
405 tmpdir = self.tempdir()
407 self.outfn = os.path.join(tmpdir, '%i.png' % self.figcount)
408 m.save(self.outfn)
409 f = self.pixmap_frame(self.outfn) # noqa
410 # f = self.svg_frame(self.outfn, name='XX')
412 def configure_cli_parser(self, parser):
414 parser.add_option(
415 '--events',
416 dest='events_filename',
417 default=None,
418 metavar='FILENAME',
419 help='Read markers from FILENAME')
421 parser.add_option(
422 '--markers',
423 dest='markers_filename',
424 default=None,
425 metavar='FILENAME',
426 help='Read markers from FILENAME')
428 parser.add_option(
429 '--stations',
430 dest='stations_filename',
431 default=None,
432 metavar='FILENAME',
433 help='Read stations from FILENAME')
435 parser.add_option(
436 '--provider',
437 dest='map_provider',
438 default='google',
439 help='map provider [google | osm] (default=osm)')
441 def __del__(self):
442 self.file_serving_worker.stop()
443 self.thread.quit()
444 self.thread.wait()
447def __snufflings__():
448 return [MapMaker()]
451if __name__ == '__main__':
452 util.setup_logging('map.py', 'info')
453 s = MapMaker()
454 options, args, parser = s.setup_cli()
455 s.markers = []
457 if options.stations_filename:
458 stations = model.load_stations(options.stations_filename)
459 s.stations = stations
460 else:
461 s.stations = None
463 if options.events_filename:
464 events = model.load_events(filename=options.events_filename)
465 markers = [gui_util.EventMarker(e) for e in events]
466 s.markers.extend(markers)
468 if options.markers_filename:
469 markers = gui_util.load_markers(options.markers_filename)
470 s.markers.extend(markers)
471 s.open_external = True
472 mapmap = {'google': 'Google Maps', 'osm': 'OpenStreetMap'}
473 s.map_kind = mapmap[options.map_provider]
474 s.call()