From 124efe8f2cb0c6cc44d5b53cfb0bda228f47b869 Mon Sep 17 00:00:00 2001 From: "Kasper D. Fischer" Date: Tue, 10 Mar 2015 13:19:15 +0000 Subject: [PATCH 1/6] Adding more.inc.de for additonal info in the "mehr" tab. --- .gitattributes | 1 + www/more.inc.de | 1 + 2 files changed, 2 insertions(+) create mode 100644 www/more.inc.de diff --git a/.gitattributes b/.gitattributes index e105340..5f6f4cd 100644 --- a/.gitattributes +++ b/.gitattributes @@ -27,4 +27,5 @@ www/external/widget-pager.js -text www/impressum.inc.de -text www/info.inc.de -text www/logo_RUB_155x30.png -text +www/more.inc.de -text www/spinner.gif -text diff --git a/www/more.inc.de b/www/more.inc.de new file mode 100644 index 0000000..bb1dfa7 --- /dev/null +++ b/www/more.inc.de @@ -0,0 +1 @@ +Das seismologische Netz der Ruhr-Universität besteht aus 13 Stationen. Zwei breitbandige Stationen (BUG und IBBN) sind gleichzeitig Bestandteil des Deutschen Seismologischen Regionalnetzes (GRSN). Sie befinden sich in der Nähe der Ruhr-Universität in einem stillgelegten Stollen der Zeche Klosterbusch bzw. bei Ibbenbüren. Weitere Außenstationen gibt es bei Rheinberg (BRHE) und Hünxe (ZERL, westliches Ruhrgebiet), bei Haltern (BAVN, nörldiches Ruhrgebiet) und Hamm (HMES, östliches Ruhrgebiet). In Ibbenbüren ergänzen die kurzperiodischen Stationen IBBE und IBBS das Stationsnetz. Im Bereich der Ruhr-Universität werden weiterhin 5 kurzperiodische Stationen (BTEZ, BSHA, BKLB, BHOF, BULI) betrieben, die zur Ortung von Ereignissen im Ruhrgebiet verwendet werden. Das von den Seismometern registrierte Messsignal wird an den Stationen digitalisiert und kontinuierlich an die Auswertezentrale an der Ruhr-Universität Bochum übertragen. \ No newline at end of file From 08ce986f24b43ba61a0b84ec485fa6bc9faeca76 Mon Sep 17 00:00:00 2001 From: "Kasper D. Fischer" Date: Tue, 10 Mar 2015 13:20:23 +0000 Subject: [PATCH 2/6] Adding wsgi directory for seismogram plotting and other wepapps. From 23ab30907f335edae5643cdecdebb21aa313d0f8 Mon Sep 17 00:00:00 2001 From: "Kasper D. Fischer" Date: Tue, 10 Mar 2015 13:25:41 +0000 Subject: [PATCH 3/6] Branching utils/trunk/python scripts to www/trunk/wsgi to start plotting webapp. --- wsgi/plotFDSN.py | 322 +++++++++++++++++++++++++++++++++++++++++++ wsgi/traceDayplot.py | 114 +++++++++++++++ 2 files changed, 436 insertions(+) create mode 100755 wsgi/plotFDSN.py create mode 100755 wsgi/traceDayplot.py diff --git a/wsgi/plotFDSN.py b/wsgi/plotFDSN.py new file mode 100755 index 0000000..8ffd530 --- /dev/null +++ b/wsgi/plotFDSN.py @@ -0,0 +1,322 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" + Get waveformdata from FDSN web service and create a fancy plot + This programm runs as a script or as a WSGI application. + + Subversion information: + $Id$ + + :license + Copyright 2015 Kasper Fischer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + MA 02110-1301, USA. +""" + + +def utc2local(utctime, timezone='Europe/Berlin'): + """ + utc2local(utctime, timezone='Europe/Berlin') + converts the UTCDateTime object utctime into a + datetime object of the given timezone + """ + + import pytz + + utc = pytz.utc + local = pytz.timezone(timezone) + utctime = utc.localize(utctime.datetime) + return local.normalize(utctime.astimezone(local)) + +def fancy_plot(st, wsgi=False, img_format='png', color=True): + """ + creating fancy plot from ObsPy stream st + returns cStringIO object if wsgi == True + :type st: ObsPy Stream object + :type wsgi: bool + :type img_format: str + :type color: bool + """ + + import matplotlib as mpl + from matplotlib.dates import date2num, AutoDateLocator, AutoDateFormatter + + if wsgi: + try: + import cStringIO as StringIO + except ImportError: + import StringIO as StringIO + import matplotlib.pyplot as plt + from numpy import arange + + # setup figure parameters + if wsgi: + mpl.rcParams['figure.figsize'] = [12.0, 6.75] # 16:9 aspect ratio + else: + mpl.rcParams['figure.figsize'] = [16.0, 9.0] # 16:9 aspect ratio + + # get starttime, endtime, localtime and timezone + tr = st[0] + stime = tr.stats.starttime + etime = tr.stats.endtime + localtime = utc2local(stime) + if localtime.tzname() == u'CET': + tzname = u'MEZ' + else: + tzname = u'MESZ' + tz = localtime.timetz().tzinfo + + # create proper time vector + d = arange(date2num(stime), date2num(etime), (date2num(etime) - date2num(stime)) / tr.stats.npts) + + # setup time axis decorator + locator = AutoDateLocator(interval_multiples=True, minticks=5, maxticks=8, tz=tz) + minor_locator = AutoDateLocator(interval_multiples=True, minticks=9, maxticks=70, tz=tz) + formatter = AutoDateFormatter(locator, tz=tz) + formatter.scaled[1. / (24. * 60.)] = '%H:%M:%S' + formatter.scaled[1. / (24. * 60. * 60. * 10.)] = '%H:%M:%S.%f' + + # draw figure + if color: + trace_color = {'Z': 'r', 'N': 'b', 'E': 'g'} + else: + trace_color = {'Z': 'k', 'N': 'k', 'E': 'k'} + + fig = plt.figure() + + ax1 = fig.add_subplot(311) + ax1.xaxis.set_major_locator(locator) + ax1.xaxis.set_major_formatter(formatter) + ax1.plot_date(d, st.select(component='Z')[0].data * 1000., trace_color['Z'], label=u'Z', tz=tz) + plt.title(u'Station %s' % st[0].stats.station, fontsize=24) + plt.legend(loc='upper right') + ax1.grid(b=True) + + ax2 = fig.add_subplot(312, sharex=ax1, sharey=ax1) + ax2.plot_date(d, st.select(component='N')[0].data * 1000., trace_color['N'], label=u'N', tz=tz) + ax2.grid(b=True) + plt.ylabel(u'Geschwindigkeit [mm/s]', fontsize=16) + plt.legend(loc='upper right') + + ax3 = fig.add_subplot(313, sharex=ax1, sharey=ax1) + ax3.plot_date(d, st.select(component='E')[0].data * 1000., trace_color['E'], label=u'E', tz=tz) + ax3.grid(b=True) + plt.legend(loc='upper right') + + plt.xlabel(u'%s (%s)' % (localtime.strftime('%d.%m.%Y'), tzname), fontsize=16) + ax1.minorticks_on() + ax1.xaxis.set_minor_locator(minor_locator) + + if wsgi: + buf = StringIO.StringIO() + plt.savefig(buf, format=img_format, facecolor="lightgray") + return buf + else: + if cla['filename']: + plt.savefig(cla['filename'], dpi=300, transparent=True) + else: + plt.show() + + +def get_fdsn(bulk_request, output='VEL', base_url='https://ariadne.geophysik.ruhr-uni-bochum.de'): + """ + Fetches waveform data from FDSN web service and returns + instrument corrected seismogram of type given in parameter output. + Acceptable values for output are "VEL", "DISP" and "ACC" (see ObsPy documentation) + + :rtype : object + :param bulk_request: list + :param output: str + :param base_url: str + :return: ObsPy Stream() object + """ + import warnings + from obspy.fdsn import Client + from obspy.fdsn.header import FDSNException + + try: + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + client = Client(base_url=base_url, debug=False) + st = client.get_waveforms_bulk(bulk_request, attach_response=True) + + st.merge() + stime = st[0].stats.starttime + etime = st[0].stats.endtime + for trace in st.traces: + stime = max(stime, trace.stats.starttime) + etime = min(etime, trace.stats.endtime) + st.trim(starttime=stime, endtime=etime) + + # choose 1 s taper + taper_fraction = 1 / (etime - stime) + for trace in st.traces: + # filter, remove response + trace.filter('bandpass', freqmin=0.01, freqmax=25, corners=3, zerophase=False). \ + remove_response(output=output, zero_mean=True, taper=True, taper_fraction=taper_fraction) + return st + except FDSNException: + return None + + +def main(backend=None, cla=None, wsgi=False): + """ + Main function to create a waveform plot. + This functions calls get_fdsn and fancy_plot to create the figure. + If wsgi == True it returns an ioString object containing the figure. + + :type backend: str + :type cla: dict + :type wsgi: bool + :rtype : object + :param backend: + :param cla: + :param wsgi: + :return: ioString object with created image + """ + + import warnings + import matplotlib as mpl + + if wsgi: + backend = 'Agg' + if backend: + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + mpl.use(backend) + from obspy import UTCDateTime + + if cla: + deltat = cla['length'] + if cla['stime']: + otime = UTCDateTime(cla['stime']) + else: + otime = UTCDateTime() - 3600 - deltat + network = cla['station'].split('.')[0] + station = cla['station'].split('.')[1] + else: + otime = UTCDateTime('2014-11-15T11:35:25Z') + deltat = 30 + network = 'GR' + station = 'BUG' + + st = get_fdsn([(network, station, '*', 'HH?', otime, otime + deltat)], base_url=cla['server']) + if st is not None: + stime = max(st[0].stats.starttime, otime) + etime = min(st[0].stats.endtime, otime + deltat) + st.trim(starttime=stime, endtime=etime) + + if wsgi: + # noinspection PyUnresolvedReferences + return fancy_plot(st, wsgi=wsgi, img_format=cla['format'], color=cla['color']) + else: + # noinspection PyUnresolvedReferences + fancy_plot(st, color=cla['color']) + elif not wsgi: + warnings.warn('No data available!') + + +def application(environ, start_response): + """ + Function application - Wrapper to process wsgi request + :param environ: contains information on the wsgi environment + :type environ: dict + :param start_response: function to process response header by the wsgi server + :type start_response: function + :return: response to be sent to the client by the wsgi server + :rtype: list + """ + + import os + from cgi import FieldStorage + + # set HOME environment variable to a directory the httpd server can write to + # otherwise matplotlib won't work + os.environ['HOME'] = '/tmp/wsgi-kasper' + try: + os.mkdir('/tmp/wsgi-kasper') + except OSError: + pass + + # fake command line arguments + # setting needed default values + cla = {'server': 'http://localhost/'} + + # fill cla with environment variables + form = FieldStorage(fp=environ['wsgi.input'], environ=environ) + cla['station'] = form.getfirst('station', 'GR.BUG').upper() + cla['stime'] = form.getfirst('start_time', []) + cla['length'] = form.getfirst('length', '30') + cla['length'] = abs(int(cla['length'])) + cla['format'] = form.getfirst('format', 'png').lower() + if cla['format'] != 'png' and cla['format'] != 'svg': + cla['format'] = 'png' + cla['color'] = form.getfirst('color', 'TRUE').upper() + if cla['color'] == 'FALSE': + cla['color'] = False + else: + cla['color'] = True + + # process the request + buf = main(cla=cla, backend='Agg', wsgi=True) + if buf is not None: + data = buf.getvalue() + # noinspection PyUnresolvedReferences + buf.close() + data_length = len(data) + + if cla['format'] == u'svg': + cla['format'] = u'svg+xml' + start_response('200 OK', [('Content-Type', 'image/%s' % cla['format']), ('Content-Length', '%d' % data_length)]) + return [data] + else: + start_response('400 Bad Request', []) + return [] + +# __main__ +if __name__ == "__main__": + import os + import argparse + + parser = argparse.ArgumentParser( + description=u'Get event waveform data of all RuhrNet stations.', + epilog=u'$Revision$ ($Date$, $Author$)'.replace( + "$", "")) + parser.add_argument(u'-v', u'-V', u'--version', action='version', + version=u'$Revision$ ($Date$, \ + $Author$)'.replace('$', '')) + parser.add_argument(u'-u', u'--url', action='store', dest='server', + default=u'https://ariadne.geophysik.ruhr-uni-bochum.de', + help=u'Base URL of the FDSN web service (https://ariadne.geophysik.ruhr-uni-bochum.de).') + parser.add_argument(u'-f', u'--file', action='store', dest='filename', metavar='FILENAME', + help=u'Save plot to file FILENAME') + parser.add_argument(u'-c', u'--color', action='store_true', help=u'Create color plot.') + parser.add_argument(u'-l', u'--length', action='store', type=int, default=30, + help=u'Length of waveform window in seconds (30 s).') + group = parser.add_mutually_exclusive_group(required=True) + group.add_argument(u'-s', u'--start_time', dest='stime', action='store', metavar='START', + help=u'Start time of waveform window.') + group.add_argument(u'-e', u'--event', action='store', metavar='EVENTID', + help=u'Get starttime from event P-phase onset at station.') + parser.add_argument(u'station', action='store', metavar='NET.STATION', + help=u'Station to plot.') + + cla = vars(parser.parse_args()) + + if os.getenv('DISPLAY'): + main(cla=cla) + else: + main('Agg', cla) \ No newline at end of file diff --git a/wsgi/traceDayplot.py b/wsgi/traceDayplot.py new file mode 100755 index 0000000..dfb51ac --- /dev/null +++ b/wsgi/traceDayplot.py @@ -0,0 +1,114 @@ +#! /usr/bin/env python +# -*- coding: utf-8 -*- +""" +Produce a dayplot + +Subversion information: +$Id$ +""" +""" +Copyright 2012 Seismological Observatory, Ruhr-University Bochum +http://www.gmg.ruhr-uni-bochum.de/geophysik/seisobs +Contributors: + Martina Rische + Kasper D. Fischer + Sebastian Wehling-Benatelli + +This program is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the Free +Software Foundation, either version 3 of the License, or (at your option) +any later version. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received a copy of the GNU General Public License along +with this program. If not, see http://www.gnu.org/licenses/. +""" + +def traceDayplot(datafile, ftype='bandpass', fmin=1.0, fmax=7.0, + col=('b','r','g'), interval=20.0, outpattern=''): + + ''' + ''' + + + from obspy.core import read + + st = read(datafile) + + #filter + if (ftype == 'bandpass') or (ftype == 'bandstop'): + st.filter(ftype, freqmin=fmin, freqmax=fmax) + elif (ftype == 'lowpass') or (ftype == 'highpass'): + st.filter(ftype, freq=fmin) + + st.merge() + + #plot + for i in range(0,len(st)): + if (outpattern != ''): + imagefile = outpattern.format(st[i].stats.network, + st[i].stats.station, + st[i].stats.channel, + st[i].stats.starttime.year, + st[i].stats.starttime.julday) + st[i].plot(type='dayplot', color=col, interval=interval, + outfile=imagefile) + else: + st[i].plot(type='dayplot', color=col, interval=interval) + + +# __main__ +if __name__ == "__main__": + + # parse arguments + import argparse + parser = argparse.ArgumentParser( + description='Produce filtered 24h-plot (dayplot).', + epilog='$Rev: 403 $ ($Date: 2012-04-13 12:16:22 +0200 (Fri, 13 Apr 2012) $, $Author: kasper $)') + parser.add_argument('-v', '-V','--version', action='version', + version='$Rev: 403 $ ($Date: 2012-04-13 12:16:22 +0200 (Fri, 13 Apr 2012) $, $Author: kasper $)') + parser.add_argument('file', action='store', metavar='FILE', nargs='+', + help='File(s) to use for the dayplot. One dayplot will be used for every file. \ + Use wildcards to use multiple file for one plot') + parser.add_argument('-f', '--filter', action='store', dest='ftype', + default='bandpass', + choices=['none', 'bandpass', 'bandstop', 'lowpass', 'highpass'], + help='Select filtertype to filter the data (default: bandpass)\ + Note: For low- and highpass only fmin is used.') + parser.add_argument('--fmin', action='store', type=float, dest='fmin', + default=1.0, + help='Lower frequency of the filter in Hz (default: 1.0)') + parser.add_argument('--fmax', action='store', type=float, dest='fmax', + default=7.0, + help='Upper frequency of the filter in Hz (default: 7.0)') + parser.add_argument('-c', '--color', '--colour', action='store', dest='color', + default=('b', 'r', 'g'), + help='Color selection to use in the dayplot (default: brg)') + parser.add_argument('-i', '--interval', action='store', type=int, dest='interval', + default=20, + help='Interval length to show in each line of the dayplot in minutes (default: 20)') + parser.add_argument('-o', '--output', action='store', dest='outpattern', + default='', + help="Output filename pattern for the plot. (default: unset, show plot on screen only), \ + Use {0} to substitute network code, \ + {1} to substitute station code, \ + {2} to subsitute station channel, \ + {3} to substitute year, \ + {4} to substitute doy number \ + .format (supported formats: emf, eps, pdf, png, ps, raw, rgba, svg, svgz)") + + cla = parser.parse_args() + if cla.fmin < 0: + cla.fmin = -cla.fmin + if cla.fmax < 0: + cla.fmax = -cla.fmax + + # call traceDayplot(...) for each file + for datafile in cla.file: + traceDayplot(datafile, cla.ftype, cla.fmin, cla.fmax, cla.color, + cla.interval, cla.outpattern) + From 5433cb606a093bb9b63f4e83905a007315bc42d6 Mon Sep 17 00:00:00 2001 From: "Kasper D. Fischer" Date: Tue, 7 Apr 2015 12:38:27 +0000 Subject: [PATCH 4/6] Changing time ordering of request after FDSNWS bug fix. --- www/events.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/events.js b/www/events.js index 3b43e18..f958781 100644 --- a/www/events.js +++ b/www/events.js @@ -88,7 +88,7 @@ function ajaxLoadEvents(stime, etime, eventid, url, target) { } else { request_data = { starttime: sprintf("%d-%02d-%02d", rtime.getFullYear(), rtime.getMonth()+1, rtime.getDate()), - orderby: 'time-asc', + orderby: 'time', minlat: sprintf('%.2f', mapBounds.getSouth()-config['map']['latlngDelta']), maxlat: sprintf('%.2f', mapBounds.getNorth()+config['map']['latlngDelta']), minlon: sprintf('%.2f', mapBounds.getWest()-config['map']['latlngDelta']), From 52b55705b8487f7cf8681513b02cdd0d63a264bd Mon Sep 17 00:00:00 2001 From: "Kasper D. Fischer" Date: Thu, 9 Apr 2015 11:41:17 +0000 Subject: [PATCH 5/6] Adding first English translation. --- www/events.js.en | 434 +++++++++++++++++++++++++++++++++++++++++++++ www/index.html.en | 184 +++++++++++++++++++ www/map.js.en | 221 +++++++++++++++++++++++ www/stations.js.en | 234 ++++++++++++++++++++++++ 4 files changed, 1073 insertions(+) create mode 100644 www/events.js.en create mode 100755 www/index.html.en create mode 100644 www/map.js.en create mode 100644 www/stations.js.en diff --git a/www/events.js.en b/www/events.js.en new file mode 100644 index 0000000..b059173 --- /dev/null +++ b/www/events.js.en @@ -0,0 +1,434 @@ +/********************************************************************** + * events.js * + * script for event specific functions and setup * + **********************************************************************/ + +/* License + Copyright 2014 Kasper D. Fischer + + This program is free software: you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the Free + Software Foundation, either version 3 of the License, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + for more details. + + You should have received a copy of the GNU General Public License along + with this program. If not, see http://www.gnu.org/licenses/. + + $Id$ +*/ + +/* adding row(s) to a table and format date strings afterwards */ +function addTableRow(row, table) { + var added = $('#'+table+' tbody').append(row); + added.find('.tablesorter-childRow td').hide(); + $('#'+table).find('td.utctime-date').each(function() { + $.localtime.formatObject($(this), "dd.MM.yyyy"); + $(this).removeClass('utctime-date'); + $(this).addClass('localtime-date'); + }); + $('#'+table).find('td.utctime-time').each(function() { + $.localtime.formatObject($(this), "HH:mm"); + $(this).removeClass('utctime-time'); + $(this).addClass('localtime-time'); + }); +}; + +/* do reverse geolocation lookup */ +function getGeolocation(id, lat, lng) { + if ( !geolocationTable[id] ) { + $.getJSON( config['ajax']['nominatimURL'], { lat: lat, lon: lng, zoom: 10, format: "json" } ) + .done(function( json ) { + var city = json.address["city"]; + var country = json.address["country"]; + var countryCode = json.address["country_code"].toUpperCase(); + geolocationTable[id] = city; + ( country != "Deutschland" ) ? geolocationTable[id] = geolocationTable[id] + " ("+countryCode+")" : null; + if ( city ) { + $("#eventstable a.toggle[eventid="+id+"]").text(geolocationTable[id]); + var sort = [[0,1],[1,1],[2,1]]; + $("#eventstable").trigger("update", [true]); + $("#eventstable").trigger("updateCache"); + $("#eventstable").trigger("sorton", [sort]); + } else { + console.log("Nominatim did not provide a city tag for "+lat+" / "+lng); + }; + }) + .fail(function( jqxhr, textStatus, error ) { + var err = textStatus + ", " + error; + console.log( "Request Failed: " + err ); + }); + }; +}; + +/* Load events using ajax */ +function ajaxLoadEvents(stime, etime, eventid, url, target) { + var mapBounds = map.getBounds(); + var request_data = {}; + var rtime; + var ajax_url = config['ajax']['eventURL']; + if ( stime == '' || !stime ) { + stime = new Date(); + stime.setDate(stime.getDate()-config['map']['timespan']); + rtime = new Date(); + rtime.setDate(rtime.getDate()-Math.min(config['ajax']['timespan'], config['map']['timespan'])); + } else { + rtime = stime; + }; + if ( url ) { + var ajax_url = url; + request_data = {}; + } else { + if ( eventid ) { + request_data = { eventid: eventid }; + } else { + request_data = { + starttime: sprintf("%d-%02d-%02d", rtime.getFullYear(), rtime.getMonth()+1, rtime.getDate()), + orderby: 'time', + minlat: sprintf('%.2f', mapBounds.getSouth()-config['map']['latlngDelta']), + maxlat: sprintf('%.2f', mapBounds.getNorth()+config['map']['latlngDelta']), + minlon: sprintf('%.2f', mapBounds.getWest()-config['map']['latlngDelta']), + maxlon: sprintf('%.2f', mapBounds.getEast()+config['map']['latlngDelta']), + minmag: sprintf('%.1f', config['event']['minMag']-config['event']['minMagDelta']), + }; + if ( etime ) { + request_data['endtime'] = sprintf("%d-%02d-%02d", etime.getFullYear(), etime.getMonth()+1, etime.getDate()); + }; + }; + }; + if ( etime == '' || !etime ) { etime = new Date(); }; + $.ajax({ + type: "GET", + url: ajax_url, + data: request_data, + dataType: "xml", + success: function (xml) { + $(xml).find('event').each(function () { + var id = $(this).attr('publicID').split('/')[2]; + var mag = $(this).find('magnitude > mag > value').text(); + var otime = $(this).find('origin > time > value').text(); + var lng = $(this).find('origin > longitude > value').text(); + var lat = $(this).find('origin > latitude > value').text(); + var depth = $(this).find('origin > depth > value').text(); + var evaluationMode = $(this).find('origin > evaluationMode').text(); + var evaluationStatus = $(this).find('origin > evaluationStatus').text(); + var type = $(this).find('type').last().text(); + var location + // get location, try this in order: + // regional map name, given value, cached value, or nominatim lookup + geolocationTable[id] ? null : getGeolocation(id, lat, lng); // do AJAX lookup if not cached, location will be updated later + location = ( geolocationTable[id] || getLocation(lat, lng)[0] || $(this).find('description > text').text() ); + // create table row: Date, Time, Mag, Location + if ( !eventTable[id] && $.inArray(type, config['event']['typeWhitelist'] )+1 && $.inArray(evaluationStatus, config['event']['evaluationBlacklist'])<0 && Number(mag)+0.05 >= config['event']['minMag'] ) { + // general event info (1st line) + var row = '' + + ''+otime.split('.')[0]+'Z' + + ''+otime.split('.')[0]+'Z' + + sprintf('%.1f', Number(mag)) + + ''+location+' map' + + ''; + // setting up event details (2nd line) + row += '' + + 'Daten werden geladen ...'; + // setting up download links (3nd line) + var xmlurl = sprintf('%s?formatted=true&includearrivals=true&eventid=%s', config['ajax']['eventURL'], id); + var oTime = new Date(otime); + var sTime = new Date(oTime.getTime()-10*1000.-oTime.getMilliseconds()); + var eTime = new Date(oTime.getTime()+50*1000.-oTime.getMilliseconds()); + var mseedurl = sprintf('%s?net=GE,GR,RN&cha=EH?,HH?&start=%04d-%02d-%02dT%02d:%02d:%02d&end=%04d-%02d-%02dT%02d:%02d:%02d', config['ajax']['mseedURL'], Number(sTime.getUTCFullYear()), Number(sTime.getUTCMonth())+1, Number(sTime.getUTCDate()), Number(sTime.getUTCHours()), Number(sTime.getUTCMinutes()), Number(sTime.getUTCSeconds()), Number(eTime.getUTCFullYear()), Number(eTime.getUTCMonth())+1, Number(eTime.getUTCDate()), Number(eTime.getUTCHours()), Number(eTime.getUTCMinutes()), Number(eTime.getUTCSeconds())); + row += '' + + '' + + sprintf('Download QuakeML or miniSEED', id, xmlurl, id, mseedurl) + + ''; + // add row to table + if ( stime <= oTime && etime >= oTime ) { + addTableRow(row, 'eventstable'); + }; + if ( target ) { + addTableRow(row, target); + } + // create marker + if ((stime <= oTime && etime >= oTime ) || ( id == eventid )) { + var marker = addEventMarker(id, Number(lat), Number(lng), Number(mag), type); + var text = sprintf('

%s

', id, location) + + sprintf('

Ereignis: %s
', id) + + sprintf('Type: %s
', type) + + sprintf('Magnitude: %3.1f
', Number(mag)) + + sprintf('Ort: %.4f °N, %.4f °O
', Number(lat), Number(lng)) + + sprintf('Tiefe: %.1f km
', Number(depth)/1000.) + + sprintf('Zeit: %sZ

', otime.split('.')[0], otime.split('.')[0]); + marker.bindPopup(text); + }; + }; + }); + }, + complete: function () { + var sort = [[0,1],[1,1],[2,1]]; + $("#eventstable").trigger("update", [true]); + $("#eventstable").trigger("updateCache"); + $("#eventstable").trigger("sorton", [sort]); + initMapLink(); + eventLayer.bringToBack(); + highlightFirstEvent(); + }, + error: function( jqxhr, textStatus, error ) { + var err = textStatus + ", " + error; + console.log( "Request Failed: " + err ); + } + }); + // create events csv download link + request_data['format'] = 'text'; + if ( eventid == '' || !eventid ) { $('#events-csv-link').attr('href', config['ajax']['eventURL']+'?'+$.param(request_data)) }; +}; + +/* ajaxLoadEventInfo */ +function ajaxLoadEventInfo(id) { + var request_data = { + eventid: id, + includeArrivals: true, + }; + $.ajax({ + type: "GET", + url: config['ajax']['eventURL'], + data: request_data, + dataType: "xml", + success: function (xml) { + eventDetails[id] = true; + $(xml).find('event').each(function () { + var event = $(this); + var mag = $(this).find('magnitude > mag > value').text(); + var otime = $(this).find('origin > time > value').text(); + var lng = $(this).find('origin > longitude > value').text(); + var lng_err = $(this).find('origin > longitude > uncertainty').text(); + var lat = $(this).find('origin > latitude > value').text(); + var lat_err = $(this).find('origin > latitude > uncertainty').text(); + var depth = $(this).find('origin > depth > value').text(); + var depth_err = $(this).find('origin > depth > uncertainty').text(); + var rms = $(this).find('origin > quality > standardError').text(); + var gap = $(this).find('origin > quality > azimuthalGap').text(); + var phases_count = $(this).find('origin > quality > usedPhaseCount').text(); + var type = $(this).find('type').last().text(); + // setting up general event info + var row = "
"
+					+ sprintf("ID %49s\n", id)
+					+ sprintf("Type %47s\n\n", type)
+					+ "Origin\n"
+					+ sprintf("Date %18s\n", otime.split('T')[0])
+					+ sprintf("Time %18s UTC\n", otime.split('T')[1].substring(0, 11))
+					+ sprintf("Latitude %14.4f °N +- %4.1f km\n",Number(lat), Number(lat_err))
+					+ sprintf("Longitude %13.4f °E +- %4.1f km\n", Number(lng), Number(lng_err))
+					+ sprintf("Depth %14.1f    km +- %4.1f km\n", Number(depth)/1000., Number(depth_err)/1000.)
+					+ sprintf("Magnitude %10.1f\n", Number(mag))
+					+ sprintf("Residual RMS %7.1f    sec\n", Number(rms))
+					+ sprintf("Azimuthal gap %6.1f    °\n\n", Number(gap))
+					+ sprintf("%d Phase arrivals:\n", Number(phases_count))
+					+ "sta  net  dist azi     phase time         res   wt\n";
+				// adding phase info (TODO sort by distance)
+				$(this).find('origin > arrival').each(function() {
+					var pickid = $(this).find('pickID').text();
+					var azi = $(this).find('azimuth').text();
+					var dist = $(this).find('distance').text();
+					var tres = $(this).find('timeResidual').text();
+					var phase = $(this).find('phase').text();
+					var tweight = $(this).find('timeWeight').text();
+					if ( Number(tweight) > 0.0 ) {
+						var waveformid = event.find('pick[publicID="'+pickid+'"] > waveformID');
+						var networkcode = waveformid.attr('networkCode');
+						var stationcode = waveformid.attr('stationCode');
+						var channel = waveformid.attr('channelCode').substring(2,2);
+						var phasemode = event.find('pick[publicID="'+pickid+'"] > evaluationMode').text().substring(0,1).toUpperCase();
+						var picktime = event.find('pick[publicID="'+pickid+'"] > time > value').text().split('T')[1].substring(0,11);
+						row = row
+							+ sprintf('%-4s %2s  %5.1f %5.1f %3s %1s %13s %5.1f %5.2f\n', stationcode, networkcode, Number(dist), Number(azi), phase, phasemode, picktime, Number(tres), Number(tweight));
+					};
+				});
+				row = row + '
'; + $('#eventstable > tbody > tr.event-details > td[eventid='+id+']').html(row); + }); + }, + complete: function () { + null; + }, + error: function( jqxhr, textStatus, error ) { + var err = textStatus + ", " + error; + console.log( "Request Failed: " + err ); + } + }); +}; + +/* toggles visibility of filtered markers + * only events in the event list are shown */ +function toggleFilteredMarkers() { + // show all shown events in map + $("#eventstable > tbody > tr:not(.filtered) > td > a.map-link").each( function() { + if ( $(this).attr("eventid") ) { + map.addLayer(eventTable[$(this).attr("eventid")]); + }; + }); + // hide filtered events in map + $("#eventstable > tbody > tr.filtered > td > a.map-link").each( function() { + if ( $(this).attr("eventid") ) { + map.removeLayer(eventTable[$(this).attr("eventid")]); + }; + }); + highlightFirstEvent(); +}; + +/* Highlight the first event of the event list on the map if no + * other event is selected */ +function highlightFirstEvent() { + var highlightStyle = { + color: config['event']['markerColorH'], + fillColor: config['event']['markerColorH'], + fillOpacity: 1, + }; + var normalStyle = { + fillColor: config['event']['markerColor'], + color: config['event']['markerColor'], + fillOpacity: config['event']['markerOpacity'], + }; + $("#eventstable a.map-link").each( function() { + if ( $(this).attr("eventid") ) { + eventTable[$(this).attr("eventid")].setStyle(normalStyle); + $(this).removeClass('first'); + $(this).text('map'); + }; + }); + $("#eventstable > tbody > tr:not(.filtered):visible").first().find("a.map-link").each(function() { + if ( $(this).attr("eventid") ) { + eventTable[$(this).attr("eventid")].setStyle(highlightStyle); + eventTable[$(this).attr("eventid")].bringToFront(); + $(this).addClass('first'); + $(this).text('map (red)'); + }; + }); +}; + +function highlightEvent( id ) { + var highlightStyle = { + color: config['event']['markerColorH'], + fillColor: config['event']['markerColorH'], + fillOpacity: 1, + }; + var normalStyle = { + fillColor: config['event']['markerColor'], + color: config['event']['markerColor'], + fillOpacity: config['event']['markerOpacity'], + }; + $("#eventstable > tbody > tr:not(.filtered)").find("a.map-link").each( function() { + if ( $(this).attr("eventid") ) { + if ( $(this).attr("eventid") == id ) { + eventTable[$(this).attr("eventid")].setStyle(highlightStyle); + eventTable[$(this).attr("eventid")].bringToFront(); + $(this).addClass('first'); + $(this).text('map (red)'); + } else { + eventTable[$(this).attr("eventid")].setStyle(normalStyle); + $(this).removeClass('first'); + $(this).text('map'); + } + }; + }); +}; + +/********************************************************************** + * document ready * + **********************************************************************/ +$(document).ready(function() { + // tablesorter for event list + $("#eventstable").tablesorter( + { + theme : 'blue', + dateFormat : "ddmmyyyy", + headers: { + 0: { sorter: "shortDate" } + }, + cssChildRow: "tablesorter-childRow", // this is the default setting + widgets: ["uitheme", "zebra", "filter", "pager"], // initialize zebra and filter widgets, "scroller" + widgetOptions: { + // possible variables: {page}, {totalPages}, {filteredPages}, {startRow}, {endRow}, {filteredRows} and {totalRows} + pager_output: '# {startRow} - {endRow} ({totalRows}) | page {page} ({totalPages})', + pager_removeRows: false, + pager_size: 35, + filter_childRows : true, + filter_cssFilter : 'tablesorter-filter', + filter_startsWith : false, + filter_ignoreCase : true, + scroller_height: $('div.map').height() - 250, + scroller_barWidth: 10, + scroller_jumpToHeader: false, + sortList: "[[0,1], [1,1], [2,1]]", + resort: true, + showProcessing: true, + } + }); + // hide child rows + $('#eventstable > tbody > tr.tablesorter-childRow td').hide(); + // update map after filtering + $('#eventstable').bind('filterEnd', function(){ + toggleFilteredMarkers(); + }); + // highlight first event + $('#eventstable').bind('sortEnd', function(){ + highlightFirstEvent(); + }); + $('#eventstable').bind('pagerComplete', function(){ + highlightFirstEvent(); + }); + // show / hide event info + $('#eventstable').delegate('.toggle', 'click' , function(){ + // load event details + var eventid = $(this).attr('eventid'); + ( eventDetails[eventid] ) ? null : ajaxLoadEventInfo(eventid); + + // toggle visibility of selected row + $(this).closest('tr').nextUntil('tr.tablesorter-hasChildRow').find('td').toggle('slow'); + // mark currently selected row and remove class selected from all other rows + // hide other rows + $(this).closest('tr').nextUntil('tr.tablesorter-hasChildRow').find('td').addClass('selected-now'); + $(this).closest('tbody').find('td.selected').each(function(){ + if ( ! $(this).hasClass('selected-now') ) { + $(this).hide(); + $(this).removeClass('selected'); + }; + }); + $(this).closest('tr').nextUntil('tr.tablesorter-hasChildRow').find('td').each(function(){ + $(this).removeClass('selected-now'); + var selected = $(this).hasClass('selected'); + if ( selected ) { + $(this).removeClass('selected'); + highlightFirstEvent(); + } else { + $(this).addClass('selected'); + highlightEvent($(this).attr('eventid')); + }; + }); + return false; + }); + // update selection / type info + $("#events-timespan").text(config['map']['timespan']); + $("#events-minmag").text(sprintf('%.1f', config['event']['minMag'])); + config['event']['typeWhitelist'].map(function(type) { + var typetext; + ( $("#events-type").text() == "Symbols:" ) ? typetext = ' ' : typetext = ', '; + switch ( type ) { + case 'earthquake': + typetext += 'tectonic earthquake (star)'; + break; + case 'explosion': + typetext += 'explosion (hexagon)'; + break; + case 'induced or triggered event': + typetext += '(mining-)induced event (circle)'; + break; + case 'quarry blast': + typetext += 'quarry blast (wheel)'; + break; + }; + $("#events-type").append(typetext); + }); +}); diff --git a/www/index.html.en b/www/index.html.en new file mode 100755 index 0000000..6e6fca5 --- /dev/null +++ b/www/index.html.en @@ -0,0 +1,184 @@ + + + + + + RUB SeisObs - Event and Station Map + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+
+ First + Prev + + Next + Last + + +
+ + + + + + + + + + + + + + + + + + +
DateTimeMag.Place
+

+ Events of the last 180 days with magnitude 1.2 or larger in the area of the map and special events in adjacents regions. Download as CSV file. +

+

+ Symbols: +

+

+ Nominatim Search Courtesy of MapQuest Mapquest Logo +

+
+ +
+
+ First + Prev + + Next + Last + + +
+ + + + + + + + + + + + + + + + + +
NetworkStationLatitude [°]Longitude [°]
+

Download as CSV file.

+
+ +
+ +
+
+

Navigation / Links

+ +

Copyright / License

+ +

Imprint

+ +
+
+
+ +
+
+ + + +
+ + + diff --git a/www/map.js.en b/www/map.js.en new file mode 100644 index 0000000..3f9663c --- /dev/null +++ b/www/map.js.en @@ -0,0 +1,221 @@ +/********************************************************************** + * map.js * + * script for map specific functions and setup * + **********************************************************************/ + +/* License + Copyright 2014 Kasper D. Fischer + + This program is free software: you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the Free + Software Foundation, either version 3 of the License, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + for more details. + + You should have received a copy of the GNU General Public License along + with this program. If not, see http://www.gnu.org/licenses/. + + $Id$ +*/ + +/* add station marker */ +function addStationMarker(id, lat, lng, station) { + var marker = L.triangleMarker(L.latLng(lat, lng), + { + gradient: true, + fillColor: config['station']['markerColor'], + fillOpacity: config['station']['markerOpacity'], + color: config['station']['markerColor'], + weight: 1, + opacity: 1, + radius: config['station']['markerSize'][id] || config['station']['markerSize']['default'], + className: id+' stationMarker', + }); + marker.bindLabel('Station '+station); + stationLayer.addLayer(marker); + stationTable[id] = marker; +}; + +/* add event marker */ +function addEventMarker(id, lat, lng, mag, type) { + if ( eventTable[id] ) { + return eventTable[id]; + } else { + var markerOptions = { + gradient: true, + dropShadow: false, + fillColor: config['event']['markerColor'], + fillOpacity: config['event']['markerOpacity'], + color: config['event']['markerColor'], + weight: 0, + opacity: 1, + className: id+' eventMarker', + radius: mag2radius(mag) + }; + var marker; + switch ( type ) { + case 'earthquake': + marker = L.starMarker(L.latLng(lat, lng), markerOptions); + break; + case 'explosion': + markerOptions['numberOfSides'] = 6; + markerOptions['radius'] = 2.0*markerOptions['radius']; + markerOptions['innerRadius'] = 0.3*markerOptions['radius']; + marker = L.regularPolygonMarker(L.latLng(lat, lng), markerOptions); + break; + case 'quarry blast': + markerOptions['numberOfPoints'] = 7; + markerOptions['innerRadius'] = 0.3*markerOptions['radius']; + marker = L.starMarker(L.latLng(lat, lng), markerOptions); + break; + default: + marker = L.circleMarker(L.latLng(lat, lng), markerOptions); + }; + eventLayer.addLayer(marker); + eventTable[id] = marker; + return marker; + }; +}; + +/* handle to show events on map */ +function initMapLink() { + $("#eventstable > tbody > tr > td > a.map-link").off('click'); + $("#eventstable > tbody > tr > td > a.map-link").on('click' , function(){ + var highlightStyle = { + color: config['event']['markerColorH'], + fillColor: config['event']['markerColorH'], + fillOpacity: 1, + className: $(this).attr('eventid') + } + var normalStyle = { + fillColor: config['event']['markerColor'], + fillOpacity: config['event']['markerOpacity'], + color: config['event']['markerColor'] + }; + // mark currently selected link and remove class selected from all other links + // set everything to normal state + $(this).addClass('selected-now'); + $("#eventstable > tbody > tr:not(.filtered) > td > a.map-link:not(.selected-now)").each(function(){ + $(this).removeClass('selected'); + $(this).text('Karte'); + eventTable[$(this).attr('eventid')].setStyle(normalStyle); + }); + // switch event of first row to normalStyle if it is not the selected one + ( $(this).hasClass('first') ) ? null : eventTable[$("#eventstable > tbody > tr:not(.filtered)").first().find("a.map-link").attr("eventid")].setStyle(normalStyle); + $(this).each(function(){ + $(this).removeClass('selected-now'); + // selected -> unselected + if ( $(this).hasClass('selected') ) { + $(this).removeClass('selected'); + $(this).text('Karte'); + map.setView(config['map']['centerDefault'], config['map']['zoomDefault']); + eventTable[$(this).attr('eventid')].setStyle(normalStyle); + highlightFirstEvent(); + // unselected -> selected + } else { + $(this).addClass('selected'); + $(this).text('at focus (red)'); + map.setView(eventTable[$(this).attr('eventid')].getLatLng(), config['map']['zoomFocus']); + eventTable[$(this).attr('eventid')].setStyle(highlightStyle) + }; + }); + return false; + }); +}; + +/********************************************************************** + * document ready * + **********************************************************************/ +$(document).ready(function() { + + // create a map in the "map" div, set the view to a given place and zoom + map = L.map('map', { zoomControl: false, worldCopyJump: true }).setView(config['map']['centerDefault'], config['map']['zoomDefault']); + new L.Control.Zoom({ position: 'topright' }).addTo(map); + new L.control.scale({position: 'bottomright', imperial: false}).addTo(map); + + // create baselayer + switch ( config['map']['baselayer'] ) { + case 'osmde': // add OpenStreetMap.DE tile layer + L.tileLayer('http://{s}.tile.openstreetmap.de/tiles/osmde/{z}/{x}/{y}.png', + { + attribution: '© OpenStreetMap contributors, CC-BY-SA', + }).addTo(map); + break; + case 'esrigray': // add ESRI Grayscale World Map (neither city nor road names) + L.tileLayer('//server.arcgisonline.com/ArcGIS/rest/services/Canvas/World_Light_Gray_Base/MapServer/tile/{z}/{y}/{x}', + { + attribution: 'Tiles © Esri — Esri, DeLorme, NAVTEQ', + maxZoom: 16 + }).addTo(map); + break; + case 'aerial': // add ESRI WordImagery tile layer + L.tileLayer('http://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', + { + attribution: 'Tiles © Esri — Source: Esri, i-cubed, USDA, USGS, AEX, GeoEye, Getmapping, Aerogrid, IGN, IGP, UPR-EGP, and the GIS User Community' + }).addTo(map); + break; + case 'mapquestgray': // add MapQuestOSM tile layer + L.tileLayer.grayscale('http://otile{s}.mqcdn.com/tiles/1.0.0/map/{z}/{x}/{y}.jpg', + { + subdomains: '1234', + detectRetina: true, + attribution: 'Map data © OpenStreetMap contributors, CC-BY-SA | Tiles Courtesy of MapQuest ', + }).addTo(map); + break; + case 'mapquest': // add MapQuestOSM tile layer + null; + default: + L.tileLayer('http://otile{s}.mqcdn.com/tiles/1.0.0/map/{z}/{x}/{y}.jpg', + { + subdomains: '1234', + detectRetina: true, + attribution: 'Map data © OpenStreetMap contributors, CC-BY-SA | Tiles Courtesy of MapQuest ', + }).addTo(map); + }; + + // create station and event layer + // stationLayer = L.geoJson().addTo(map); + stationLayer = new L.MarkerGroup().addTo(map); + eventLayer = new L.MarkerGroup().addTo(map); + + // load events + ajaxLoadEvents('', '', '', 'events.xml'); + ajaxLoadEvents(); + specialEvents.map(function(id) { + ajaxLoadEvents('', '', id) + }); + toggleFilteredMarkers(); + + // bind popupopen event + map.on('popupopen', function() { + // convert date/time to localtime + $("div.leaflet-popup span.utctime").each(function(){$(this).addClass("localtime").removeClass("utctime");$.localtime.formatObject($(this), "dd.MM.yyyy - HH:mm")}); + openMarkerID = $("div.leaflet-popup h3").attr("eventid"); + if ( openMarkerID ) { + // update city in popup + $("div.leaflet-popup h3").text(geolocationTable[openMarkerID]); + // highlight event in table and show details + // highlightEvent(eventid); + $('#eventstable > tbody > tr > td > a.toggle').each(function() { + if ( $(this).attr('eventid') == openMarkerID ) { + $(this)[0].click(); + }; + }); + }; + }); + map.on('popupclose', function() { + $('#eventstable > tbody > tr > td > a.toggle').each(function() { + if ( $(this).attr('eventid') == openMarkerID ) { + $(this)[0].click(); + }; + }); + }); + + // print icon + // L.easyPrint().addTo(map); + +}); diff --git a/www/stations.js.en b/www/stations.js.en new file mode 100644 index 0000000..5d6c1ba --- /dev/null +++ b/www/stations.js.en @@ -0,0 +1,234 @@ +/********************************************************************** + * stations.js * + * script for station specific functions and setup * + **********************************************************************/ + +/* License + Copyright 2014 Kasper D. Fischer + + This program is free software: you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the Free + Software Foundation, either version 3 of the License, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + for more details. + + You should have received a copy of the GNU General Public License along + with this program. If not, see http://www.gnu.org/licenses/. + + $Id$ +*/ + +/* Load the stations using ajax */ +function loadStations(stime, etime) { + var mapBounds = map.getBounds(); + var N = mapBounds.getNorth(); + var E = mapBounds.getEast(); + var S = mapBounds.getSouth(); + var W = mapBounds.getWest(); + if ( !stime ) { + var stime = new Date(); + stime.setDate(stime.getDate()-config['map']['timespan']); + }; + if ( !etime ) { + var etime = new Date(); + etime.setDate(etime.getDate()+1); + }; + var request_data = { + endafter: sprintf("%d-%02d-%02d", stime.getFullYear(), stime.getMonth()+1, stime.getDate()), + startbefore: sprintf("%d-%02d-%02d", etime.getFullYear(), etime.getMonth()+1, etime.getDate()), + level: 'channel', + minlat: S-config['map']['latlngDelta'], + maxlat: N+config['map']['latlngDelta'], + minlon: W-config['map']['latlngDelta'], + maxlon: E+config['map']['latlngDelta'], + }; + $.ajax({ + type: "GET", + url: config['ajax']['stationURL'], + dataType: "xml", + data: request_data, + success: function (xml) { + $(xml).find('Network').each(function () { + var network = $(this).attr('code'); + if ( $.inArray(network, config['station']['networkBlacklist'])<0 ) { + $(this).find('Station').each(function () { + var station = $(this).attr('code'), + lat = $(this).find('Latitude:first').text(), + lng = $(this).find('Longitude:first').text(), + stationID = network+'_'+station, + stationText = network+'.'+station; + if ( !stationTable[stationID] ) { + // general station info (1st line) + var row = sprintf('%s%s%7.4f%7.4f' , network, station, Number(lat), Number(lng)); + // setting up network details (2nd line) + row += sprintf('%s', networkText[network] || ''); + row += ( $.inArray(station, bochumStation)+1 ) ? '
Operator: Ruhr-University Bochum' : '' ; + if ( network == 'RN' || network == 'X5' || $.inArray(station, bochumStation)+1 ) { + // setting up station details (3rd line) + row += ''; + row += stationDetails(station, network, lat, lng, stationID, stationText, $(this)); + row += ''; + // setting up download links (4th line) + var URL, fdsnxmlURL, fdsnxmlRespURL, sc3mlURL, sc3mlRespURL, dlsvURL; + URL = sprintf('%s?network=%s&station=%s', config['ajax']['stationURL'], network, station); + fdsnxmlURL = URL + '&level=station&format=xml'; + fdsnxmlRespURL = URL + '&level=response&format=xml'; + sc3mlURL = URL + '&level=station&format=sc3ml'; + sc3mlRespURL = URL + '&level=response&format=sc3ml'; + dlsvFile = sprintf('%s_%s.dlsv', network.toUpperCase(), station.toUpperCase()); + row += '' + + sprintf('Download details: FDSNxml or SC3ml
', stationID, fdsnxmlURL, stationID, sc3mlURL) + + sprintf('Response files: FDSNxml, SC3ml ', stationID, fdsnxmlRespURL, stationID, sc3mlRespURL) + + sprintf('or datalessSEED', config['ajax']['dlsvURL'] + '/' + dlsvFile, dlsvFile.toLowerCase()) + + ''; + } + else { + row += 'Kontaktieren Sie den '; + row += ( networkURL[network.toUpperCase()] ) ? 'Netzwerkkoordinator' : 'Netzwerkkoordinator'; + row += ' für weitere Details.'; + }; + $('#stationstable tbody').append(row); + addStationMarker(stationID, Number(lat), Number(lng), stationText.toUpperCase()); + }; + }); + }; + }); + }, + complete: function () { + initStationTable(); + var sort = [[0,0],[1,0]]; + $("#stationstable").trigger("update", [true]); + $("#stationstable").trigger("updateCache"); + $("#stationstable").trigger("sorton", [sort]); + $("#stationstable > tbody > tr:even").addClass("odd"); + $("#stationstable > tbody > tr:odd").addClass("even"); + stationLayer.bringToFront(); + }, + error: function( jqxhr, textStatus, error ) { + var err = textStatus + ", " + error; + console.log( "Request Failed: " + err ); + } + }); + // create stations csv download link + request_data['format'] = 'text'; + $('#stations-csv-link').attr('href', config['ajax']['stationURL']+'?'+$.param(request_data)); +}; + +/* format station Details */ +function stationDetails(station, network, lat, lng, stationId, stationText, stationObject) { + var output; + var elevation = stationObject.find('Elevation:first').text(); + var name = stationObject.find('Site > Name').text(); + output = '
'
+		+ name + '
' + + 'Position: ' + lat + '°N ' + lng + '°E, height: ' + elevation + ' m a.s.l.
'; + stationObject.find('Channel').each(function() { + var code = $(this).attr('code'); + var sensor = $(this).find('Sensor > Type').text().split(',')[0]; + var sampleRate = $(this).find('SampleRate').text(); + output += '
Chanel ' + code + ', Samplingrate ' + sampleRate + ' Hz, Sensor ' + sensor; + }); + output += '
'; + return output; +}; + +/* initStationTable */ +function initStationTable() { + // tablesorter for station list + $("#stationstable").tablesorter( + { + theme : 'blue', + cssChildRow: "tablesorter-childRow", // this is the default setting + widgets: ["uitheme", "zebra", "filter", "pager"], // initialize zebra and filter widgets, "scroller" + widgetOptions: { + // output default: '{page}/{totalPages}' + // possible variables: {page}, {totalPages}, {filteredPages}, {startRow}, {endRow}, {filteredRows} and {totalRows} + pager_output: '# {startRow} - {endRow} ({totalRows}) | page {page} ({totalPages})', + // apply disabled classname to the pager arrows when the rows at either extreme is visible + pager_updateArrows: true, + // starting page of the pager (zero based index) + pager_startPage: 0, + // Number of visible rows + pager_size: 35, + // Save pager page & size if the storage script is loaded (requires $.tablesorter.storage in jquery.tablesorter.widgets.js) + pager_savePages: true, + // if true, the table will remain the same height no matter how many records are displayed. The space is made up by an empty + // table row set to a height to compensate; default is false + pager_fixedHeight: false, + // remove rows from the table to speed up the sort of large tables. + // setting this to false, only hides the non-visible rows; needed if you plan to add/remove rows with the pager enabled. + pager_removeRows: false, + // css class names of pager arrows + pager_css: { + container : 'stations-tablesorter-pager', + errorRow : 'stations-tablesorter-errorRow', // error information row (don't include period at beginning) + disabled : 'disabled' // class added to arrows @ extremes (i.e. prev/first arrows "disabled" on first page) + }, + // jQuery pager selectors + pager_selectors: { + container : '.stationspager', // target the pager markup (wrapper) + first : '.stationsfirst', // go to first page arrow + prev : '.stationsprev', // previous page arrow + next : '.stationsnext', // next page arrow + last : '.stationslast', // go to last page arrow + goto : '.stationsgotoPage', // go to page selector - select dropdown that sets the current page + pageDisplay : '.stationspagedisplay', // location of where the "output" is displayed + pageSize : '.stationspagesize' // page size selector - select dropdown that sets the "size" option + }, + + filter_childRows : true, + filter_cssFilter : 'stations-tablesorter-filter', + filter_startsWith : false, + filter_ignoreCase : true, + scroller_height: $('div.map').height() - 250, + scroller_barWidth: 10, + scroller_jumpToHeader: false, + sortList: "[[0,0], [1,0]]", + resort: true, + showProcessing: true, + } + }); + // hide child rows + $('#stationstable > tbody > tr.tablesorter-childRow > td').hide(); + // update map after filtering + // $('#stationsstable').bind('filterEnd', function(){ + // toggleFilteredMarkers(); + // }); +}; + +/********************************************************************** + * document ready * + **********************************************************************/ +$(document).ready(function() { + loadStations(); + // show / hide station info + $('#stationstable').delegate('.toggle', 'click' , function(){ + // toggle visibility of selected row + $(this).closest('tr').nextUntil('tr.tablesorter-hasChildRow').find('td').toggle('slow'); + // mark currently selected row and remove class selected from all other rows + // hide other rows + $(this).closest('tr').nextUntil('tr.tablesorter-hasChildRow').find('td').addClass('selected-now'); + $(this).closest('tbody').find('td.selected').each(function(){ + if ( ! $(this).hasClass('selected-now') ) { + $(this).hide(); + $(this).removeClass('selected'); + }; + }); + $(this).closest('tr').nextUntil('tr.tablesorter-hasChildRow').find('td').each(function(){ + $(this).removeClass('selected-now'); + var selected = $(this).hasClass('selected'); + if ( selected ) { + $(this).removeClass('selected'); + //highlightFirstEvent(); + } else { + $(this).addClass('selected'); + //toggleHighlightStation($(this).attr('stationid')); + }; + }); + return false; + }); +}); From b074f0e695dc2273e5e98fb2210a4bf130308d7c Mon Sep 17 00:00:00 2001 From: "Kasper D. Fischer" Date: Fri, 10 Apr 2015 08:51:58 +0000 Subject: [PATCH 6/6] Updating plotting app. --- .gitignore | 1 + wsgi/plotFDSN.py | 149 +++++++++++++++++++++++++++++++++---------- wsgi/traceDayplot.py | 81 ++++++++++++----------- 3 files changed, 157 insertions(+), 74 deletions(-) diff --git a/.gitignore b/.gitignore index 4f44310..2b35943 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +wsgi/.idea www/dlsv www/event.xml www/events.xml diff --git a/wsgi/plotFDSN.py b/wsgi/plotFDSN.py index 8ffd530..1145823 100755 --- a/wsgi/plotFDSN.py +++ b/wsgi/plotFDSN.py @@ -1,8 +1,8 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- """ - Get waveformdata from FDSN web service and create a fancy plot - This programm runs as a script or as a WSGI application. + Get waveform data from FDSN web service and create a fancy plot + This programme runs as a script or as a WSGI application. Subversion information: $Id$ @@ -41,6 +41,7 @@ def utc2local(utctime, timezone='Europe/Berlin'): utctime = utc.localize(utctime.datetime) return local.normalize(utctime.astimezone(local)) + def fancy_plot(st, wsgi=False, img_format='png', color=True): """ creating fancy plot from ObsPy stream st @@ -131,6 +132,61 @@ def fancy_plot(st, wsgi=False, img_format='png', color=True): plt.show() +def trace_dayplot(st, deltat = None, + ftype='bandpass', fmin=1.0, fmax=7.0, + col=('b', 'r', 'g'), interval=20, outpattern='', + wsgi=False): + """ + + :type st: object + :type ftype: str + :type fmin: float + :type fmax: float + :type col: tuple + :type interval: float + :type outpattern: str + :type wsgi: bool + """ + + if wsgi: + try: + import cStringIO as StringIO + except ImportError: + import StringIO as StringIO + + # filter + if (ftype == 'bandpass') or (ftype == 'bandstop'): + st.filter(ftype, freqmin=fmin, freqmax=fmax) + elif (ftype == 'lowpass') or (ftype == 'highpass'): + st.filter(ftype, freq=fmin) + st.merge() + + stime = st[0].stats.starttime + if deltat: + etime = stime + deltat + else: + etime = st[0].stats.endtime + + # plot + for i in range(0, len(st)): + if wsgi: + buf = StringIO.StringIO() + st[i].plot(outfile=buf, format=outpattern, type='dayplot', color=col, interval=interval, + starttime=stime, endtime=etime) + return buf + else: + if outpattern != '': + imagefile = outpattern.format(st[i].stats.network, + st[i].stats.station, + st[i].stats.channel, + st[i].stats.starttime.year, + st[i].stats.starttime.julday) + st[i].plot(type='dayplot', color=col, interval=interval, + outfile=imagefile) + else: + st[i].plot(type='dayplot', color=col, interval=interval) + + def get_fdsn(bulk_request, output='VEL', base_url='https://ariadne.geophysik.ruhr-uni-bochum.de'): """ Fetches waveform data from FDSN web service and returns @@ -172,18 +228,18 @@ def get_fdsn(bulk_request, output='VEL', base_url='https://ariadne.geophysik.ruh return None -def main(backend=None, cla=None, wsgi=False): +def main(backend=None, args=None, wsgi=False): """ Main function to create a waveform plot. This functions calls get_fdsn and fancy_plot to create the figure. If wsgi == True it returns an ioString object containing the figure. :type backend: str - :type cla: dict + :type args: dict :type wsgi: bool :rtype : object :param backend: - :param cla: + :param args: :param wsgi: :return: ioString object with created image """ @@ -199,32 +255,48 @@ def main(backend=None, cla=None, wsgi=False): mpl.use(backend) from obspy import UTCDateTime - if cla: - deltat = cla['length'] - if cla['stime']: - otime = UTCDateTime(cla['stime']) + if args: + deltat = args['length'] + if args['stime']: + otime = UTCDateTime(args['stime']) else: otime = UTCDateTime() - 3600 - deltat - network = cla['station'].split('.')[0] - station = cla['station'].split('.')[1] + network = args['station'].split('.')[0] + station = args['station'].split('.')[1] + if args['type'] == 'dayplot': + channel = 'BHZ' + else: + if args['length'] < 3600: + channel = 'HH?' + else: + channel = 'BH?' else: otime = UTCDateTime('2014-11-15T11:35:25Z') deltat = 30 network = 'GR' station = 'BUG' + channel = 'HH?' - st = get_fdsn([(network, station, '*', 'HH?', otime, otime + deltat)], base_url=cla['server']) + st = get_fdsn([(network, station, '*', channel, otime, otime + deltat)], base_url=args['server']) if st is not None: stime = max(st[0].stats.starttime, otime) etime = min(st[0].stats.endtime, otime + deltat) st.trim(starttime=stime, endtime=etime) if wsgi: - # noinspection PyUnresolvedReferences - return fancy_plot(st, wsgi=wsgi, img_format=cla['format'], color=cla['color']) + if args['type'] == 'dayplot': + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + return trace_dayplot(st, outpattern=args['format'], wsgi=wsgi, deltat=deltat) + else: + return fancy_plot(st, wsgi=wsgi, img_format=args['format'], color=args['color']) else: - # noinspection PyUnresolvedReferences - fancy_plot(st, color=cla['color']) + if args['type'] == 'dayplot': + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + trace_dayplot(st, wsgi=wsgi) + else: + fancy_plot(st, color=args['color']) elif not wsgi: warnings.warn('No data available!') @@ -253,34 +325,39 @@ def application(environ, start_response): # fake command line arguments # setting needed default values - cla = {'server': 'http://localhost/'} + wsgi_args = {'server': 'http://localhost/'} - # fill cla with environment variables + # fill wsgi_args with environment variables form = FieldStorage(fp=environ['wsgi.input'], environ=environ) - cla['station'] = form.getfirst('station', 'GR.BUG').upper() - cla['stime'] = form.getfirst('start_time', []) - cla['length'] = form.getfirst('length', '30') - cla['length'] = abs(int(cla['length'])) - cla['format'] = form.getfirst('format', 'png').lower() - if cla['format'] != 'png' and cla['format'] != 'svg': - cla['format'] = 'png' - cla['color'] = form.getfirst('color', 'TRUE').upper() - if cla['color'] == 'FALSE': - cla['color'] = False + wsgi_args['station'] = form.getfirst('station', 'GR.BUG').upper() + wsgi_args['stime'] = form.getfirst('start_time', []) + wsgi_args['length'] = form.getfirst('length', '30') + wsgi_args['length'] = abs(int(wsgi_args['length'])) + wsgi_args['format'] = form.getfirst('format', 'png').lower() + if wsgi_args['format'] != 'png' and wsgi_args['format'] != 'svg': + wsgi_args['format'] = 'png' + wsgi_args['color'] = form.getfirst('color', 'TRUE').upper() + if wsgi_args['color'] == 'FALSE': + wsgi_args['color'] = False else: - cla['color'] = True + wsgi_args['color'] = True + wsgi_args['type'] = form.getfirst('type', '').lower() + if (wsgi_args['type'] != 'dayplot' and wsgi_args['type'] != 'normal') or wsgi_args['type'] == '': + if wsgi_args['length'] < 43200: + wsgi_args['type'] = 'normal' + else: + wsgi_args['type'] = 'dayplot' # process the request - buf = main(cla=cla, backend='Agg', wsgi=True) + buf = main(args=wsgi_args, backend='Agg', wsgi=True) if buf is not None: data = buf.getvalue() - # noinspection PyUnresolvedReferences buf.close() data_length = len(data) - if cla['format'] == u'svg': - cla['format'] = u'svg+xml' - start_response('200 OK', [('Content-Type', 'image/%s' % cla['format']), ('Content-Length', '%d' % data_length)]) + if wsgi_args['format'] == 'svg': + wsgi_args['format'] = 'svg+xml' + start_response('200 OK', [('Content-Type', 'image/%s' % wsgi_args['format']), ('Content-Length', '%d' % data_length)]) return [data] else: start_response('400 Bad Request', []) @@ -301,6 +378,8 @@ if __name__ == "__main__": parser.add_argument(u'-u', u'--url', action='store', dest='server', default=u'https://ariadne.geophysik.ruhr-uni-bochum.de', help=u'Base URL of the FDSN web service (https://ariadne.geophysik.ruhr-uni-bochum.de).') + parser.add_argument(u'-t', u'--type', action='store', dest='type', metavar='TYPE', + help=u'Type of plot: normal or dayplot.') parser.add_argument(u'-f', u'--file', action='store', dest='filename', metavar='FILENAME', help=u'Save plot to file FILENAME') parser.add_argument(u'-c', u'--color', action='store_true', help=u'Create color plot.') @@ -317,6 +396,6 @@ if __name__ == "__main__": cla = vars(parser.parse_args()) if os.getenv('DISPLAY'): - main(cla=cla) + main(args=cla) else: main('Agg', cla) \ No newline at end of file diff --git a/wsgi/traceDayplot.py b/wsgi/traceDayplot.py index dfb51ac..9056f8f 100755 --- a/wsgi/traceDayplot.py +++ b/wsgi/traceDayplot.py @@ -1,13 +1,13 @@ #! /usr/bin/env python # -*- coding: utf-8 -*- """ -Produce a dayplot +Produce a dayplot from seismogram recordings Subversion information: $Id$ -""" -""" -Copyright 2012 Seismological Observatory, Ruhr-University Bochum + +license: gpl3 +Copyright 2012-2015 Seismological Observatory, Ruhr-University Bochum http://www.gmg.ruhr-uni-bochum.de/geophysik/seisobs Contributors: Martina Rische @@ -28,28 +28,30 @@ You should have received a copy of the GNU General Public License along with this program. If not, see http://www.gnu.org/licenses/. """ -def traceDayplot(datafile, ftype='bandpass', fmin=1.0, fmax=7.0, - col=('b','r','g'), interval=20.0, outpattern=''): - ''' - ''' +def trace_dayplot(st, ftype='bandpass', fmin=1.0, fmax=7.0, + col=('b', 'r', 'g'), interval=20.0, outpattern=''): + """ + :type st: object + :type ftype: str + :type fmin: float + :type fmax: float + :type col: tuple + :type interval: float + :type outpattern: str + """ - from obspy.core import read - - st = read(datafile) - - #filter + # filter if (ftype == 'bandpass') or (ftype == 'bandstop'): st.filter(ftype, freqmin=fmin, freqmax=fmax) elif (ftype == 'lowpass') or (ftype == 'highpass'): st.filter(ftype, freq=fmin) - st.merge() - #plot - for i in range(0,len(st)): - if (outpattern != ''): + # plot + for i in range(0, len(st)): + if outpattern != '': imagefile = outpattern.format(st[i].stats.network, st[i].stats.station, st[i].stats.channel, @@ -66,37 +68,39 @@ if __name__ == "__main__": # parse arguments import argparse + from obspy.core import read + parser = argparse.ArgumentParser( description='Produce filtered 24h-plot (dayplot).', epilog='$Rev: 403 $ ($Date: 2012-04-13 12:16:22 +0200 (Fri, 13 Apr 2012) $, $Author: kasper $)') - parser.add_argument('-v', '-V','--version', action='version', - version='$Rev: 403 $ ($Date: 2012-04-13 12:16:22 +0200 (Fri, 13 Apr 2012) $, $Author: kasper $)') + parser.add_argument('-v', '-V', '--version', action='version', + version='$Rev: 403 $ ($Date: 2012-04-13 12:16:22 +0200 (Fri, 13 Apr 2012) $, $Author: kasper $)') parser.add_argument('file', action='store', metavar='FILE', nargs='+', - help='File(s) to use for the dayplot. One dayplot will be used for every file. \ + help='File(s) to use for the dayplot. One dayplot will be used for every file. \ Use wildcards to use multiple file for one plot') parser.add_argument('-f', '--filter', action='store', dest='ftype', - default='bandpass', - choices=['none', 'bandpass', 'bandstop', 'lowpass', 'highpass'], - help='Select filtertype to filter the data (default: bandpass)\ + default='bandpass', + choices=['none', 'bandpass', 'bandstop', 'lowpass', 'highpass'], + help='Select filtertype to filter the data (default: bandpass)\ Note: For low- and highpass only fmin is used.') parser.add_argument('--fmin', action='store', type=float, dest='fmin', - default=1.0, - help='Lower frequency of the filter in Hz (default: 1.0)') + default=1.0, + help='Lower frequency of the filter in Hz (default: 1.0)') parser.add_argument('--fmax', action='store', type=float, dest='fmax', - default=7.0, - help='Upper frequency of the filter in Hz (default: 7.0)') + default=7.0, + help='Upper frequency of the filter in Hz (default: 7.0)') parser.add_argument('-c', '--color', '--colour', action='store', dest='color', - default=('b', 'r', 'g'), - help='Color selection to use in the dayplot (default: brg)') + default=('b', 'r', 'g'), + help='Color selection to use in the dayplot (default: brg)') parser.add_argument('-i', '--interval', action='store', type=int, dest='interval', - default=20, - help='Interval length to show in each line of the dayplot in minutes (default: 20)') - parser.add_argument('-o', '--output', action='store', dest='outpattern', - default='', - help="Output filename pattern for the plot. (default: unset, show plot on screen only), \ + default=20, + help='Interval length to show in each line of the dayplot in minutes (default: 20)') + parser.add_argument('-o', '--output', action='store', dest='outpattern', + default='', + help="Output filename pattern for the plot. (default: unset, show plot on screen only), \ Use {0} to substitute network code, \ {1} to substitute station code, \ - {2} to subsitute station channel, \ + {2} to substitute station channel, \ {3} to substitute year, \ {4} to substitute doy number \ .format (supported formats: emf, eps, pdf, png, ps, raw, rgba, svg, svgz)") @@ -107,8 +111,7 @@ if __name__ == "__main__": if cla.fmax < 0: cla.fmax = -cla.fmax - # call traceDayplot(...) for each file + # call trace_dayplot(...) for each file for datafile in cla.file: - traceDayplot(datafile, cla.ftype, cla.fmin, cla.fmax, cla.color, - cla.interval, cla.outpattern) - + trace_dayplot(read(datafile), cla.ftype, cla.fmin, cla.fmax, cla.color, + cla.interval, cla.outpattern) \ No newline at end of file