├── 033-plot-multiple-seismometers-velocity-Alaska-2020-08-13.py ├── 038-map-epicentre.py ├── Alaska-2020-08-13-map.PNG ├── Basic-filter.PNG ├── Cornish Quake.PNG ├── Earthquake-phases.png ├── Filter-Summary.png ├── Jamaica quake.PNG ├── Kuril'sk quake.PNG ├── L01 hello.py ├── L02 input and assignment.PNG ├── L02 input and assignment.py ├── L03 sequence and selection.PNG ├── L03 sequence-selection.py ├── L04 iteration and lists.png ├── L04 iteration and lists.py ├── L05 cornish-stations.PNG ├── L05 packages and constants.PNG ├── L05 packages and constants.py ├── L05 plot multiple stations.PNG ├── L05 plot multiple stations.py ├── L06 obspy and datetime.PNG ├── L06 obspy and datetime.py ├── L07 obspy reading seismic data - output.png ├── L07 obspy reading seismic data.png ├── L07 obspy reading seismic data.py ├── L07 obspy reading seismograms.py ├── L07a multiple seismometers.PNG ├── L07a obspy read and write - output.PNG ├── L07a obspy read and write.PNG ├── L07a obspy traces and mseed.py ├── L08 Simplified Daily Plotter.PNG ├── L08 simplified daily plotter with filtering - RA736.py ├── L08 simplified daily plotter.py ├── L09 filtering.PNG ├── L09 filtering.py ├── L09a filtering.PNG ├── L09a filtering.py ├── L10 Basic section plot.PNG ├── L10 basic section plot.py ├── L10a section with data reader.PNG ├── L10a section with data reader.py ├── L11 Retrieving Data.PNG ├── L11 Retrieving Data.py ├── L12 automated section.PNG ├── L12 automated section.py ├── L12a automated section.py ├── L12b Searles-valley.html ├── L12b automated section with model lines.py ├── L13 user-defined function code.PNG ├── L13 user-defined subroutines.py ├── L14 find arrival.py ├── L15 - plot phases part A.PNG ├── L15 - plot phases part B.PNG ├── L15 - plot phases.py ├── L16 - Chile-section-model-lines-2020-06-03.py ├── LICENSE ├── Lesson 13 user-defined functions.PNG ├── M3.1 Puerto Rico-Section.png ├── M4 - 13km SW of Searles Valley CA-Section.png ├── Network.png ├── R0BEF.mseed ├── R38DC.mseed ├── README.md ├── REC8D.mseed ├── ShakeNetwork2020.csv ├── Simple-section-Puerto-Rico.PNG ├── Somerset quake.PNG ├── cornish-stations.html ├── get_earthquake_details.py └── get_earthquake_details_geonet.py /033-plot-multiple-seismometers-velocity-Alaska-2020-08-13.py: -------------------------------------------------------------------------------- 1 | # Multiple seismometers plotted for Alaska earthquake 2 | # Requires MAPFILE and LOGOS to be set, plus the following libraries 3 | from obspy.clients.fdsn import Client 4 | from obspy import UTCDateTime, Stream 5 | import numpy as np 6 | import matplotlib.pyplot as plt 7 | from obspy.taup import TauPyModel 8 | from matplotlib.cm import get_cmap 9 | from obspy.geodetics.base import locations2degrees 10 | from obspy.geodetics import gps2dist_azimuth 11 | import matplotlib.transforms as transforms 12 | from matplotlib.ticker import FormatStrFormatter 13 | from matplotlib.offsetbox import OffsetImage, AnnotationBbox 14 | 15 | def nospaces(string): 16 | out = "" 17 | for l in string.upper(): 18 | if l in "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789": 19 | out += l 20 | else: 21 | out += "_" 22 | return out 23 | 24 | model = TauPyModel(model='iasp91') 25 | #PHASES = sorted(["P", "pP", "PP"]) # list of phases for which to compute theoretical times 26 | #PHASES = sorted(["PKP", "PKIKP", "PKiKP", "pPKiKP", "SKiKP", "SKP"]) # list of phases for which to compute theoretical times 27 | #PHASES = sorted(["P", "pP", "pPP", "PP", "PPP", "S", "Pdiff", "PKP", "PKIKP", "PcP", "ScP", "ScS", "PKiKP", "SKiKP", "SKP", "SKS"]) # list of phases for which to compute theoretical times 28 | #PHASES = sorted(["P", "pP", "S", "PcP", "ScP"]) # list of phases for which to compute theoretical times 29 | #PHASES = sorted(["PP", "PKP", "PKIKP", "PcP", "ScP", "ScS", "PKiKP", "SKiKP", "SKP"]) # list of phases for which to compute theoretical times 30 | PHASES = sorted(["P", "pP", "PcP"]) 31 | #PHASES = sorted(["PKiKP", "PKIKP"]) 32 | #PHASES = sorted(["PKP", "PKIKP", "PKiKP"]) # list of phases for which to compute theoretical times 33 | #PHASES = sorted(["PKP", "PKIKP", "PKiKP"]) # list of phases for which to compute theoretical times 34 | #PHASES = sorted(["P", "pP", "PP", "PPP", "PKP", "PcP"]) 35 | #PHASES = sorted(["PKP", "PKIKP", "PKiKP", "pPKIKP", "pPKP", "pPKiKP"]) # list of phases for which to compute theoretical times 36 | DATA_PROVIDER = "RASPISHAKE" 37 | 38 | DETAILS = [53.42, -163.6973,10.43,'2020-08-13 06:39:58', 74.7744331862, 700.223, 'M5.7 158 km ESE of Akutan, Alaska','https://earthquake.usgs.gov/earthquakes/eventpage/us6000bduv/executive'] 39 | 40 | # Event details 41 | URL = DETAILS[7] 42 | EQNAME = DETAILS[6] 43 | EQLAT = DETAILS[0] 44 | EQLON = DETAILS[1] 45 | EQZ = DETAILS[2] 46 | EQTIME = DETAILS[3] 47 | FILE_STEM = 'Alaska-2020-08-13' 48 | MAGNITUDE = "M5.7" 49 | MAPFILE='Alaska-2020-08-13-map.png' 50 | LOGOS='logos.png' 51 | LABEL=nospaces(EQNAME) 52 | F1=0.9 53 | F2=3.1 54 | 55 | # set the data window 56 | STARTTIME=UTCDateTime(EQTIME) 57 | DURATION = 1800 58 | PSTART = DETAILS[5] - 10 59 | PEND = DETAILS[5] + 35 60 | SAMESCALE = False 61 | YAXIS = "ACC" 62 | YMAX = 5e-6 63 | 64 | # Home station 65 | NETWORK = 'AM' # AM = RaspberryShake network 66 | STATION = "RAD67" # Station code of local station to plot 67 | STA_LAT = 50.2385 # Latitude of local station 68 | STA_LON = -5.1822 # Longitude of local station 69 | CHANNEL = 'EHZ' # channel to grab data for (e.g. EHZ, SHZ, EHE, EHN) 70 | LOCATION = "St Day" 71 | DISTANCE=locations2degrees(EQLAT, EQLON, STA_LAT, STA_LON) # Station dist in degrees from epicentre 72 | STA_DIST, _, _ = gps2dist_azimuth(STA_LAT, STA_LON, EQLAT, EQLON) # Station dist in m from epicentre 73 | 74 | # list of stations 75 | SEISLIST=['RB30C','RB5E8','RD93E','R82BD','R7FA5','R0353','R9FEE', 'R303A', 'RAD67'] #, 'CCA1'] #, 'R480A'] 76 | LOCATIONS=['Falmouth','Penzance','Redruth','Richard Lander','Truro School','Penair','Truro High', 'Constantine', 'St Day'] #, 'Carnmenellis'] #, 'Camborne'] 77 | LATITUDES=[50.1486,50.1179833,50.2344,50.2596,50.2609,50.2673,50.2570,50.117,50.2385]#,50.1867] #,50.2072 ] 78 | LONGITUDES=[-5.0945,-5.5391226,-5.2384,-5.1027,-5.0434,-5.0299,-5.0566,-5.175,-5.1822]#,-5.2273] #,-5.3074] 79 | 80 | # fill the list of seismometers and sort it by distance from the epicentre 81 | seismometers = [] # empty list of seismometers 82 | for stationID in range(len(SEISLIST)): 83 | distance = locations2degrees(EQLAT, EQLON, LATITUDES[stationID], LONGITUDES[stationID]) 84 | seismometers.append([SEISLIST[stationID], round(LATITUDES[stationID],4), round(LONGITUDES[stationID],4), round(distance,4), LOCATIONS[stationID]]) 85 | seismometers.sort(key = lambda i: i[3]) # Contains 'Code', Latitude, Longitude, distance (degrees), location name: sort by distance from epicentre 86 | 87 | # Pretty paired colors. Reorder to have saturated colors first and remove 88 | # some colors at the end. This cmap is compatible with obspy taup 89 | CMAP = get_cmap('Paired', lut=12) 90 | COLORS = ['#%02x%02x%02x' % tuple(int(col * 255) for col in CMAP(i)[:3]) for i in range(12)] 91 | COLORS = COLORS[1:][::2][:-1] + COLORS[::2][:-1] 92 | 93 | # read in list of Raspberry Shakes by looping through the list from the second seismometer 94 | client=Client(DATA_PROVIDER) 95 | # create a new variable of type "obspy stream" containing no data 96 | waveform = Stream() 97 | loaded = [] 98 | # loop through the list of seismometers. The index for a list starts at 0 in Python 99 | for x in range (0,len(seismometers)): 100 | if seismometers[x][0] == 'CCA1': 101 | # read in BGS seismometer at Carnmenellis from IRIS. Not all BGS seismometers transmit data to IRIS. 102 | client=Client("IRIS") 103 | st=client.get_waveforms('GB','CCA1','--','HHZ',STARTTIME,STARTTIME+DURATION,attach_response=True) 104 | st.merge(method=0, fill_value='latest') 105 | st.detrend(type='demean') 106 | pre_filt = [0.01, 0.1, 12.0, 15] 107 | st.remove_response(pre_filt=pre_filt,output=YAXIS,water_level=40,taper=True)#, plot=True) 108 | loaded.append(seismometers[x]) # Add the details of loaded seismometers to the loaded list 109 | waveform += st 110 | else: 111 | # read in list of Raspberry Shakes by looping through the list from the second seismometer 112 | client=Client(base_url='https://fdsnws.raspberryshakedata.com/') 113 | # remember, Python lists are indexed from zero, so this looks from the second item in the list 114 | try: 115 | st=client.get_waveforms('AM',seismometers[x][0],'00','EHZ',STARTTIME,STARTTIME+DURATION,attach_response=True) 116 | st.merge(method=0, fill_value='latest') 117 | st.detrend(type='demean') 118 | pre_filt = [0.01, 0.1, 12.0, 15] 119 | st.remove_response(pre_filt=pre_filt,output=YAXIS,water_level=40,taper=True)#, plot=True) 120 | loaded.append(seismometers[x]) # Add the details of loaded seismometers to the loaded list 121 | waveform += st 122 | except: 123 | print(seismometers[x][0], "from", seismometers[x][4], "not returned from server, skipping this trace.") 124 | 125 | # Filter the data 126 | waveform.filter("bandpass", freqmin=F1, freqmax = F2, corners=4) 127 | FILTERLABEL = '"bandpass", freqmin=' + str(F1) + ', freqmax=' + str(F2) + ', corners=4' 128 | 129 | # Find the maximum y value to scale the axes 130 | ylim = 0 131 | for trace in waveform: # find maximum value from traces for plotting when SAMESCALE is True 132 | print(trace.max()) 133 | if abs(trace.max()) * 1.1 > ylim: 134 | ylim = abs(trace.max()) * 1.1 135 | ylim = YMAX 136 | 137 | # Plot figure with subplots of different sizes 138 | fig = plt.figure(1, figsize=(19, 11)) 139 | # set up the page margins, height and spacing of the plots for traces 140 | margin = 0.06 141 | twidth = 0.60 142 | spacing = 0.00 143 | theight = ((1.0 - 2 * margin) / waveform.count()) - spacing 144 | axes = [] # A list for the trace axis graphs 145 | # set up an axis covering the whole plot area for annotations and turn off the axes and ticks 146 | annotations = fig.add_axes([0, 0, 1, 1]) 147 | annotations.spines['top'].set_visible(False) 148 | annotations.spines['right'].set_visible(False) 149 | annotations.spines['bottom'].set_visible(False) 150 | annotations.spines['left'].set_visible(False) 151 | annotations.get_xaxis().set_ticks([]) 152 | annotations.get_xaxis().set_ticks([]) 153 | # Add descriptive labels to the plot 154 | networksummary = "from the Raspberry Shake Network." 155 | titletxt = EQNAME + " " + str(EQTIME[0:10]) + "\n" + networksummary + "\nSee: " + URL + " for more info." 156 | annotations.text(0.36, 0.99, titletxt, transform=annotations.transAxes, horizontalalignment='center', verticalalignment='top') 157 | #annotations.text(0.01, 0.50, 'Amplitude [m]', rotation=90, horizontalalignment='center', verticalalignment='center') 158 | if YAXIS == "DISP": 159 | annotations.text(0.01, 0.50, 'Displacement [m]', rotation=90, horizontalalignment='center', verticalalignment='center') 160 | elif YAXIS == "VEL": 161 | annotations.text(0.01, 0.50, 'Velocity [m/s]', rotation=90, horizontalalignment='center', verticalalignment='center') 162 | elif YAXIS == "ACC": 163 | annotations.text(0.01, 0.50, 'Acceleration [m/s²]', rotation=90, horizontalalignment='center', verticalalignment='center') 164 | else: 165 | annotations.text(0.01, 0.50, 'Counts', rotation=90, horizontalalignment='center', verticalalignment='center') 166 | annotations.text(0.36, 0.01, 'Time since earthquake [s]', horizontalalignment='center', verticalalignment='bottom') 167 | annotations.text(0.01, 0.01, "Channel: " + CHANNEL + ", filter: " + FILTERLABEL, horizontalalignment='left', verticalalignment='bottom') 168 | annotations.text(0.67, 0.51, "Epicentre: Lat: " + str(round(EQLAT, 2)) + "° Lon: " + str(round(EQLON,2)) + "° Depth: " + str(round(EQZ, 1)) + "km Time: " + EQTIME[0:19] + "UTC", horizontalalignment='left', verticalalignment='center') 169 | annotations.text(0.67, 0.16, "Station: " + STATION + "-" + LOCATION + " Lat: " + str(round(STA_LAT, 2)) + "° Lon: " + str(round(STA_LON,2)) + "° Dist: " + str(STA_DIST//1000) + "km", horizontalalignment='left', verticalalignment='center') 170 | annotations.text(0.67, 0.56, "Ray paths and arrivals\ncalculated from iasp91 model.", horizontalalignment='left', verticalalignment='center') 171 | # Add the logos and map in the right-hand panel 172 | # logo 173 | arr_logos = plt.imread(LOGOS) 174 | imagebox = OffsetImage(arr_logos, zoom=0.5) 175 | ab = AnnotationBbox(imagebox, (0.830, 0.08), frameon=False) 176 | annotations.add_artist(ab) 177 | # map - generated from folium 178 | arr_map = plt.imread(MAPFILE) 179 | mapbox = OffsetImage(arr_map, zoom=0.24) 180 | mb = AnnotationBbox(mapbox, (0.83, 0.335), frameon=False) 181 | annotations.add_artist(mb) 182 | # set up stream plots for each seismometer 183 | for i in range(waveform.count()): 184 | if i == 0: # bottom axis first, including tick labels 185 | axes.append(fig.add_axes([margin, margin + ((theight + spacing) * i), twidth, theight])) 186 | else: # tick labels switched off for subsequent axes 187 | axes.append(fig.add_axes([margin, margin + ((theight + spacing) * i), twidth, theight], sharex=axes[0])) 188 | plt.setp(axes[i].get_xticklabels(), visible=False) 189 | # Create a list of elapsed times for the stream file (what does this do in the case of data gaps? 190 | time = np.arange(0, waveform[i].count()/100, 0.01) 191 | # set up plot parameters 192 | axes[i].set_xlim([PSTART,PEND]) 193 | if not SAMESCALE: 194 | ylim = abs(waveform[i].max()) * 1.1 195 | axes[i].set_ylim([-1*ylim,ylim]) 196 | axes[i].yaxis.set_major_formatter(FormatStrFormatter('%.1e')) 197 | axes[i].tick_params(axis="both", direction="in", which="both", right=True, top=True) 198 | # plot the data 199 | axes[i].plot(time, waveform[i], linewidth=0.75) 200 | textstr = loaded[i][0] + " " + str(round(loaded[i][3], 2)) + "° " + loaded[i][4] 201 | plt.text(0.5, 0.96, textstr, transform=axes[i].transAxes, horizontalalignment='center', verticalalignment='top') 202 | # Add the arrival times and vertical lines 203 | plotted_arrivals = [] 204 | for j, phase in enumerate(PHASES): 205 | color = COLORS[PHASES.index(phase) % len(COLORS)] 206 | arrivals = model.get_travel_times(source_depth_in_km=EQZ, distance_in_degree=loaded[i][3], phase_list=[phase]) 207 | printed = False 208 | if arrivals: 209 | for k in range(len(arrivals)): 210 | instring = str(arrivals[k]) 211 | phaseline = instring.split(" ") 212 | phasetime = float(phaseline[4]) 213 | if phaseline[0] == phase and printed == False and (PSTART < phasetime < PEND): 214 | plotted_arrivals.append(tuple([round(float(phaseline[4]), 2), phaseline[0], color])) 215 | printed = True 216 | if plotted_arrivals: 217 | plotted_arrivals.sort(key=lambda tup: tup[0]) #sorts the arrivals to be plotted by arrival time 218 | trans = transforms.blended_transform_factory(axes[i].transData, axes[i].transAxes) 219 | phase_ypos = 0 220 | 221 | for phase_time, phase_name, color_plot in plotted_arrivals: 222 | axes[i].vlines(phase_time, ymin=0, ymax=1, alpha=.50, color=color_plot, ls='--', zorder=1, transform=trans) 223 | plt.text(phase_time, 0, phase_name+" ", alpha=.50, c=color_plot, fontsize=11, horizontalalignment='right', verticalalignment='bottom', zorder=1, transform=trans) 224 | # Add the inset picture of the globe at x, y, width, height, as a fraction of the parent plot 225 | ax1 = fig.add_axes([0.64, 0.545, 0.38, 0.38], polar=True) 226 | # Plot all pre-determined phases 227 | for phase in PHASES: 228 | arrivals = model.get_ray_paths(EQZ, DISTANCE, phase_list=[phase]) 229 | try: 230 | ax1 = arrivals.plot_rays(plot_type='spherical', legend=True, label_arrivals=False, plot_all=True, phase_list = PHASES, show=False, ax=ax1) 231 | except: 232 | print(phase + " not present.") 233 | 234 | # Annotate regions of the globe 235 | ax1.text(0, 0, 'Solid\ninner\ncore', horizontalalignment='center', alpha=0.5, verticalalignment='center', bbox=dict(facecolor='white', edgecolor='none', alpha=0.5)) 236 | ocr = (model.model.radius_of_planet - (model.model.s_mod.v_mod.iocb_depth + model.model.s_mod.v_mod.cmb_depth) / 2) 237 | ax1.text(np.deg2rad(180), ocr, 'Fluid outer core', alpha=0.5, horizontalalignment='center', bbox=dict(facecolor='white', edgecolor='none', alpha=0.5)) 238 | mr = model.model.radius_of_planet - model.model.s_mod.v_mod.cmb_depth / 2 239 | ax1.text(np.deg2rad(180), mr, 'Solid mantle', alpha=0.5, horizontalalignment='center', bbox=dict(facecolor='white', edgecolor='none', alpha=0.5)) 240 | rad = model.model.radius_of_planet*1.15 241 | ax1.text(np.deg2rad(DISTANCE), rad, " " + STATION, horizontalalignment='left', bbox=dict(facecolor='white', edgecolor='none', alpha=0)) 242 | ax1.text(np.deg2rad(0), rad, EQNAME + "\nEpicentral distance: " + str(round(DISTANCE, 1)) + "°" + "\nEQ Depth: " + str(EQZ) + "km", 243 | horizontalalignment='left', bbox=dict(facecolor='white', edgecolor='none', alpha=0)) 244 | 245 | # Save the plot to file, then print to screen 246 | plt.savefig(nospaces(LABEL)+'-Summary.png') 247 | #plt.tight_layout() 248 | plt.show() 249 | 250 | print(EQNAME + " at " + EQTIME[0:19] + "UTC recorded on the Cornwall Schools @uniteddownsgeo @raspishake network in Cornwall, UK." 251 | + " See: " + URL + ". Uses @obspy, @matplotlib & folium libraries.") -------------------------------------------------------------------------------- /038-map-epicentre.py: -------------------------------------------------------------------------------- 1 | import folium 2 | from obspy.geodetics.base import locations2degrees 3 | import math 4 | import numpy as np 5 | from numpy import arctan2, sqrt 6 | import numexpr as ne 7 | from sympy import * 8 | from numpy import cross, eye, dot 9 | from scipy.linalg import expm, norm 10 | 11 | PI = 3.14159 12 | R = 6367.5 13 | 14 | def M(axis, theta): 15 | return expm(cross(eye(3), axis/norm(axis)*theta)) 16 | 17 | def rotation_matrix(axis, theta): 18 | """ 19 | Return the rotation matrix associated with counterclockwise rotation about 20 | the given axis by theta radians. 21 | """ 22 | axis = np.asarray(axis) 23 | axis = axis / math.sqrt(np.dot(axis, axis)) 24 | a = math.cos(theta / 2.0) 25 | b, c, d = -axis * math.sin(theta / 2.0) 26 | aa, bb, cc, dd = a * a, b * b, c * c, d * d 27 | bc, ad, ac, ab, bd, cd = b * c, a * d, a * c, a * b, b * d, c * d 28 | return np.array([[aa + bb - cc - dd, 2 * (bc + ad), 2 * (bd - ac)], 29 | [2 * (bc - ad), aa + cc - bb - dd, 2 * (cd + ab)], 30 | [2 * (bd + ac), 2 * (cd - ab), aa + dd - bb - cc]]) 31 | 32 | def appendSpherical_np(xyz): 33 | ptsnew = np.hstack((xyz, np.zeros(xyz.shape))) 34 | xy = xyz[:,0]**2 + xyz[:,1]**2 35 | ptsnew[:,3] = np.sqrt(xy + xyz[:,2]**2) 36 | ptsnew[:,4] = np.arctan2(np.sqrt(xy), xyz[:,2]) # for elevation angle defined from Z-axis down 37 | #ptsnew[:,4] = np.arctan2(xyz[:,2], np.sqrt(xy)) # for elevation angle defined from XY-plane up 38 | ptsnew[:,5] = np.arctan2(xyz[:,1], xyz[:,0]) 39 | return ptsnew 40 | 41 | def asCartesian(rthetaphi): 42 | #takes list rthetaphi (single coord) 43 | r = rthetaphi[0] 44 | theta = rthetaphi[1]*PI/180 # to radian 45 | phi = rthetaphi[2]*PI/180 46 | x = r * math.sin( theta ) * math.cos( phi ) 47 | y = r * math.sin( theta ) * math.sin( phi ) 48 | z = r * math.cos( theta ) 49 | return [x,y,z] 50 | 51 | def asSpherical(xyz): 52 | #takes list xyz (single coord) 53 | x = xyz[0] 54 | y = xyz[1] 55 | z = xyz[2] 56 | r = sqrt(x*x + y*y + z*z) 57 | theta = math.acos(z/r)*180/ PI #to degrees 58 | phi = math.atan2(y,x)*180/ PI 59 | return [r,theta,phi] 60 | 61 | def cart2sph(x,y,z, ceval=ne.evaluate): 62 | """ x, y, z : ndarray coordinates 63 | ceval: backend to use: 64 | - eval : pure Numpy 65 | - numexpr.evaluate: Numexpr """ 66 | azimuth = ceval('arctan2(y,x)') 67 | xy2 = ceval('x**2 + y**2') 68 | elevation = ceval('arctan2(z, sqrt(xy2))') 69 | r = eval('sqrt(xy2 + z**2)') 70 | return azimuth, elevation, r 71 | 72 | DETAILS = [53.42, -163.6973,10.43,'2020-08-13 06:39:58', 74.7744331862, 700.223, 'M5.7 158 km ESE of Akutan, Alaska','https://earthquake.usgs.gov/earthquakes/eventpage/us6000bduv/executive'] 73 | 74 | # Event details 75 | URL = DETAILS[7] 76 | EQNAME = DETAILS[6] 77 | EQLAT = DETAILS[0] 78 | EQLON = DETAILS[1] 79 | EQZ = DETAILS[2] 80 | EQTIME = DETAILS[3] 81 | FILE_STEM = 'Alaska-2020-08-13' 82 | 83 | MAPFILE=FILE_STEM + '-map.png' 84 | LOGOS='logos.png' 85 | 86 | # Things to change once for your station 87 | # Home station 88 | NETWORK = 'AM' # AM = RaspberryShake network 89 | STATION = "RAD67" # Station code of local station to plot 90 | STA_LAT = 50.2385 # Latitude of local station 91 | STA_LON = -5.1822 # Longitude of local station 92 | CHANNEL = 'EHZ' # channel to grab data for (e.g. EHZ, SHZ, EHE, EHN) 93 | LOCATION = "St Day" 94 | 95 | # Plot the epicentre and seismometer on the map 96 | map = folium.Map(location=[EQLAT, EQLON],zoom_start=2,tiles='Stamen Terrain') 97 | # The following arguments will centre the map on Caustic. 98 | map = folium.Map(location=[47.8288, 1.9324],zoom_start=6,tiles='Stamen Terrain') 99 | 100 | # theta is 90 - latitude 101 | # phi is longitude but phi 180-360 becomes negative longitude 102 | # r = radius of earth (6,378 EQ + 6,357 POLE) / 2 = 6367.5 103 | dend = [] 104 | dstart = [] 105 | 106 | for phi in range(0, 360, 1): 107 | theta = 140 108 | dend.append(asCartesian([R, theta, phi])) 109 | omega = 104 110 | dstart.append(asCartesian([R, omega, phi])) 111 | 112 | dendt = [] 113 | axis = [math.cos((90+EQLON)/180*PI), math.sin((90+EQLON)/180*PI), 0] 114 | theta = (90-EQLAT)/180*PI 115 | for i in range(len(dend)): 116 | dend[i]=np.dot(rotation_matrix(axis, theta), dend[i]) 117 | send = asSpherical(dend[i]) 118 | lat = 90 - (send[1]) 119 | long = send[2] 120 | dendt.append([lat, long]) 121 | 122 | dstartt = [] 123 | axis = [math.cos((90+EQLON)/180*PI), math.sin((90+EQLON)/180*PI), 0] 124 | theta = (90-EQLAT)/180*PI 125 | for i in range(len(dstart)): 126 | dstart[i]=np.dot(rotation_matrix(axis, theta), dstart[i]) 127 | sstart = asSpherical(dstart[i]) 128 | lat = 90 - (sstart[1]) 129 | long = sstart[2] 130 | dstartt.append([lat, long]) 131 | 132 | linepoints=[] 133 | for lon in range(-180, 180, 10): 134 | # Northern hemisphere 135 | DISTANCE=locations2degrees(EQLAT, EQLON, 0, lon) # Station dist in degrees from epicentre 136 | bestfit = abs(DISTANCE-140) 137 | bestloc = [0, lon] 138 | plotit = False 139 | for lat in range(0, 91, 1): 140 | DISTANCE=locations2degrees(EQLAT, EQLON, lat, lon) # Station dist in degrees from epicentre 141 | if DISTANCE > 139.6 and DISTANCE < 140.4: 142 | plotit = True 143 | for latf in range((lat-1)*300, (lat+2)*300, 1): 144 | DISTANCE=locations2degrees(EQLAT, EQLON, latf/300, lon) 145 | fit = abs(DISTANCE-140) 146 | if fit < bestfit: 147 | bestfit = fit 148 | bestloc = [latf/300, lon] 149 | if plotit == True: 150 | linepoints.append(bestloc) 151 | # southern hemisphere 152 | DISTANCE=locations2degrees(EQLAT, EQLON, 0, lon) # Station dist in degrees from epicentre 153 | bestfit = abs(DISTANCE-140) 154 | bestloc = [0, lon] 155 | plotit = False 156 | for lat in range(-90, 1, 1): 157 | DISTANCE=locations2degrees(EQLAT, EQLON, lat, lon) # Station dist in degrees from epicentre 158 | if DISTANCE > 139.6 and DISTANCE < 140.4: 159 | plotit = True 160 | for latf in range((lat-1)*300, (lat+2)*300, 1): 161 | DISTANCE=locations2degrees(EQLAT, EQLON, latf/300, lon) 162 | fit = abs(DISTANCE-140) 163 | if fit < bestfit: 164 | bestfit = fit 165 | bestloc = [latf/300, lon] 166 | if plotit == True: 167 | linepoints.append(bestloc) 168 | 169 | #folium.vector_layers.PolyLine(linepoints, color="green", weight=2, opacity=0.8, smooth_factor=0.5).add_to(map) 170 | line = [] 171 | counter = 0 172 | for i, dp in enumerate(dendt): 173 | if i > 0: 174 | if abs(dendt[i-1][0] - dendt[i][0]) > 10 or abs(dendt[i-1][1] - dendt[i][1]) > 10: 175 | folium.vector_layers.PolyLine(line, color="red", weight=3, opacity=1.0, smooth_factor=0.5).add_to(map) 176 | line = [] 177 | if dp[0] < -90: 178 | dp[0] += 180 179 | if dp[0] > 90: 180 | dp[0] -= 180 181 | if dp[1] < -180: 182 | dp[1] += 360 183 | if dp[1] > 180: 184 | dp[1] -= 360 185 | line.append(dp) 186 | folium.vector_layers.PolyLine(line, color="red", weight=3, opacity=1.0, smooth_factor=0.5).add_to(map) 187 | 188 | line = [] 189 | counter = 0 190 | for i, dp in enumerate(dstartt): 191 | if i > 0: 192 | if abs(dstartt[i-1][0] - dstartt[i][0]) > 10 or abs(dstartt[i-1][1] - dstartt[i][1]) > 10: 193 | folium.vector_layers.PolyLine(line, color="red", weight=3, opacity=1.0, smooth_factor=0.5).add_to(map) 194 | line = [] 195 | if dp[0] < -90: 196 | dp[0] += 180 197 | if dp[0] > 90: 198 | dp[0] -= 180 199 | if dp[1] < -180: 200 | dp[1] += 360 201 | if dp[1] > 180: 202 | dp[1] -= 360 203 | line.append(dp) 204 | folium.vector_layers.PolyLine(line, color="red", weight=3, opacity=1.0, smooth_factor=0.5).add_to(map) 205 | 206 | ''' 207 | STATIONS = [ 208 | [39.8739, 25.2737, 139.0268, 8.08521143225e-08, 'R9AC0', 15464017.12610259, 63.8190132031462, 314.36286748274676], 209 | [48.2162, 16.3481, 139.4912, 1.24012198719e-07, 'R21E0', 15512026.232576072, 45.386903080507025, 330.48708270854775], 210 | [51.3423, 9.0719, 140.0754, 2.86703703369e-08, 'RC4FB', 15575527.635725247, 33.23599842700995, 339.1674835760409], 211 | [40.7297, 22.9923, 140.1738, 4.13058393523e-08, 'RC574', 15591170.019331088, 60.498471853957795, 316.790179065272], 212 | [51.4234, 6.8339, 140.7428, 6.0645088915e-08, 'RDE9F', 15649547.206057, 30.011949426045884, 341.0963191101199], 213 | [46.973, 15.3948, 140.8208, 2.81219932953e-08, 'R830F', 15660220.298032654, 45.19373918516495, 329.8180786207821], 214 | [37.973, 23.7496, 140.9189, 5.08230866469e-08, 'R7AD4', 15675231.197069323, 64.27509328069624, 312.51392119632123], 215 | [46.3333, 15.9599, 140.9848, 1.8184929551e-08, 'RCF63', 15678722.622802122, 46.49579256213755, 328.66368400640465], 216 | [50.955, 7.1663, 141.0401, 2.77531725314e-08, 'RB99F', 15682738.027138494, 30.777767617374888, 340.43823999631377], 217 | [50.2252, 8.5907, 141.1751, 9.32369137867e-08, 'RA52C', 15698050.774803933, 33.293873827404056, 338.6062523520899], 218 | [49.964, 8.8242, 141.3091, 7.73763462492e-08, 'R74D9', 15713030.505896715, 33.804801530906445, 338.1777091962111], 219 | [48.1441, 12.287, 141.3893, 3.92489175662e-08, 'R06C4', 15722782.58651194, 40.02431899045048, 333.5340139097541], 220 | [50.1081, 7.8955, 141.5161, 2.26880020602e-08, 'R0333', 15735927.126348088, 32.37494113266299, 339.1029048180517], 221 | [39.5405, 21.7645, 141.5738, 8.58100145689e-08, 'RAC91', 15747320.91143851, 60.405997753038875, 315.8870849930575], 222 | [50.9459, 5.3051, 141.6273, 1.07104225243e-07, 'R78B8', 15747881.05902008, 28.074279350863975, 342.06159555428354], 223 | [48.1802, 11.566, 141.6691, 8.23380529745e-08, 'R4A43', 15753809.657778796, 38.99492989160851, 334.16664813071804], 224 | [47.4775, 12.5303, 141.7881, 4.08355153448e-08, 'R6B73', 15767348.634716062, 40.900511430314495, 332.6404427704048], 225 | [49.3243, 8.6952, 141.8854, 4.72325183687e-08, 'R7266', 15777262.278000155, 34.065110096124954, 337.7168934997417], 226 | [52.4595, -1.9369, 141.9411, 8.07108824554e-07, 'R8118', 15781960.760614118, 16.451417886967825, 349.67343004257185], 227 | [39.6577, 20.8424, 142.1317, 4.0037490716e-08, 'R1388', 15809244.968678603, 59.23388036395095, 316.63182100347433], 228 | [49.8739, 6.2069, 142.2818, 9.75732727564e-08, 'RCEAB', 15820966.248155689, 30.06173508736019, 340.40819745854367], 229 | [51.3243, 1.413, 142.326, 9.34488989481e-07, 'R8C09', 15825170.411010163, 22.042485089244035, 345.9001941804129], 230 | [51.6486, -0.2178, 142.3846, 1.19991870698e-07, 'R0AEF', 15831509.249612723, 19.402947017522838, 347.6360727565328], 231 | [51.6036, -0.3337, 142.451, 1.32745781091e-07, 'R0887', 15838889.695852866, 19.24399094062836, 347.7227018187861], 232 | [51.4595, -0.0145, 142.52, 9.20167598181e-08, 'R2E51', 15846599.297615675, 19.796556176448803, 347.3380303820712], 233 | [51.4234, -0.0722, 142.5661, 4.41461436921e-07, 'R63BA', 15851735.500129828, 19.72348510103283, 347.3735289040536], 234 | [51.3694, -0.0722, 142.6169, 7.52574441491e-08, 'RCC45', 15857390.435926516, 19.746979319482367, 347.34371049929035], 235 | [45.9369, 13.0459, 142.6926, 5.91814131326e-08, 'RC01C', 15868464.006681547, 42.92982544353731, 330.5364368375782], 236 | [48.6577, 7.8152, 142.7607, 3.93093117343e-07, 'R4B4A', 15874660.529874427, 33.25702570840153, 337.9001004221063], 237 | [48.6396, 7.7447, 142.8014, 5.59079951175e-08, 'R60B1', 15879189.883457486, 33.1661588739929, 337.94807612388104], 238 | [48.5135, 7.6979, 142.9237, 3.20104435716e-07, 'RA7C1', 15892816.31329672, 33.18718121640123, 337.877203766338], 239 | [47.4685, 8.9568, 143.3026, 3.76069604302e-08, 'R7B2E', 15935347.134961065, 35.819513671601946, 335.7417032994448], 240 | [46.7748, 10.1561, 143.3614, 9.0894392508e-08, 'R3EDD', 15942228.94002849, 38.121506197903884, 333.9564403829112], 241 | [47.3423, 8.5896, 143.5503, 3.38895483384e-08, 'R03EA', 15962889.109817965, 35.37698095181571, 335.95751799710655], 242 | [47.8108, 7.3379, 143.6426, 7.88474960924e-08, 'R79D9', 15972870.846218215, 33.16126376874516, 337.57289695680373], 243 | [47.6847, 7.4818, 143.6943, 6.89762168935e-08, 'R3774', 15978660.330086328, 33.469777065949536, 337.3204837508338], 244 | [47.6937, 7.3481, 143.7365, 5.32719718545e-08, 'RF5BC', 15983344.958010405, 33.262782150363186, 337.45550813108116], 245 | [47.6486, 7.2625, 143.8059, 7.43248010302e-08, 'R9DBD', 15991061.48833064, 33.16758638776435, 337.4953321114253], 246 | [47.6126, 7.2303, 143.8479, 3.53749610299e-07, 'R9D74', 15995735.964670816, 33.14577901869697, 337.4928599005947], 247 | [43.5946, 13.5099, 144.1286, 2.38406744292e-07, 'RF7E5', 16028986.757775346, 45.75965395991925, 327.40367424975835], 248 | [46.6847, 7.683, 144.444, 1.84838254678e-07, 'R0C73', 16062300.992469292, 34.53495626041903, 336.1799603274634], 249 | [50.2523, -5.0309, 144.5607, 2.51145293762e-07, 'R7FA5', 16073291.109304361, 12.281288488578284, 351.87865795451887], 250 | [50.2072, -5.3075, 144.642, 6.40721692254e-08, 'R480A', 16082321.111570034, 11.84150678184185, 352.15963946080495], 251 | [50.1081, -5.1702, 144.7205, 2.64242225928e-07, 'R303A', 16091069.94561483, 12.095280303645783, 351.97633823192405], 252 | [46.3063, 7.5646, 144.8011, 2.59958774876e-07, 'R2AEB', 16102087.825666783, 34.65440524888269, 335.9265473703645], 253 | [46.5225, 6.5733, 145.0048, 2.74433362573e-08, 'RD14A', 16124529.877269402, 32.966298390593025, 337.1196870664719], 254 | [45.5586, 8.158, 145.168, 4.60441850113e-07, 'R976C', 16143155.27180041, 36.168169327888016, 334.5907317093059], 255 | [47.9009, 1.8814, 145.3455, 8.36002571101e-08, 'S7A70', 16161490.230750268, 24.604960440739557, 343.1485930842906], 256 | [47.8288, 1.9324, 145.3965, 6.26512975665e-07, 'R51FD', 16167182.75630816, 24.730229658813684, 343.0415486458332], 257 | [44.045, 10.0522, 145.5144, 1.19852495572e-07, 'RF212', 16182414.243399696, 40.360204199417744, 331.09795579490924], 258 | [44.7838, 7.9972, 145.8573, 9.17646713394e-08, 'R4FB1', 16219982.196730752, 36.58369347328169, 333.94446752315616], 259 | [44.7838, 6.8674, 146.3284, 8.8959736833e-08, 'R7CE4', 16272168.531511128, 34.82697852931433, 335.10896602059364], 260 | [45.1802, 5.726, 146.4516, 1.97668119373e-08, 'R7A15', 16285573.409416324, 32.69184757052468, 336.71352442451627], 261 | [45.7477, 3.0599, 146.9137, 1.78557865177e-07, 'R86F8', 16336383.342223246, 27.92976715054852, 340.15944210589885], 262 | [44.2072, 5.0778, 147.519, 1.37479772081e-07, 'RC7DF', 16404354.204405338, 32.43963674649258, 336.46912822273646], 263 | [15.5045, 20.4563, 148.3249, 2.34908562585e-08, 'RA77C', 16508080.183242608, 94.08223112632622, 274.724478184751], 264 | [44.8468, 0.2542, 148.5904, 2.28835896918e-07, 'RB1D6', 16522453.564030897, 23.765449904187925, 342.7418089265241], 265 | [43.3063, -0.3591, 150.1771, 7.02172507817e-08, 'R7F64', 16698921.543941455, 23.696663499459525, 342.3232616513428], 266 | [42.8739, 0.0123, 150.4587, 8.19871805497e-08, 'R0CA8', 16730349.43788516, 24.684836419617433, 341.47249041736734], 267 | [43.2613, -2.9321, 150.9065, 1.77826384688e-07, 'R12EB', 16779484.17465724, 18.912428472926837, 345.8141881933233], 268 | [43.3694, -4.115, 151.0687, 6.19362193259e-08, 'R9081', 16797301.650472563, 16.586000795386173, 347.55818235728253], 269 | [43.3604, -5.9107, 151.4153, 7.75969544797e-08, 'R0D06', 16835553.593549155, 13.092527692671533, 350.1542839395658], 270 | [42.3243, -8.652, 152.8045, 7.3528983592e-08, 'R29F5', 16989607.780029785, 7.90396847118016, 353.9409527560108], 271 | [40.1982, -8.4452, 154.884, 4.44281770378e-08, 'RC085', 17220640.77622039, 8.969649704782185, 352.8988608463395] 272 | ] 273 | ''' 274 | # Now you can add markers to show each station in turn 275 | # station is a simple list showing the stationID, location, lat, long, for each station in turn 276 | #for station in STATIONS: 277 | # folium.Marker(location=[station[0], station[1]], popup=station[4], icon=folium.Icon(color='orange')).add_to(map) 278 | # Finally, add a red marker for UDDGP, the deepest borehole in mainland UK 279 | #folium.Marker(location=[47.8288, 1.9324], popup='Caustic', icon=folium.Icon(color='red')).add_to(map) 280 | 281 | folium.Marker(location=[EQLAT, EQLON], popup='Earthquake_location', 282 | icon=folium.Icon(color='orange')).add_to(map) 283 | folium.Marker(location=[STA_LAT, STA_LON], popup=LOCATION, 284 | icon=folium.Icon(color='red')).add_to(map) 285 | map.save(FILE_STEM + '-earthquakemap.html') 286 | 287 | 288 | ''' 289 | if plotit == True: 290 | folium.Circle( 291 | location=[bestloc[0],bestloc[1]], 292 | popup=str(lat) + " " + str(lon) + " " + str(DISTANCE), 293 | radius=1, 294 | color='crimson', 295 | fill=True, 296 | fill_color='crimson' 297 | ).add_to(map) 298 | if plotit == True: 299 | folium.Circle( 300 | location=[bestloc[0],bestloc[1]+360], 301 | popup=str(lat) + " " + str(lon) + " " + str(DISTANCE), 302 | radius=1, 303 | color='crimson', 304 | fill=True, 305 | fill_color='crimson' 306 | ).add_to(map) 307 | if plotit == True: 308 | folium.Circle( 309 | location=[bestloc[0],bestloc[1]-360], 310 | popup=str(lat) + " " + str(lon) + " " + str(DISTANCE), 311 | radius=1, 312 | color='crimson', 313 | fill=True, 314 | fill_color='crimson' 315 | ).add_to(map) 316 | 317 | if plotit == True: 318 | folium.Circle( 319 | location=[bestloc[0],bestloc[1]], 320 | popup=str(lat) + " " + str(lon) + " " + str(DISTANCE), 321 | radius=1, 322 | color='crimson', 323 | fill=True, 324 | fill_color='crimson' 325 | ).add_to(map) 326 | if plotit == True: 327 | folium.Circle( 328 | location=[bestloc[0],bestloc[1]+360], 329 | popup=str(lat) + " " + str(lon) + " " + str(DISTANCE), 330 | radius=1, 331 | color='crimson', 332 | fill=True, 333 | fill_color='crimson' 334 | ).add_to(map) 335 | if plotit == True: 336 | folium.Circle( 337 | location=[bestloc[0],bestloc[1]-360], 338 | popup=str(lat) + " " + str(lon) + " " + str(DISTANCE), 339 | radius=1, 340 | color='crimson', 341 | fill=True, 342 | fill_color='crimson' 343 | ).add_to(map) 344 | ''' -------------------------------------------------------------------------------- /Alaska-2020-08-13-map.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wmvanstone/LearnPythonForObspy/73ee37c075970778b324355517ae8c07bc5b880f/Alaska-2020-08-13-map.PNG -------------------------------------------------------------------------------- /Basic-filter.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wmvanstone/LearnPythonForObspy/73ee37c075970778b324355517ae8c07bc5b880f/Basic-filter.PNG -------------------------------------------------------------------------------- /Cornish Quake.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wmvanstone/LearnPythonForObspy/73ee37c075970778b324355517ae8c07bc5b880f/Cornish Quake.PNG -------------------------------------------------------------------------------- /Earthquake-phases.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wmvanstone/LearnPythonForObspy/73ee37c075970778b324355517ae8c07bc5b880f/Earthquake-phases.png -------------------------------------------------------------------------------- /Filter-Summary.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wmvanstone/LearnPythonForObspy/73ee37c075970778b324355517ae8c07bc5b880f/Filter-Summary.png -------------------------------------------------------------------------------- /Jamaica quake.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wmvanstone/LearnPythonForObspy/73ee37c075970778b324355517ae8c07bc5b880f/Jamaica quake.PNG -------------------------------------------------------------------------------- /Kuril'sk quake.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wmvanstone/LearnPythonForObspy/73ee37c075970778b324355517ae8c07bc5b880f/Kuril'sk quake.PNG -------------------------------------------------------------------------------- /L01 hello.py: -------------------------------------------------------------------------------- 1 | # Greeting by name 2 | name = input("Hello, what is your name? ") 3 | age = int(input("Hello " + name + " how old are you in years? ")) 4 | year = 2020 - age 5 | print("You were born in " + str(year-1) + " or " + str(year)) -------------------------------------------------------------------------------- /L02 input and assignment.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wmvanstone/LearnPythonForObspy/73ee37c075970778b324355517ae8c07bc5b880f/L02 input and assignment.PNG -------------------------------------------------------------------------------- /L02 input and assignment.py: -------------------------------------------------------------------------------- 1 | # Use input() function to collect string input 2 | # assign the string to the variable name using = 3 | name = input("Hello, what is your name? ") 4 | # Convert from string to integer (whole number) using int(). 5 | # Assign the input to the variable age using an = sign. 6 | age = int(input("Hello " + name + " how old are you in years? ")) 7 | # perform a calcualation to find birth year 8 | year = 2020 - age 9 | # print the results, using string concatenation with + 10 | # and type casting, from integer to string, using str() 11 | print("You were born in " + str(year-1) + " or " + str(year)) -------------------------------------------------------------------------------- /L03 sequence and selection.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wmvanstone/LearnPythonForObspy/73ee37c075970778b324355517ae8c07bc5b880f/L03 sequence and selection.PNG -------------------------------------------------------------------------------- /L03 sequence-selection.py: -------------------------------------------------------------------------------- 1 | # Lesson 3 - sequence and selection 2 | # The SEQUENCE of program flow is from top to bottom and left to right 3 | authorised_user = "Mark" 4 | name = input("Please enter your name: ") 5 | # Program flow branches at if statements. If allows SELECTION to be performed. 6 | # The == operator tests for equality. Are name and authorised user the same? 7 | # If the condition is matched, then the subsequent indented block of code is run. 8 | # I always use a tab character for the indent. The colon : here is essential. 9 | if name == authorised_user: 10 | print("Good morning " + name) 11 | print("You are welcome to explore my facilities") 12 | # elif stands for else if. It is only tested when the first if condition is not met. 13 | elif name == "Dr Chandra": 14 | print("Good morning Dr Chandra, this is HAL") 15 | print("I am ready for my first lesson") 16 | # You can have multiple elif statements 17 | elif name == "Superuser": 18 | print("Good morning " + name) 19 | print("You have administrator access") 20 | # else is what happens if neither if or elif conditions are met. 21 | else: 22 | print("Good morning " + name) 23 | print("You have guest access.") 24 | # You don't need to capture the return value from the input() function 25 | input("Press Enter to continue") 26 | # triple-quoted strings can be split across multiple lines 27 | print(''' 28 | TTTTT H H EEEEE EEEEE N N DDD 29 | T H H E E NN N D D 30 | T HHHHH EEE EEE N N N D D 31 | T H H E E N NN D D 32 | T H H EEEEE EEEEE N N DDD ''') 33 | 34 | -------------------------------------------------------------------------------- /L04 iteration and lists.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wmvanstone/LearnPythonForObspy/73ee37c075970778b324355517ae8c07bc5b880f/L04 iteration and lists.png -------------------------------------------------------------------------------- /L04 iteration and lists.py: -------------------------------------------------------------------------------- 1 | # Python 3 iteration and lists 2 | # ITERATION is how programs repeat a procedure over and over again. 3 | # Definite iteration is where the number of repetitions is defined in advance. 4 | # Definite iteration can be performed using a for loop or a while loop (in a future lesson) 5 | # Here a variable i is incremented from 0 to 9, producing 10 numbers, which are printed 6 | for i in range(10): 7 | print(i) 8 | print("") 9 | # By default, the range() function counts from zero to one less than its argument 10 | input("Press enter to continue to the ten times table") 11 | print("") 12 | 13 | # The next script prints the ten times table, from 1x10 to 12x10 14 | timestable = 10 15 | print(" " + str(timestable) + " Times Table") 16 | print("") 17 | # here, the for loop interated from 1 to 12 (one less than 13) 18 | for i in range(1, 13): 19 | print(str(i) + " x " + str(timestable) + " = " + str(i*timestable)) 20 | print("") 21 | input("Press enter to print out the seismometers") 22 | print("") 23 | 24 | # for loops can also iterate through lists containing text. 25 | # seislist is a list of Raspberry Shake seismometer names. 26 | seislist=['RB30C','RB5E8','RD93E','R82BD','R7FA5','R0353','R9FEE'] 27 | # the name of each seismometer in turn is passed to the stationID variable and printed. 28 | for stationID in seislist: 29 | # If the seismometer is this specific one, then it is identified as the home station. 30 | if stationID == 'R7FA5': 31 | print(stationID + " home") 32 | else: 33 | print(stationID) -------------------------------------------------------------------------------- /L05 cornish-stations.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wmvanstone/LearnPythonForObspy/73ee37c075970778b324355517ae8c07bc5b880f/L05 cornish-stations.PNG -------------------------------------------------------------------------------- /L05 packages and constants.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wmvanstone/LearnPythonForObspy/73ee37c075970778b324355517ae8c07bc5b880f/L05 packages and constants.PNG -------------------------------------------------------------------------------- /L05 packages and constants.py: -------------------------------------------------------------------------------- 1 | # Lesson 5: Packages need to be imported into Thonny using Tools | Manage Packages. 2 | # Search for Folium in PyPI and install the package before running this program. 3 | # Folium contains a toolkit of mapping functions that you can use once the package is imported. 4 | import folium 5 | # First we enter earthquake details and paramaters for data selection and plotting. 6 | # Constants contain data that does not change during program execution. They are named using upper case. 7 | EQ_NAME = "Mw 5.4 Nikol'skoye" 8 | EQ_LAT = 54.831 9 | EQ_LON = 166.175 10 | # Details of your station 11 | STATION = "R7FA5" # Station code of local station to plot 12 | STA_LAT = 50.2609 # Latitude of local station 13 | STA_LON = -5.0434 # Longitude of local station 14 | # Now you can plot the epicentre and seismometer on the map. 15 | # First, set up the map, centred on the earthquake epicentre. 16 | # Map() is a function which is found in the folium package, location, zoom_start and tiles are arguments. 17 | # dot notation is used to identify that Map is found inside folium. folium.Map() returns a map object. 18 | map = folium.Map(location=[EQ_LAT, EQ_LON],zoom_start=2,tiles='Stamen Terrain') 19 | # Now you can add an orange marker at the earthquake epicentre onto the map. Marker is a function in folium. 20 | folium.Marker(location=[EQ_LAT, EQ_LON], popup=EQ_NAME, icon=folium.Icon(color='orange')).add_to(map) 21 | # You can also add a red marker at the seismometer. 22 | folium.Marker(location=[STA_LAT, STA_LON], popup=STATION, icon=folium.Icon(color='red')).add_to(map) 23 | # save the file to disk as a web page. 24 | map.save('earthquakemap.html') 25 | # Locate the file and double click on it. You have an interactive map showing the epicentre and seismometer. 26 | # You can zoom in and out, as well as clicking on the markers to see the popup text. 27 | -------------------------------------------------------------------------------- /L05 plot multiple stations.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wmvanstone/LearnPythonForObspy/73ee37c075970778b324355517ae8c07bc5b880f/L05 plot multiple stations.PNG -------------------------------------------------------------------------------- /L05 plot multiple stations.py: -------------------------------------------------------------------------------- 1 | # Lesson 5b: Packages need to be imported into Thonny using Tools | Manage Packages. 2 | # Plot multiple stations, centred on the United Downs Deep Geothermal Project 3 | import folium 4 | # Set up a list of stations. This is a list of lists, a 2 dimensional list. 5 | STATIONS = [ 6 | ['RB30C','Falmouth',50.149,-5.095], 7 | ['RB5E8','Penzance',50.118,-5.539], 8 | ['RD93E','Redruth',50.234,-5.238], 9 | ['R82BD','Richard Lander',50.26,-5.103], 10 | ['R7FA5','Truro School',50.261,-5.043], 11 | ['R0353','Penair',50.267,-5.03], 12 | ['R9FEE','Truro High',50.257,-5.057] 13 | ] 14 | # The following arguments will centre the map on United Downs Deep Geothermal Project, zooming in to Cornwall. 15 | map = folium.Map(location=[50.230, -5.166],zoom_start=11,tiles='Stamen Terrain') 16 | # Now you can add markers to show each station in turn 17 | # station is a simple list showing the stationID, location, lat, long, for each station in turn 18 | for station in STATIONS: 19 | folium.Marker(location=[station[2], station[3]], popup=station[1], icon=folium.Icon(color='orange')).add_to(map) 20 | # Finally, add a red marker for UDDGP, the deepest borehole in mainland UK 21 | folium.Marker(location=[50.230, -5.166], popup='UDDGP', icon=folium.Icon(color='red')).add_to(map) 22 | # save the file to disk as a web page. 23 | map.save('cornish-stations.html') 24 | # Locate the file and double click on it. You have an interactive map showing the cornish Raspberry Shake stations. 25 | # You can zoom in and out, as well as clicking on the markers to see the popup text. 26 | -------------------------------------------------------------------------------- /L06 obspy and datetime.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wmvanstone/LearnPythonForObspy/73ee37c075970778b324355517ae8c07bc5b880f/L06 obspy and datetime.PNG -------------------------------------------------------------------------------- /L06 obspy and datetime.py: -------------------------------------------------------------------------------- 1 | # Lesson 6: Obspy and DateTime 2 | # Obspy is a Python package containing software for processing and plotting seismic data 3 | # You can read the documentation here: https://docs.obspy.org/contents.html 4 | # Install obspy in Thonny using Tools | Manage Packages, search for obspy and click install 5 | # now import the UTCDateTime class into your code. Importing just this class saves memory. 6 | from obspy import UTCDateTime 7 | # UTCDateTime converts date-time text strings into a format that can be manipulated in your code 8 | example = UTCDateTime("2020-04-21T16:35:00") 9 | # \n is an escape character, it adds an extra newline character to your printout, spacing it neatly 10 | print("The example is: " + str(example) + "\n") 11 | # If you call UTCDateTime with no argument, it returns the current Coordinated Universal Time 12 | datetime = UTCDateTime() 13 | print("The current time is: " + str(datetime) + "\n") 14 | # You can use dot notation to separate out different parts of the date-time 15 | print("The year is: " + str(datetime.year)) 16 | print("The day of the year is: " + str(datetime.julday)) 17 | print("The seconds since 1970-01-01 00:00:00 is: " + str(datetime.timestamp)) 18 | print("The day of the week is: " + str(datetime.weekday)) 19 | # To name the day of the week, you can index a tuple, like a list, but immutable (not changeable) 20 | weekdays = ("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday") 21 | print("Today is: " + weekdays[datetime.weekday] + "\n") 22 | # You can add to the time in seconds. 900s is 15 minutes. 86400s is a day. 23 | newtime = datetime + 900 24 | print("In 15 minutes the time will be: " + str(newtime.time) + "\n") 25 | # you can also subtract times to calculate a duration 26 | eq_name = "M 6.6 - 209km W of Chichi-shima, Japan" 27 | eq_time = UTCDateTime("2020-04-18 08:25:37") 28 | elapsed_time = datetime - eq_time 29 | print(str(elapsed_time) + "s have elapsed since the " + eq_name + " earthquake on " 30 | + weekdays[datetime.weekday] + " " + str(eq_time.date)) -------------------------------------------------------------------------------- /L07 obspy reading seismic data - output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wmvanstone/LearnPythonForObspy/73ee37c075970778b324355517ae8c07bc5b880f/L07 obspy reading seismic data - output.png -------------------------------------------------------------------------------- /L07 obspy reading seismic data.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wmvanstone/LearnPythonForObspy/73ee37c075970778b324355517ae8c07bc5b880f/L07 obspy reading seismic data.png -------------------------------------------------------------------------------- /L07 obspy reading seismic data.py: -------------------------------------------------------------------------------- 1 | # Python Lesson 7: reading and writing seismic data with obspy 2 | # Import the FDSN Web service client for obspy, UTCDateTime and read classes 3 | from obspy.clients.fdsn import Client 4 | from obspy import UTCDateTime 5 | # set the start of the data window, which is an earthquake rupture time in Japan 6 | starttime = UTCDateTime("2020-04-18 08:25:37") 7 | # Import data from the FDSN Web service for Raspberry Shake R38DC in Mishima, Japan 8 | # Lat:35.1351 Lon:138.9269, 497km from the epicentre, network=AM, stationID=R38DC, location=00, channel=EHZ 9 | st = Client("RASPISHAKE").get_waveforms('AM', 'R38DC', '00', 'EHZ', starttime, starttime + 500) 10 | # Look at details of the stream object. A stream object can contain many traces. 11 | input("Hit enter to see details of the stream object\n") 12 | print(st) 13 | input("\nHit enter to print the statistics associated with the first (and only) trace in this stream.\n") 14 | # the stats are accessed from the trace object using dot notation with the stats keyword 15 | # tr.stats.mseed is a dictionary, which is yet aother collection data type in Python 16 | tr = st[0] 17 | print(tr.stats) 18 | input("\nHit enter to print details of specific items of data using their keywords.\n") 19 | print("Station:", tr.stats.station) 20 | input("\nHit enter to access the actual waveform data via the data keyword on the trace.\n") 21 | # Trace data is held in an array, which behaves like an immutable list, containing data of one type. 22 | print("The trace data:", tr.data) 23 | # the index [0:4] selects the first four items from the array, [0], [1], [2], [3] 24 | print("The first four data points are: ", tr.data[0:4]) 25 | # len is a Python function which prints out the length of data in the variable that's passed to it 26 | print("The length of the trace is: ", len(tr), "points") 27 | input("\nHit enter to see a preview plot of the actual seismic data, with the y-axis scale in counts.\n") 28 | print("Click x to close the plot, to continue program execution.") 29 | st.plot() -------------------------------------------------------------------------------- /L07 obspy reading seismograms.py: -------------------------------------------------------------------------------- 1 | # Python Lesson 7a: reading and writing seismic data with obspy 2 | # Import the FDSN Web service client for obspy, UTCDateTime and read classes 3 | from obspy.clients.fdsn import Client 4 | from obspy import UTCDateTime, read, Stream 5 | # set the start of the data window, which is an earthquake rupture time in Japan 6 | starttime = UTCDateTime("2020-04-18 08:25:37") 7 | # Import data from the FDSN Web service for three Raspberry shakes close to an earthquake in Japan 8 | seismolist = ['REC8D', 'R38DC', 'R0BEF'] 9 | st = Stream() 10 | for seismometer in seismolist: 11 | waveform = Client("RASPISHAKE").get_waveforms('AM', seismometer, '00', 'EHZ', starttime, starttime + 500) 12 | waveform.write(seismometer + ".mseed", format="MSEED") 13 | # Write this stream to file, in the same folder as your program file, for later use, in MSEED format 14 | st += waveform 15 | # Look at details of the stream object, which now contains three traces 16 | input("Hit enter to see details of the stream object\n") 17 | print(st) 18 | input("\nHit enter to print the station names, accessed using their keywords.\n") 19 | for tr in st: 20 | print("Station:", tr.stats.station) 21 | input("\nHit enter to read the traces from file again (not really necessary) and see a preview plot.\n") 22 | # += adds what is on the right to the variable on the left, in this case, the stream that we saved earlier 23 | st = Stream() 24 | for seismometer in seismolist: 25 | st += read(seismometer + ".mseed") 26 | print("Click x to close the plot and finish.") 27 | st.plot() 28 | -------------------------------------------------------------------------------- /L07a multiple seismometers.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wmvanstone/LearnPythonForObspy/73ee37c075970778b324355517ae8c07bc5b880f/L07a multiple seismometers.PNG -------------------------------------------------------------------------------- /L07a obspy read and write - output.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wmvanstone/LearnPythonForObspy/73ee37c075970778b324355517ae8c07bc5b880f/L07a obspy read and write - output.PNG -------------------------------------------------------------------------------- /L07a obspy read and write.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wmvanstone/LearnPythonForObspy/73ee37c075970778b324355517ae8c07bc5b880f/L07a obspy read and write.PNG -------------------------------------------------------------------------------- /L07a obspy traces and mseed.py: -------------------------------------------------------------------------------- 1 | # Python Lesson 7a: reading and writing seismic data with obspy 2 | # Import the FDSN Web service client for obspy, UTCDateTime and read classes 3 | from obspy.clients.fdsn import Client 4 | from obspy import UTCDateTime, read, Stream 5 | # set the start of the data window, which is an earthquake rupture time in Japan 6 | starttime = UTCDateTime("2020-04-18 08:25:37") 7 | # Import data from the FDSN Web service for three Raspberry shakes close to an earthquake in Japan 8 | seismolist = ['REC8D', 'R38DC', 'R0BEF'] 9 | st = Stream() 10 | for seismometer in seismolist: 11 | waveform = Client("RASPISHAKE").get_waveforms('AM', seismometer, '00', 'EHZ', starttime, starttime + 500) 12 | waveform.write(seismometer + ".mseed", format="MSEED") 13 | # Write this stream to file, in the same folder as your program file, for later use, in MSEED format 14 | st += waveform 15 | # Look at details of the stream object, which now contains three traces 16 | input("Hit enter to see details of the stream object\n") 17 | print(st) 18 | input("\nHit enter to print the station names, accessed using their keywords.\n") 19 | for tr in st: 20 | print("Station:", tr.stats.station) 21 | input("\nHit enter to read the traces from file again (not really necessary) and see a preview plot.\n") 22 | # += adds what is on the right to the variable on the left, in this case, the stream that we saved earlier 23 | st = Stream() 24 | for seismometer in seismolist: 25 | st += read(seismometer + ".mseed") 26 | print("Click x to close the plot and finish.") 27 | st.plot() 28 | -------------------------------------------------------------------------------- /L08 Simplified Daily Plotter.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wmvanstone/LearnPythonForObspy/73ee37c075970778b324355517ae8c07bc5b880f/L08 Simplified Daily Plotter.PNG -------------------------------------------------------------------------------- /L08 simplified daily plotter with filtering - RA736.py: -------------------------------------------------------------------------------- 1 | # Python Lesson 8: daily plot of unfiltered data 2 | from obspy.clients.fdsn import Client 3 | from obspy import UTCDateTime 4 | # set up plotting parameters 5 | START = UTCDateTime("2020-05-04 00:00:00") 6 | DAYS = 1 7 | SEISMOMETER = 'RA736' 8 | CLIENT = Client('RASPISHAKE') 9 | # choose whether to include worldwide earthquake events on the plot. 10 | # .upper() changes the response to upper case, so "y" or "Y" will be acceptable 11 | printevents = input("Do you want to print worldwide earthquake events? Y/N ").upper() 12 | if printevents == "Y": 13 | # set the minimum magnitude of events to 5. Events come from obspy.clients.fdsn 14 | events = {'min_magnitude': 5} 15 | else: 16 | # don't plot events 17 | events = [] 18 | # loop through the required number of days from the start date/time 19 | for day in range(DAYS): 20 | starttime = START + (day * 86400) 21 | endtime = starttime + 86400 22 | # Read the seismic stream 23 | # try: ... except: ... else: will capture and report an error loading a trace without the program crashing 24 | try: 25 | st = CLIENT.get_waveforms('AM', SEISMOMETER, '00', 'EHZ', starttime, endtime) 26 | # merge fragmented traces and interpolate across any gaps 27 | st.merge(method=0, fill_value='latest') 28 | # if there is an error, report it, skip else: and continue with next day 29 | except: 30 | print("Unable to load trace data for day: " + str(day)) 31 | # else: will run if no errors occurred in try: 32 | else: 33 | filename = SEISMOMETER + "-" + str(starttime.date) + ".png" 34 | print("Printing: " + filename) 35 | # write plot to file 36 | st.plot(type="dayplot", interval=60, right_vertical_labels=True, one_tick_per_line = True, size=(1920,1080), 37 | color = ['k', 'r', 'b', 'g'], show_y_UTC_label=True, vertical_scaling_range=2000, events=events, 38 | outfile="unfiltered-"+filename) 39 | # print to screen 40 | st.plot(type="dayplot", interval=60, right_vertical_labels=True, one_tick_per_line = True, size=(1000,800), 41 | color = ['k', 'r', 'b', 'g'], show_y_UTC_label=True, vertical_scaling_range=2000, events=events) 42 | # filter 43 | st.filter("bandpass", freqmin=0.9, freqmax = 3.0, corners=4) 44 | # write plot to file 45 | st.plot(type="dayplot", interval=60, right_vertical_labels=True, one_tick_per_line = True, size=(1920,1080), 46 | color = ['k', 'r', 'b', 'g'], show_y_UTC_label=True, vertical_scaling_range=400, events=events, 47 | outfile="filtered-"+filename) 48 | # print to screen 49 | st.plot(type="dayplot", interval=60, right_vertical_labels=True, one_tick_per_line = True, size=(1000,800), 50 | color = ['k', 'r', 'b', 'g'], show_y_UTC_label=True, vertical_scaling_range=400, events=events) 51 | -------------------------------------------------------------------------------- /L08 simplified daily plotter.py: -------------------------------------------------------------------------------- 1 | # Python Lesson 8: daily plot of unfiltered data 2 | from obspy.clients.fdsn import Client 3 | from obspy import UTCDateTime 4 | # set up plotting parameters 5 | START = UTCDateTime("2020-04-21 00:00:00") 6 | DAYS = 2 7 | SEISMOMETER = 'R7FA5' 8 | CLIENT = Client('RASPISHAKE') 9 | # choose whether to include worldwide earthquake events on the plot. 10 | # .upper() changes the response to upper case, so "y" or "Y" will be acceptable 11 | printevents = input("Do you want to print worldwide earthquake events? Y/N ").upper() 12 | if printevents == "Y": 13 | # set the minimum magnitude of events to 5. Events come from obspy.clients.fdsn 14 | events = {'min_magnitude': 5} 15 | else: 16 | # don't plot events 17 | events = [] 18 | # loop through the required number of days from the start date/time 19 | for day in range(DAYS): 20 | starttime = START + (day * 86400) 21 | endtime = starttime + 86400 22 | # Read the seismic stream 23 | # try: ... except: ... else: will capture and report an error loading a trace without the program crashing 24 | try: 25 | st = CLIENT.get_waveforms('AM', SEISMOMETER, '00', 'EHZ', starttime, endtime) 26 | # merge fragmented traces and interpolate across any gaps 27 | st.merge(method=0, fill_value='latest') 28 | # if there is an error, report it, skip else: and continue with next day 29 | except: 30 | print("Unable to load trace data for day: " + str(day)) 31 | # else: will run if no errors occurred in try: 32 | else: 33 | filename = SEISMOMETER + "-" + str(starttime.date) + ".png" 34 | print("Printing: " + filename) 35 | # write plot to file 36 | st.plot(type="dayplot", interval=60, right_vertical_labels=True, one_tick_per_line = True, size=(1920,1080), 37 | color = ['k', 'r', 'b', 'g'], show_y_UTC_label=True, vertical_scaling_range=1600, events=events, 38 | outfile="unfiltered-"+filename) 39 | # print to screen 40 | st.plot(type="dayplot", interval=60, right_vertical_labels=True, one_tick_per_line = True, size=(1000,800), 41 | color = ['k', 'r', 'b', 'g'], show_y_UTC_label=True, vertical_scaling_range=1600, events=events) -------------------------------------------------------------------------------- /L09 filtering.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wmvanstone/LearnPythonForObspy/73ee37c075970778b324355517ae8c07bc5b880f/L09 filtering.PNG -------------------------------------------------------------------------------- /L09 filtering.py: -------------------------------------------------------------------------------- 1 | #Lesson 09: Filtering - adapted from: 2 | # https://docs.obspy.org/tutorial/code_snippets/filtering_seismograms.html 3 | import numpy as np # numpy is a library of numerical functions 4 | import matplotlib.pyplot as plt # matplot lib is a plotting library 5 | from obspy.clients.fdsn import Client 6 | from obspy import UTCDateTime 7 | 8 | # set up station parameters 9 | CLIENT=Client("RASPISHAKE") 10 | STATION='R7FA5' 11 | STARTTIME=UTCDateTime("2020-02-06 13:43:10") 12 | 13 | # import a trace, merge if it has become fragmented and return the mean value to the origin 14 | st=CLIENT.get_waveforms('AM',STATION,'00','EHZ',STARTTIME,STARTTIME+1000) 15 | st.merge(method=0, fill_value='latest') 16 | st.detrend(type='demean') 17 | 18 | # There is only one trace in the Stream object, let's work on that trace... 19 | tr = st[0] 20 | 21 | # Filtering with a lowpass on a copy of the original Trace 22 | tr_filt = tr.copy() 23 | # Filter the data with bandpass, bandstop, lowpass or highpass filtering 24 | # zerophase applies the filter twice, both forward and backward to eliminate phase changes 25 | tr_filt.filter('lowpass', freq=4.0, corners=2, zerophase=True) 26 | 27 | # Now let's plot the raw and filtered data... 28 | # set up an array containing values corresponding to the sample times 29 | t = np.arange(0, tr.stats.npts / tr.stats.sampling_rate, tr.stats.delta) 30 | plt.subplot(211) # divide the plot area into 2 plots high by one wide and work with plot 1 31 | plt.plot(t, tr.data, 'k') # plot the trace data in block 32 | plt.ylabel('Raw Data') # add a y-axis label 33 | plt.subplot(212) # work with plot 2 34 | plt.plot(t, tr_filt.data, 'k') # plot the filtered data in black 35 | plt.ylabel('Lowpassed Data') # add a y-axis label 36 | plt.xlabel('Time [s]') # add the x-axis label 37 | plt.suptitle(tr.stats.starttime) # add a main title for the graphs 38 | plt.show() # display on screen -------------------------------------------------------------------------------- /L09a filtering.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wmvanstone/LearnPythonForObspy/73ee37c075970778b324355517ae8c07bc5b880f/L09a filtering.PNG -------------------------------------------------------------------------------- /L09a filtering.py: -------------------------------------------------------------------------------- 1 | #L09a filtering 2 | from obspy.clients.fdsn import Client 3 | from obspy import UTCDateTime 4 | import numpy as np 5 | import matplotlib.pyplot as plt 6 | # set up station parameters 7 | CLIENT=Client("RASPISHAKE") 8 | STATION='R7FA5' 9 | STARTTIME=UTCDateTime("2020-02-06 12:55:00") 10 | COLORS=('k', 'r', 'g', 'b', 'm', 'c', 'y') 11 | # import data, merge if they have become fragmented and return the mean value to the origin 12 | st=CLIENT.get_waveforms('AM',STATION,'00','EHZ',STARTTIME,STARTTIME+3600) 13 | st.merge(method=0, fill_value='latest') 14 | st.detrend(type='demean') 15 | # make four more copies of the raw data to show the effects of the different filters 16 | for i in range(4): 17 | st += st[0].copy() 18 | # Filter each of the traces, adding the filter details to the filters list using filters.append() 19 | filters=[] 20 | st[0].filter("lowpass", freq=0.7, corners=2, zerophase=True) 21 | filters.append("Lowpass filter, freq=0.7, corners=2, zerophase=True") 22 | st[1].filter("bandpass", freqmin=0.7, freqmax=3.0, corners=4, zerophase=True) 23 | filters.append("Bandpass filter, freqmin=0.7, freqmax=3.0, corners=4, zerophase=True") 24 | st[2].filter("bandpass", freqmin=3.0, freqmax=20.0, corners=4, zerophase=True) 25 | filters.append("Bandpass filter, freqmin=3.0, freqmax=20.0, corners=4, zerophase=True") 26 | st[3].filter("highpass", freq=20.0, corners=2, zerophase=True) 27 | filters.append("Highpass filter, freq=20.0, corners=2, zerophase=True") 28 | filters.append("Raw Data") 29 | # Set up the figure for printing 30 | fig = plt.figure(1, figsize=(16, 9)) 31 | # Use Numpy to create an array of time values using the statistics from the trace header 32 | t = np.arange(0, st[0].stats.npts / st[0].stats.sampling_rate, st[0].stats.delta) 33 | for i in range(len(st)): 34 | if i == len(st)-1: # For the last plot in the list, add tick values and axis label 35 | ax = plt.subplot(5, 1, i+1) # use a grid that is 5 subplots x 1 subplot, indexed using i 36 | plt.xlabel('Time [s]') 37 | else: # suppress numbers on the axis for all but the bottom x-axis 38 | ax = plt.subplot(5, 1, i+1) 39 | plt.setp(ax.get_xticklabels(), visible=False) 40 | plt.plot(t, st[i], COLORS[i]) # plot using colors from the COLORS tuple defined above 41 | plt.ylabel(filters[i][0:8]) # show what filter has been used on the y-axis label 42 | # show the filter details in a subtitle for each subplot 43 | ax.text(0.5, 1.01, filters[i], transform=ax.transAxes, ha='center', va='bottom') 44 | # add an overall title for the plots 45 | plt.suptitle("Comparison of filtering for Raspberry Shake " + STATION + " from " 46 | + str(STARTTIME.date) + " " +str(STARTTIME.time)) 47 | # Save the plot to file, then print to screen 48 | plt.savefig('Filter-Summary.png') 49 | plt.show() 50 | -------------------------------------------------------------------------------- /L10 Basic section plot.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wmvanstone/LearnPythonForObspy/73ee37c075970778b324355517ae8c07bc5b880f/L10 Basic section plot.PNG -------------------------------------------------------------------------------- /L10 basic section plot.py: -------------------------------------------------------------------------------- 1 | #Lesson 10: basic section plot 2 | from obspy.clients.fdsn import Client 3 | from obspy import UTCDateTime, Stream 4 | from obspy.geodetics import gps2dist_azimuth # a function for calculating the distance between a seismometer and quake in metres 5 | CLIENT=Client("RASPISHAKE") 6 | 7 | # Details of the earthquake event from https://earthquake.usgs.gov/earthquakes/map/ 8 | EQLAT = 17.982 9 | EQLON = -66.945 10 | EQNAME = "M3.1 Puerto Rico" 11 | START_TIME = UTCDateTime("2020-04-24 20:31:02") 12 | DURATION = 50 # duration of record to download in seconds 13 | 14 | # Filtering parameters 15 | F1 = 1.0 # High-pass filter corner 16 | F2 = 6.0 # Low-pass filter corner 17 | 18 | # Seismometers to load (see http://www.fdsn.org/networks/detail/AM/ for a list of all stations) 19 | seismometers = [ 20 | [ 'R4DB9' , 18.018 , -66.8386 ], 21 | [ 'RD17E' , 18.0811 , -67.0314 ], 22 | [ 'RCCD1' , 17.991 , -66.6108 ], 23 | [ 'REA26' , 18.4414 , -67.1532 ], 24 | [ 'R2974' , 18.4595 , -66.3415 ], 25 | [ 'S4051' , 18.3063 , -66.0759 ], 26 | [ 'RA906' , 18.4324 , -66.0588 ]] 27 | 28 | # Load the data 29 | waveform = Stream() # set up a blank stream variable 30 | for station in seismometers: 31 | # Download and filter data 32 | st = CLIENT.get_waveforms("AM", station[0], "00", "EHZ", starttime=START_TIME, endtime=START_TIME + DURATION) 33 | st.merge(method=0, fill_value='latest') 34 | st.detrend(type='demean') 35 | st.filter('bandpass', freqmin=F1, freqmax=F2) 36 | # Add the coordinates of the stations and distance in metres from the earthquake, needed for the section plot 37 | st[0].stats["coordinates"] = {} 38 | st[0].stats["coordinates"]["latitude"] = station[1] 39 | st[0].stats["coordinates"]["longitude"] = station[2] 40 | distance = gps2dist_azimuth(station[1], station[2], EQLAT, EQLON) # (great circle distance in m, azimuth A->B, azimuth B->A) 41 | st[0].stats["distance"] = distance[0] 42 | # Add this processed stream to the waveform stream 43 | waveform += st.copy() 44 | 45 | # plot the section 46 | waveform.plot(type='section', orientation='horizontal') -------------------------------------------------------------------------------- /L10a section with data reader.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wmvanstone/LearnPythonForObspy/73ee37c075970778b324355517ae8c07bc5b880f/L10a section with data reader.PNG -------------------------------------------------------------------------------- /L10a section with data reader.py: -------------------------------------------------------------------------------- 1 | #Lesson 10a: Section plot with automatic data selection 2 | from obspy.clients.fdsn import Client 3 | from obspy import UTCDateTime, Stream 4 | from obspy.geodetics.base import locations2degrees 5 | from matplotlib.transforms import blended_transform_factory 6 | import matplotlib.pyplot as plt 7 | CLIENT=Client("RASPISHAKE") 8 | 9 | # Details of the earthquake event from https://earthquake.usgs.gov/earthquakes/map/ 10 | EQLAT = 17.982 11 | EQLON = -66.945 12 | EQNAME = "M3.1 Puerto Rico" 13 | START_TIME = UTCDateTime("2020-04-24 20:31:02") 14 | DURATION = 50 # duration of record to download in seconds 15 | MAX_DIST = 1 # Distance in degrees 16 | MIN_DIST = 0 # Distance in degrees 17 | EXCLUDE = ['S897D', 'R804D', 'R4DE3'] # Seismometers to exclude, which are noisy, or plot on top of each other 18 | F1 = 1.0 # High-pass filter corner 19 | F2 = 6.0 # Low-pass filter corner 20 | 21 | # Get the list of seismometers from file, data downloaded from http://www.fdsn.org/networks/detail/AM/ 22 | allStations = [] # list to save stations 23 | with open ("ShakeNetwork2020.csv", "r") as rd: 24 | for line in rd: # Read lines using a loop loop 25 | noCR = line.strip() # All lines (besides the last) will include newline, so strip it off 26 | allStations.append(noCR.split(',')) # Parse the line using commas 27 | 28 | # work out the distance of each seismometer from the epicentre and add them to the list if they are within the required range 29 | seismometers = [] # empty list of seismometers 30 | for station in allStations: 31 | if station[2] != "Latitude": # ignore the header line 32 | distance = locations2degrees(EQLAT, EQLON, float(station[2]), float(station[3])) # calculate the distance in degrees from the epicentre 33 | if (distance <= MAX_DIST and distance >= MIN_DIST and station[0] not in EXCLUDE): 34 | seismometers.append([station[0], round(float(station[2]),4), round(float(station[3]),4), round(distance,4)]) 35 | waveform = Stream() # set up a blank stream variable 36 | for station in seismometers: # Run through the list of seismometers which are in the required range 37 | try: # Download and filter data 38 | st = CLIENT.get_waveforms("AM", station[0], "00", "EHZ", starttime=START_TIME, endtime=START_TIME + DURATION) 39 | st.merge(method=0, fill_value='latest') 40 | st.detrend(type='demean') 41 | st.filter('bandpass', freqmin=F1, freqmax=F2) 42 | print("Loaded station ", station[0]) 43 | except: 44 | print("Unable to load station", station[0]) 45 | else: # Add lat, long and distance to the trace 46 | st[0].stats["coordinates"] = {} # add the coordinates to the dictionary, needed for the section plot 47 | st[0].stats["coordinates"]["latitude"] = station[1] 48 | st[0].stats["coordinates"]["longitude"] = station[2] 49 | st[0].stats["distance"] = station[3] 50 | waveform += st.copy() 51 | 52 | # Create the section plot 53 | fig = plt.figure(figsize=(16, 12), dpi=80) 54 | plt.title('Section plot for '+EQNAME+" "+str(START_TIME.date)+" "+str(START_TIME.time)+" lat:"+str(EQLAT)+" lon:"+str(EQLON), fontsize=12, y=1.07) 55 | plt.xlabel("Angle (degrees)") 56 | plt.ylabel("Elapsed time (seconds)") 57 | 58 | # print the data 59 | waveform.plot(size=(960,720), type='section', recordlength=DURATION, linewidth=1.5, grid_linewidth=.5, show=False, fig=fig, color='black', 60 | method='full', starttime=START_TIME, plot_dx=0.25, ev_coord = (EQLAT, EQLON), dist_degree=True, alpha=0.50, time_down=True) 61 | ax = fig.axes[0] 62 | transform = blended_transform_factory(ax.transData, ax.transAxes) # set up plotting coordinates with x-axis in data coordinates and y-axis not 63 | for tr in waveform: # for each trace, add the name of the trace at the top of the graph, along the x-axis 64 | ax.text(float(tr.stats.distance), 1.0, tr.stats.station, rotation=270, va="bottom", ha="center", transform=transform, zorder=10, fontsize=12) 65 | # add the filter details at the bottom left of the graph using data coordinates 66 | plt.text(0, DURATION *1.05, "Bandpass filter, maxfrequency = " + str(F2) + "Hz, minfrequency = " + str(F1) + "Hz") 67 | 68 | # save the file and show the plot on scren 69 | plt.savefig(EQNAME+'-Section.png') 70 | plt.show() -------------------------------------------------------------------------------- /L11 Retrieving Data.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wmvanstone/LearnPythonForObspy/73ee37c075970778b324355517ae8c07bc5b880f/L11 Retrieving Data.PNG -------------------------------------------------------------------------------- /L11 Retrieving Data.py: -------------------------------------------------------------------------------- 1 | # Lesson 11: Retrieving data 2 | # In addition to Raspberry Shakes, I have downloaded data from the BGS and USGS 3 | from obspy.clients.fdsn import Client 4 | from obspy import UTCDateTime 5 | 6 | # Use IRIS as the source for data from the BGS Carnmenellis seismometer 7 | print("Plot records from the BGS sesimometer at Carnmenellis for the Cornish earthquake of 2019-08-08") 8 | # set the data window 9 | starttime=UTCDateTime("2019-08-08 16:52:00") 10 | endtime=starttime+60 11 | # read in BGS seismometer at Carnmenellis 12 | client=Client("IRIS") 13 | waveform=client.get_waveforms('GB','CCA1','--','HHZ',starttime,endtime) 14 | waveform.plot(size=(1024,800),type='normal', automerge=True, equal_scale=False, starttime=starttime) 15 | 16 | # Use FTP (File Transfer Protocol) to get data from the BGS seismometer at Hartland 17 | from ftplib import FTP 18 | from obspy import read 19 | print("Plot records from the BGS seismometer HTL at Hartland for the Somerset earthquake of 2019-12-05") 20 | starttime=UTCDateTime("2019-12-05 22:49:30") 21 | endtime=starttime+60 22 | # Firstly download the file NETWORK.SEISMOMETER.LOCATION.CHANNEL.D.YEAR.DAYNO 23 | filename = 'GB.HTL.00.HHZ.D.2019.339' 24 | # Log in to the BGS service 25 | ftp = FTP('seiswav.bgs.ac.uk') 26 | ftp.login() 27 | # Change working directory to the Hartland 2019 data folder 28 | ftp.cwd('2019') 29 | ftp.cwd('HTL') 30 | ftp.cwd('HHZ.D') 31 | # copy the file to your computer to a folder called Data on your computer, in binary mode and close the connection 32 | fp = open("../Data/" + filename, 'wb') 33 | ftp.retrbinary('RETR '+ filename, fp.write, 1024) 34 | ftp.quit() 35 | fp.close() 36 | # read the local file, filter and plot 37 | st = read("../Data/" + filename) 38 | st.detrend(type='demean') 39 | st.filter("bandpass", freqmin=1.0, freqmax = 10.0, corners=4) 40 | waveform = st.slice(starttime, endtime) 41 | waveform.plot(size=(1024,800),type='normal', automerge=True, equal_scale=False, starttime=starttime) 42 | 43 | # Use IRIS to retrieve data from the ANMO seismometer in New Mexico 44 | print("Plot records from the Albuquerque, NM seismometer for M7.7 earthquake from Jamaica 2020-01-28 19:15:00") 45 | client=Client("IRIS") 46 | starttime=UTCDateTime("2020-01-28 19:15:00") 47 | endtime=starttime+1200 48 | waveform=client.get_waveforms('IU','ANMO','00','LHZ',starttime,endtime) 49 | waveform.plot(size=(1024,800),type='normal', automerge=True, equal_scale=False, starttime=starttime) 50 | 51 | # Use wild cards to get data from the iris federator for seismometers near a location in Nepal 52 | print("Plot records from a station near Nepal for the M7.5 earthquake in the Kuril'sk Islands on 2020-02-13 10:33:44") 53 | from obspy.clients.fdsn import RoutingClient 54 | client = RoutingClient("iris-federator") 55 | starttime = UTCDateTime("2020-02-13T10:33:44") 56 | lat = 28.0 57 | lon = 84.0 58 | minradius = 0 59 | maxradius = 10 60 | st = client.get_waveforms(channel="BHZ", station="*", network="*", starttime=starttime, endtime=starttime+1200, 61 | latitude=lat, longitude=lon, minradius=minradius, maxradius=maxradius) 62 | print(st.__str__(extended=True)) 63 | st.filter("bandpass", freqmin=1.0, freqmax=4.0, corners=2, zerophase=False) 64 | st[0].plot(size=(1024,800),type='normal', automerge=True, equal_scale=False, starttime=starttime) 65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /L12 automated section.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wmvanstone/LearnPythonForObspy/73ee37c075970778b324355517ae8c07bc5b880f/L12 automated section.PNG -------------------------------------------------------------------------------- /L12 automated section.py: -------------------------------------------------------------------------------- 1 | #Lesson 12: Automated section plotter 2 | from obspy.clients.fdsn import RoutingClient 3 | from obspy import UTCDateTime, Stream 4 | from obspy.geodetics.base import locations2degrees 5 | import matplotlib.pyplot as plt 6 | from matplotlib.transforms import blended_transform_factory 7 | CLIENT = RoutingClient("iris-federator") 8 | # Set up the event properties from https://earthquake.usgs.gov/earthquakes/eventpage/ci39406880/executive 9 | START_TIME = UTCDateTime("2020-04-27 03:46:00") # time of the earthquake 10 | EQNAME = "M4 - 13km SW of Searles Valley CA" # name of the earthquake 11 | EQLAT = 35.674 # latitude of the epicentre 12 | EQLON = -117.496 # longitude of the epicentre 13 | MIN_RADIUS = 0 # minimum radius in degrees 14 | MAX_RADIUS = 5 # maximum radius to search for in degrees 15 | SEPARATION = 0.05 # minimum distance between traces in degrees 16 | EXCLUDE = ['BUCR', 'TCHL', 'TWIT', 'JEPS', 'FARB', 'BRK'] # list of stations to exclude from the plot for noise or other quality reasons 17 | DURATION = 200 # Duration of trace shown on section in seconds 18 | F1 = 1.0 # Highpass filter corner in Hz 19 | F2 = 6.0 # Lowpass filter corner in Hz 20 | # collect the station data 21 | inventory = CLIENT.get_stations(channel="BHZ", network="*", station="*", starttime=START_TIME-10, endtime= START_TIME + DURATION + 10, 22 | latitude=EQLAT, longitude=EQLON, minradius=MIN_RADIUS, maxradius=MAX_RADIUS) 23 | # collect the traces 24 | st = CLIENT.get_waveforms( channel="BHZ", station="*", network="*", starttime=START_TIME-10, 25 | endtime= START_TIME + DURATION + 10, latitude=EQLAT, longitude=EQLON, minradius=MIN_RADIUS, maxradius=MAX_RADIUS) 26 | # copy the lat-long information from the station inventory to the traces and calculate the distance from the epicentre 27 | for y in range(len(st)): 28 | st[y].stats["distance"] = -999 # set the distance of the station from the epicentre to a null value 29 | networkID, stationID, locationID, channelID = st[y].get_id().split(".") 30 | if stationID not in EXCLUDE: 31 | for network in inventory: 32 | for station in network: 33 | if networkID == network.code: 34 | if stationID == station.code: # match the coordinates to the trace 35 | st[y].stats["coordinates"] = {} # add the coordinates to the dictionary, needed for the section plot 36 | st[y].stats["coordinates"]["latitude"] = station.latitude 37 | st[y].stats["coordinates"]["longitude"] = station.longitude 38 | distance = locations2degrees(EQLAT, EQLON, station.latitude, station.longitude) 39 | st[y].stats["distance"] = distance 40 | # sort the traces by distance and subsample them, leaving at least SEPARATION between them and copying the desired traces into st2 41 | st.sort(keys=['distance']) # sort the traces by distance from the epicentre 42 | st2 = Stream() # set up a blank stream object for data 43 | last_distance=-10 # variable to record the last distance copied to the stream for the plot 44 | for trace in st: 45 | if last_distance + SEPARATION <= trace.stats.distance: # only copy traces that are >= SEPARATION from the previous copied trace 46 | st2 += trace.copy() 47 | last_distance = trace.stats.distance 48 | print(st2.__str__(extended=True)) 49 | # filter 50 | st2.filter("bandpass", freqmin=F1, freqmax=F2, corners=2, zerophase=True) 51 | # Create the section plot 52 | fig = plt.figure(figsize=(16, 12), dpi=80) 53 | plt.title('Section plot for '+EQNAME+" "+str(START_TIME.date)+" "+str(START_TIME.time)+" lat:"+str(EQLAT)+" lon:"+str(EQLON), fontsize=12, y=1.07) 54 | # plot the data 55 | st2.plot(size=(960,720), type='section', recordlength=DURATION, linewidth=1.5, grid_linewidth=.5, show=False, fig=fig, color='black', 56 | method='full', starttime=START_TIME, plot_dx=0.25, ev_coord = (EQLAT, EQLON), dist_degree=True, alpha=0.50, time_down=True) 57 | ax = fig.axes[0] 58 | transform = blended_transform_factory(ax.transData, ax.transAxes) # set up plotting coordinates with x-axis in data coordinates and y-axis not 59 | for tr in st2: # for each trace, add the name of the trace at the top of the graph, along the x-axis 60 | ax.text(float(tr.stats.distance), 1.0, tr.stats.station, rotation=270, va="bottom", ha="center", transform=transform, zorder=10, fontsize=12) 61 | # add the filter details at the bottom left of the graph using data coordinates 62 | plt.text(0, DURATION *1.05, "Bandpass filter, maxfrequency = " + str(F2) + "Hz, minfrequency = " + str(F1) + "Hz") 63 | # save the file and show the plot on scren 64 | plt.savefig(EQNAME+'-Section.png') 65 | plt.show() -------------------------------------------------------------------------------- /L12a automated section.py: -------------------------------------------------------------------------------- 1 | #Lesson 12a: Automated section plotter that doesn't download excess traces 2 | from obspy.clients.fdsn import RoutingClient 3 | from obspy import UTCDateTime, Stream 4 | from obspy.geodetics.base import locations2degrees 5 | import matplotlib.pyplot as plt 6 | from matplotlib.transforms import blended_transform_factory 7 | CLIENT = RoutingClient("iris-federator") 8 | # Set up the event properties from https://earthquake.usgs.gov/earthquakes/eventpage/ci39406880/executive 9 | START_TIME = UTCDateTime("2020-04-27 03:46:00") # time of the earthquake 10 | EQNAME = "M4 - 13km SW of Searles Valley CA" # name of the earthquake 11 | EQLAT = 35.674 # latitude of the epicentre 12 | EQLON = -117.496 # longitude of the epicentre 13 | MIN_RADIUS = 0 # minimum radius in degrees 14 | MAX_RADIUS = 5 # maximum radius to search for in degrees 15 | SEPARATION = 0.05 # minimum distance between traces in degrees 16 | EXCLUDE = ['BUCR', 'TCHL', 'TWIT', 'JEPS', 'FARB', 'BRK', 'BABI'] # list of stations to exclude from the plot for noise 17 | DURATION = 200 # Duration of trace shown on section in seconds 18 | F1 = 1.0 # Highpass filter corner in Hz 19 | F2 = 6.0 # Lowpass filter corner in Hz 20 | # collect the station data 21 | inventory = CLIENT.get_stations(channel="BHZ", network="*", station="*", starttime=START_TIME-10, endtime= START_TIME + DURATION + 10, 22 | latitude=EQLAT, longitude=EQLON, minradius=MIN_RADIUS, maxradius=MAX_RADIUS) 23 | stations = [] 24 | for network in inventory: 25 | for station in network: 26 | distance = locations2degrees(EQLAT, EQLON, station.latitude, station.longitude) # find distance in degrees 27 | stations.append([distance, station.code, network.code, station.latitude, station.longitude]) 28 | stations.sort() # sort the stations by distance from the epicentre 29 | last_distance=-10 30 | st = Stream() 31 | # Only find and load traces from stations that are further than SEPARATION from the previous loaded station 32 | for station in stations: 33 | if last_distance + SEPARATION <= station[0] and station[1] not in EXCLUDE: 34 | try: 35 | tr=CLIENT.get_waveforms(channel="BHZ",station=station[1],network=station[2],location="*", starttime=START_TIME-10,endtime=START_TIME+DURATION+10) 36 | except: 37 | print("Can't load " + station[1]) 38 | else: 39 | if len(tr) > 0: 40 | tr.merge(method=0, fill_value='latest') 41 | tr.detrend(type='demean') 42 | last_distance = station[0] 43 | tr[0].stats["coordinates"] = {} # add the coordinates to the dictionary, needed for the section plot 44 | tr[0].stats["coordinates"]["latitude"] = station[3] 45 | tr[0].stats["coordinates"]["longitude"] = station[4] 46 | tr[0].stats["distance"] = station[0] 47 | st+=tr[0].copy() 48 | print(station) 49 | if len(st) > 0: 50 | # filter 51 | st.filter("bandpass", freqmin=F1, freqmax=F2, corners=2, zerophase=True) 52 | # Create the section plot 53 | fig = plt.figure(figsize=(16, 12), dpi=80) 54 | plt.title('Section plot for '+EQNAME+" "+str(START_TIME.date)+" "+str(START_TIME.time)+" lat:"+str(EQLAT)+" lon:"+str(EQLON), fontsize=12, y=1.07) 55 | # plot the data 56 | st.plot(size=(960,720), type='section', recordlength=DURATION, linewidth=1.5, grid_linewidth=.5, show=False, fig=fig, color='black', 57 | method='full', starttime=START_TIME, plot_dx=0.25, ev_coord = (EQLAT, EQLON), dist_degree=True, alpha=0.50, time_down=True) 58 | ax = fig.axes[0] 59 | transform = blended_transform_factory(ax.transData, ax.transAxes) # set up plotting coordinates with x-axis in data coordinates and y-axis not 60 | for tr in st: # for each trace, add the name of the trace at the top of the graph, along the x-axis 61 | ax.text(float(tr.stats.distance), 1.0, tr.stats.station, rotation=270, va="bottom", ha="center", transform=transform, zorder=10, fontsize=12) 62 | # add the filter details at the bottom left of the graph using data coordinates 63 | plt.text(0, DURATION *1.05, "Bandpass filter, maxfrequency = " + str(F2) + "Hz, minfrequency = " + str(F1) + "Hz") 64 | # save the file and show the plot on scren 65 | plt.savefig(EQNAME+'-Section.png') 66 | plt.show() 67 | else: 68 | print("No traces returned") -------------------------------------------------------------------------------- /L12b Searles-valley.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 5 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 25 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /L12b automated section with model lines.py: -------------------------------------------------------------------------------- 1 | #Lesson 12b: Automated section plotter with arrival lines that doesn't download excess traces 2 | from obspy.clients.fdsn import RoutingClient 3 | from obspy import UTCDateTime, Stream 4 | from obspy.geodetics.base import locations2degrees 5 | import matplotlib.pyplot as plt 6 | from matplotlib.transforms import blended_transform_factory 7 | from obspy.taup import TauPyModel 8 | from matplotlib import cm 9 | from numpy import linspace 10 | CLIENT = RoutingClient("iris-federator") 11 | # Set up the event properties from https://earthquake.usgs.gov/earthquakes/eventpage/ci39406880/executive 12 | START_TIME = UTCDateTime("2020-04-27 03:46:00") # time of the earthquake 13 | EQNAME = "M4 - 13km SW of Searles Valley CA" # name of the earthquake 14 | EQLAT = 35.674 # latitude of the epicentre 15 | EQLON = -117.496 # longitude of the epicentre 16 | EVT_Z = 8.8 17 | MIN_RADIUS = 0 # minimum radius in degrees 18 | MAX_RADIUS = 5 # maximum radius to search for in degrees 19 | SEPARATION = 0.05 # minimum distance between traces in degrees 20 | EXCLUDE = ['BUCR', 'TCHL', 'TWIT', 'JEPS', 'FARB', 'BRK', 'BABI'] # list of stations to exclude from the plot for noise 21 | EXCLUDE += ['B917', 'MCA', 'WCT', 'POC', 'SCH', 'WMD', 'ORC', 'MGN', 'ANT', 'ADH', 'HCK', 'LHV', 'BHU', 'BHP', 'FLV', 'BEN', 'BON', 'MCC', 'LUL', 'CPX', 'REDF', 'MPT', 'GVAR1'] 22 | DURATION = 200 # Duration of trace shown on section in seconds 23 | F1 = 1.0 # Highpass filter corner in Hz 24 | F2 = 6.0 # Lowpass filter corner in Hz 25 | PHASES = ["P", "S"] # list of phases for which to compute theoretical times 26 | PHASE_PLOT = "line" # choose lines or spots for phases 27 | MODEL = 'iasp91' 28 | COLORS = [ cm.plasma(x) for x in linspace(0, 0.8, len(PHASES)) ] # colours from 0.8-1.0 are not very visible 29 | # define functions 30 | def plottext(xtxt, ytxt, phase, vertical, horizontal, color, textlist): 31 | clash = True 32 | while clash == True: 33 | clash = False 34 | for i in range(len(textlist)): 35 | while textlist[i][0] > (xtxt - 0.1) and textlist[i][0] < (xtxt + 0.1) and textlist[i][1] > (ytxt - 0.7) and textlist[i][1] < (ytxt + 0.7): 36 | clash = True 37 | ytxt -= 0.1 38 | plt.text(xtxt, ytxt, phase, verticalalignment=vertical, horizontalalignment=horizontal, color=color, fontsize=10) 39 | textlist.append((xtxt, ytxt)) 40 | 41 | # collect the station data, can include Raspishake using ?HZ for the channel, to include BHZ and EHZ 42 | inventory = CLIENT.get_stations(channel="?HZ", network="*", station="*", starttime=START_TIME-10, endtime= START_TIME + DURATION + 10, 43 | latitude=EQLAT, longitude=EQLON, minradius=MIN_RADIUS, maxradius=MAX_RADIUS, level="channel") 44 | inventory_dictionary=inventory.get_contents() 45 | stations = [] 46 | for channel in inventory_dictionary['channels']: 47 | metadata = inventory.get_channel_metadata(channel) 48 | distance = locations2degrees(EQLAT, EQLON, metadata['latitude'], metadata['longitude']) # find distance in degrees 49 | stationID = channel.split(".") 50 | if stationID[3] in ['EHZ', 'BHZ', 'HHZ']: 51 | stations.append([distance, stationID[1] , stationID[0], stationID[2], stationID[3], metadata['latitude'], metadata['longitude']]) 52 | stations.sort() # sort the stations by distance from the epicentre 53 | last_distance=-10 54 | st = Stream() 55 | # Only find and load traces from stations that are further than SEPARATION from the previous loaded station 56 | for station in stations: 57 | if last_distance + SEPARATION <= station[0] and station[1] not in EXCLUDE: 58 | try: 59 | tr=CLIENT.get_waveforms(station=station[1],network=station[2],location=station[3],channel=station[4],starttime=START_TIME-10,endtime=START_TIME+DURATION+10) 60 | except: 61 | print("Can't load " + station[1]) 62 | else: 63 | if len(tr) > 0: 64 | tr.merge(method=0, fill_value='latest') 65 | tr.detrend(type='demean') 66 | last_distance = station[0] 67 | tr[0].stats["coordinates"] = {} # add the coordinates to the dictionary, needed for the section plot 68 | tr[0].stats["coordinates"]["latitude"] = station[5] 69 | tr[0].stats["coordinates"]["longitude"] = station[6] 70 | tr[0].stats["distance"] = station[0] 71 | st+=tr[0].copy() 72 | print(station) 73 | if len(st) > 0: 74 | # filter 75 | st.filter("bandpass", freqmin=F1, freqmax=F2, corners=2, zerophase=True) 76 | # Create the section plot 77 | fig = plt.figure(figsize=(16, 12), dpi=80) 78 | plt.title('Section plot for '+EQNAME+" "+str(START_TIME.date)+" "+str(START_TIME.time)+" lat:"+str(EQLAT)+" lon:"+str(EQLON), fontsize=12, y=1.07) 79 | # plot the data 80 | st.plot(size=(960,720), type='section', recordlength=DURATION, linewidth=1.5, grid_linewidth=.5, show=False, fig=fig, color='black', 81 | method='full', starttime=START_TIME, plot_dx=0.25, ev_coord = (EQLAT, EQLON), dist_degree=True, alpha=0.50, time_down=True) 82 | ax = fig.axes[0] 83 | transform = blended_transform_factory(ax.transData, ax.transAxes) # set up plotting coordinates with x-axis in data coordinates and y-axis not 84 | for tr in st: # for each trace, add the name of the trace at the top of the graph, along the x-axis 85 | ax.text(float(tr.stats.distance), 1.0, tr.stats.station, rotation=270, va="bottom", ha="center", transform=transform, zorder=10, fontsize=12) 86 | # add the filter details at the bottom left of the graph using data coordinates 87 | plt.text(0.1, DURATION *1.05, "Bandpass filter, maxfrequency = " + str(F2) + "Hz, minfrequency = " + str(F1) + "Hz") 88 | # save the file and show the plot on screen 89 | textlist = [] # list of text on plot, to avoid over-writing 90 | for j, color in enumerate(COLORS): 91 | phase = PHASES[j] 92 | model = TauPyModel(model=MODEL) 93 | x=[] 94 | y=[] 95 | for dist in range(int(MIN_RADIUS*10), int((MAX_RADIUS*10)+1), 1): 96 | arrivals = model.get_travel_times(source_depth_in_km=EVT_Z,distance_in_degree=dist/10, phase_list=[phase]) 97 | printed = False 98 | for i in range(len(arrivals)): 99 | instring = str(arrivals[i]) 100 | phaseline = instring.split(" ") 101 | if phaseline[0] == phase and printed == False and int(dist/10) >= 0 and int(dist/10) <= 180 and float(phaseline[4])>=0 and float(phaseline[4])<=DURATION: 102 | x.append(float(dist/10)) 103 | y.append(float(phaseline[4])) 104 | printed = True 105 | if PHASE_PLOT == "spots": 106 | plt.scatter(x, y, color=color, alpha=0.5, s=1) 107 | else: 108 | plt.plot(x, y, color=color, linewidth=1.0, linestyle='solid', alpha=0.5) 109 | if len(x) > 0 and len(y) > 0: # this function prevents text being overwritten 110 | plottext(x[0], y[0], phase, 'top', 'right', color, textlist) 111 | plottext(x[len(x)-1], y[len(y)-1], phase, 'top', 'left', color, textlist) 112 | plt.savefig(EQNAME+'-Lines-Section.png') 113 | plt.show() 114 | else: 115 | print("No traces returned") -------------------------------------------------------------------------------- /L13 user-defined function code.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wmvanstone/LearnPythonForObspy/73ee37c075970778b324355517ae8c07bc5b880f/L13 user-defined function code.PNG -------------------------------------------------------------------------------- /L13 user-defined subroutines.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Lesson 13: User-defined functions 4 | Mark Vanstone 5 | Truro School, Cornwall 6 | April 2020 7 | """ 8 | from geopy.geocoders import Nominatim 9 | geolocator = Nominatim(user_agent="Raspberry Shake section plotter") # tool for getting place names 10 | 11 | # Subroutine for getting station address details. One parameter, station, a list [ID, lat, lon] 12 | # This subroutine does not return a value 13 | def find_address(station): 14 | try: 15 | location = str(geolocator.reverse(str(station[1]) + ", " + str(station[2]))) 16 | except: 17 | print("No address found for " + station[0]) 18 | output = "No address found" 19 | else: 20 | address = location.split(", ") 21 | output = address[0] + ", " + address[1] + ", " + address[2] + ", " + address[-1] 22 | print(output) 23 | 24 | # Subroutine for reading Raspberry Shake ID, Lat, Long data, returns a list with this data 25 | # This subroutine has no parameters. It returns a list. 26 | def read_stations(): 27 | output = [] 28 | with open ("ShakeNetwork2020.csv", "r") as rd: 29 | for line in rd: # Read lines using a loop loop 30 | noCR = line.strip() # All lines (besides the last) will include newline, so strip it off 31 | tempString = noCR.split(',') # Parse the line using commas 32 | output.append([tempString[0], tempString[2], tempString[3]]) 33 | return output 34 | 35 | # Subroutine the returns the Cornish Raspberry Shakes 36 | def cornish_stations(): 37 | STATIONS = [['RB30C',50.149,-5.095],['RB5E8',50.118,-5.539],['RD93E',50.234,-5.238], 38 | ['R82BD',50.26,-5.103],['R7FA5',50.261,-5.043],['R0353',50.267,-5.03],['R9FEE',50.257,-5.057]] 39 | return STATIONS 40 | 41 | # Main program 42 | if __name__ == "__main__": 43 | print("\nDisplay the addresses of Cornish Stations\n") 44 | stations = cornish_stations() 45 | # find the station address and print it to screen 46 | for station in stations: 47 | find_address(station) 48 | 49 | print("\nDisplay the addresses of Worldwide Stations\n") 50 | # Get the list of seismometers from file, data downloaded from http://www.fdsn.org/networks/detail/AM/ 51 | stations = read_stations() # list to save stations 52 | # find the station address and print it to screen 53 | for i in range(1, 11): 54 | find_address(stations[i]) -------------------------------------------------------------------------------- /L14 find arrival.py: -------------------------------------------------------------------------------- 1 | #Lesson 14: finding arrival times using TauP 2 | from obspy import UTCDateTime 3 | from obspy.taup import TauPyModel 4 | from obspy.geodetics.base import locations2degrees 5 | from obspy.geodetics import gps2dist_azimuth 6 | 7 | # EQ details and paramaters for data selection and plotting 8 | EQNAME = "M 6.6 - 89km S of Ierapetra, Greece" 9 | EQLAT = 34.205 10 | EQLON = 25.712 11 | EQTIME = "2020-05-02 12:51:06" 12 | EQZ = 17 13 | 14 | # Home station 15 | STA_LAT = 50.2609 # Latitude of local station 16 | STA_LON = -5.0434 # Longitude of local station 17 | LOCATION = "TRURO SCHOOL" 18 | DEGREES = locations2degrees(EQLAT, EQLON, STA_LAT, STA_LON) # Station dist in degrees from epicentre 19 | DISTANCE, _, _ = gps2dist_azimuth(STA_LAT, STA_LON, EQLAT, EQLON) # Station dist in m from epicentre 20 | 21 | # Model phases 22 | model = TauPyModel(model='iasp91') 23 | PHASES = sorted(["P", "pP", "PP", "S", "Pdiff", "PKP", "PKIKP", "PcP", "ScP", "ScS", "PKiKP", "SKiKP", "SKP", "SKS"]) # list of phases 24 | 25 | # Find the arrival times 26 | arrival_list=[] 27 | for phase in PHASES: 28 | arrivals = model.get_travel_times(source_depth_in_km=EQZ, distance_in_degree=DEGREES, phase_list=[phase]) 29 | if arrivals: 30 | for arrival in arrivals: 31 | phasestring = str(arrival) # arrivals are in this format "P phase arrival at 345.851 seconds" 32 | phaseline = phasestring.split(" ") 33 | phasetime = float(phaseline[4]) 34 | arrival_list.append([phasetime, phaseline[0]]) 35 | 36 | # Print out the earthquake details, station details and phases in order 37 | print("For " + EQNAME + " at " + EQTIME) 38 | print("Distance to " + LOCATION + " is " + str(round(DISTANCE/1000, 1)) + "km, or " + str(round(DEGREES, 1)) + "°\n") 39 | print("The phases arriving at this location, in time order are:") 40 | arrival_list.sort() 41 | for arrival in arrival_list: 42 | arrival_time = (UTCDateTime(EQTIME)+arrival[0]) 43 | print(arrival[1] + " phase arrival after " + str(round(arrival[0], 0)) + "s at " + (arrival_time.strftime("%Y-%m-%d %H:%M:%S"))) -------------------------------------------------------------------------------- /L15 - plot phases part A.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wmvanstone/LearnPythonForObspy/73ee37c075970778b324355517ae8c07bc5b880f/L15 - plot phases part A.PNG -------------------------------------------------------------------------------- /L15 - plot phases part B.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wmvanstone/LearnPythonForObspy/73ee37c075970778b324355517ae8c07bc5b880f/L15 - plot phases part B.PNG -------------------------------------------------------------------------------- /L15 - plot phases.py: -------------------------------------------------------------------------------- 1 | from obspy.clients.fdsn import Client # for seismic data download 2 | from obspy import UTCDateTime # for turning the date/time into a datetime object 3 | import numpy as np # for more advanced maths functions 4 | import matplotlib.pyplot as plt # for graph plotting 5 | from obspy.taup import TauPyModel # source of the phase arrival information 6 | from matplotlib.cm import get_cmap # colourmap for coloured lines 7 | from obspy.geodetics.base import locations2degrees # for distance in degrees 8 | from obspy.geodetics import gps2dist_azimuth # for distance in metres 9 | import matplotlib.transforms as transforms # for plotting in data or plot coordinates 10 | model = TauPyModel(model='iasp91') # The model containing the phase arrivals 11 | client=Client("RASPISHAKE") # the seismic data client 12 | 13 | # EQ details and paramaters for data selection and plotting 14 | EQNAME = "M6.2 Java, Indonesia" 15 | EQLAT = -6.073 16 | EQLON = 113.116 17 | EQTIME = "2020-02-05 18:12:37" 18 | EQZ=589.6 19 | STARTTIME = UTCDateTime(EQTIME) 20 | PSTART = 500 21 | PEND = 3500 22 | DURATION = PEND-PSTART 23 | 24 | # Home station 25 | NETWORK = 'AM' # AM = RaspberryShake network 26 | STATION = "RD5F3" # Station code of local station to plot 27 | STA_LAT = 34.76576577 # Latitude of local station 28 | STA_LON = -112.5250159 # Longitude of local station 29 | CHANNEL = 'EHZ' # channel to grab data for (e.g. EHZ, SHZ, EHE, EHN) 30 | LOCATION = "Chino Valley" 31 | DISTANCE=locations2degrees(EQLAT, EQLON, STA_LAT, STA_LON) # Station dist in degrees from epicentre 32 | STA_DIST, _, _ = gps2dist_azimuth(STA_LAT, STA_LON, EQLAT, EQLON) # Station dist in m from epicentre 33 | 34 | # Pretty paired colors. Reorder to have saturated colors first and remove some colors at the end. 35 | CMAP = get_cmap('Paired', lut=12) 36 | COLORS = ['#%02x%02x%02x' % tuple(int(col * 255) for col in CMAP(i)[:3]) for i in range(12)] 37 | COLORS = COLORS[1:][::2][:-1] + COLORS[::2][:-1] 38 | 39 | # get the data, detrend and filter 40 | st=client.get_waveforms('AM',STATION,'00','EHZ',STARTTIME+PSTART,STARTTIME+PEND) 41 | st.merge(method=0, fill_value='latest') 42 | st.detrend(type='demean') 43 | st.filter("bandpass", freqmin=0.8, freqmax = 3.0, corners=2, zerophase=True) 44 | FILTERLABEL = 'bandpass, freqmin=0.8, freqmax=3.0' 45 | 46 | # Plot figure with subplots of different sizes 47 | fig = plt.figure(1, figsize=(19, 11)) 48 | ax = fig.add_axes([0.1, 0.1, 0.7, 0.8]) # left margin, bottom margin, width, height 49 | time = np.arange(PSTART, PEND+0.01, 0.01) # A list of elapsed times for the stream file 50 | 51 | # set up plot parameters 52 | ax.set_xlim([PSTART, PEND]) 53 | ax.tick_params(axis="both", direction="in", which="both", right=True, top=True) 54 | # plot the data 55 | ax.plot(time, st[0], linewidth=0.75) 56 | plt.text(0.5, 1.07, "Core phases from iasp91 model for " + str(EQZ) + "km deep " + EQNAME + " earthquake on " + EQTIME + 57 | "\nrecorded on Raspberry Shake " + STATION + " in " + LOCATION + " at " + str(STA_DIST//1000) +"km, or " + str(round(DISTANCE,1)) + 58 | "° from the epicentre.\nFilter: " + FILTERLABEL, transform=ax.transAxes, horizontalalignment='center', verticalalignment='top') 59 | plt.text(0.5, -0.05, "Elapsed time [s]", transform=ax.transAxes, horizontalalignment='center', verticalalignment='top') 60 | plt.text(-0.05, 0.5, "Counts", rotation=90, va="center", ha="center", transform=ax.transAxes, zorder=10, fontsize=10) 61 | # Find the arrivals for this epicentral distance and earthquake depth 62 | arrivals = model.get_travel_times(source_depth_in_km=EQZ, distance_in_degree=DISTANCE) 63 | plotted_arrivals=[] 64 | if arrivals: 65 | for j, phase in enumerate(arrivals): 66 | color = COLORS[j%10] 67 | instring = str(arrivals[j]) 68 | phaseline = instring.split(" ") 69 | phasetime = float(phaseline[4]) 70 | if PSTART < phasetime < PEND: 71 | plotted_arrivals.append([round(float(phaseline[4]), 2), phaseline[0], color]) 72 | # If there are arrivals here, plot them 73 | if plotted_arrivals: 74 | plotted_arrivals.sort() #sorts the arrivals to be plotted by arrival time 75 | trans = transforms.blended_transform_factory(ax.transData, ax.transAxes) 76 | lineNo = 0 77 | # x and y axis labels 78 | plt.text(PEND+(0.02*DURATION), 1.03, "Elapsed time [s]", alpha=1.0, c="black", fontsize=11, ha='left', va='bottom', zorder=1, transform=trans) 79 | plt.text(PEND+(0.12*DURATION), 1.03, "Phase name", alpha=1.0, c="black", fontsize=11, ha='left', va='bottom', zorder=1, transform=trans) 80 | # Plot the phases on the graph 81 | for phase_time, phase_name, color_plot in plotted_arrivals: 82 | # Phase line and label 83 | ax.vlines(phase_time, ymin=0, ymax=1, alpha=.50, color=color_plot, ls='--', zorder=1, transform=trans) 84 | plt.text(phase_time, 0.98-(lineNo*0.02), phase_name+" ", alpha=.50, c=color_plot, fontsize=11, ha='right', va='bottom', zorder=1, transform=trans) 85 | # Entry on printed table to right of plot 86 | plt.text(PEND+(0.02*DURATION), 0.98-(lineNo*0.02), str(phase_time) + "s", alpha=1.0, c=color_plot, fontsize=11, ha='left', va='bottom', zorder=1, transform=trans) 87 | plt.text(PEND+(0.12*DURATION), 0.98-(lineNo*0.02), phase_name, alpha=1.0, c=color_plot, fontsize=11, ha='left', va='bottom', zorder=1, transform=trans) 88 | # Increment the counter for the line number of the label 89 | lineNo+=1 90 | if lineNo == 21: # step over the plotted trace with the phase labels 91 | lineNo += 8 92 | # Save the plot to file, then print to screen 93 | plt.savefig('Earthquake-phases.png') 94 | plt.show() -------------------------------------------------------------------------------- /L16 - Chile-section-model-lines-2020-06-03.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """ 4 | Script to download and plot RaspberryShake station data 5 | Also computes and plots theoretical phase arrival times 6 | 7 | See https://docs.obspy.org/packages/obspy.taup.html for more info on 8 | Earth models and phase-naming nomenclature. 9 | 10 | Copy this file and ShakeNetwork2020.csv into the same folder 11 | 12 | Mark Vanstone 13 | Truro School, Cornwall 14 | Feb 2020 15 | """ 16 | from obspy.clients.fdsn import Client 17 | from obspy import UTCDateTime, Stream, read, read_inventory 18 | from obspy.taup import TauPyModel 19 | from obspy.geodetics.base import locations2degrees 20 | from matplotlib.transforms import blended_transform_factory 21 | from os import path 22 | import matplotlib.pyplot as plt 23 | from geopy.geocoders import Nominatim 24 | import numpy as np 25 | from matplotlib.cm import get_cmap 26 | from obspy.geodetics import gps2dist_azimuth 27 | DATA_PROVIDER = "RASPISHAKE" 28 | 29 | client=Client(DATA_PROVIDER) 30 | 31 | # EQ details and paramaters for data selection and plotting 32 | URL = 'https://earthquake.usgs.gov/earthquakes/eventpage/us6000a4yi/executive' 33 | EQNAME = 'M6.8 - San Pedro de Atacama, Chile' 34 | EQLAT = -23.2955 35 | EQLON = -68.4217 36 | EQZ = 96.84 37 | EQTIME = '2020-06-03T07:35:34.844Z' 38 | FILE_STEM = 'Chile-2020-06-03' 39 | MAGNITUDE = "M 6.8" 40 | 41 | DECIMATION = 1 # decimation factor, can be up to 15 to keep 1 sample point in every 15, reduces memory usage on computer 42 | # Things to change once for your station 43 | NETWORK = 'AM' # AM = RaspberryShake network 44 | STATION = "R7FA5" # Station code of local station to plot 45 | STA_LAT = 50.2609 # Latitude of local station 46 | STA_LON = -5.0434 # Longitude of local station 47 | CHANNEL = 'EHZ' # channel to grab data for (e.g. EHZ, SHZ, EHE, EHN) 48 | found_home = False 49 | # configuration of the section plot 50 | DURATION = 1500 # Length in seconds of data to plot after origin time 51 | TDOWNLOAD = 1800 # data length to download and write to file 52 | MIN_DIST = 0 # minimum distance for a seismometer in degrees 53 | MAX_DIST = 180 # maximum distance in degrees 54 | STEP = 0.75 # step in degrees between seismometers 55 | ANGLE_DX = 10 # angle between tick marks on x-axis of section 56 | PHASES = sorted(["P", "pP", "PP", "S", "Pdiff", "PKP", "PKIKP", "PcP", "ScP", "ScS", "PKiKP", "pPKiKP", "SKiKP", "SKP", "SKS", "sP"]) # list of phases for which to compute theoretical times 57 | 58 | PHASE_PLOT = "spots" # choose lines or spots for phases 59 | DPI = 80 # dpi for plot 60 | FIG_SIZE = (1920/DPI, 1080/DPI) # size of figure in inches. Slightly bigger than PLOT_SIZE 61 | PLOT_SIZE = (FIG_SIZE[0]*DPI*0.75,FIG_SIZE[1]*DPI*0.75) # plot size in pixels with borders for labels 62 | F1 = 0.9 # High-pass filter corner for seismometers up to 90 degrees 63 | F2 = 3.0 # Low-pass filter corner 64 | F3 = 0.9 # High-pass filter corner for seismometers from 90 degrees 65 | F4 = 2.5 # Low-pass filter corner 66 | MODEL = 'iasp91' # Velocity model to predict travel-times through 67 | #MODEL = 'ak135' # Velocity model to predict travel-times through 68 | 69 | EXCLUDE=['RB305','R25AC','R64DA','RFD01','R25AC','R79D5','RE81A','R7363','R04C9','RCD29','RA71B','R0BEF','R50BE','RD2D3','RBD5C'] 70 | EXCLUDE+=['R2683','R2323','R623F','R51F6','RC131','RBA56','R328C','R2370','R3F1B','R5FE9','RE1CC','RE684','RDE9F','RDEE3','R2FF2'] 71 | EXCLUDE+=['R2F34','RFB1A','R95B0','R359E','R5C4C','R5D53','RCE32', 'R10DB','R1E9D','RF212','RBF42','R03D7','R8D5C','S4458','R21C3'] 72 | EXCLUDE+=['RBF59','R2547','R24AE','RAA7F','R1033','R06C4','R8118','R77AA','R3CC7','RB7BB','R9AC0','R4186','R5A10','R1B4E','RF082'] 73 | EXCLUDE+=['RBFE8','RFCBE','R0128','RD897','R0ODC','R00DC','RO0DC', 'R8FE8','RA666','R8A6C','R21E0','R617F','RB822','R013B'] 74 | EXCLUDE+=['R7BC1','R7A15','REA0F','R4A43','R4F38','R62B3','RB9CC','RD184','RBD3F','RCB48','RC848','R00A4','RF7E5','R2E8D','RAC91'] 75 | EXCLUDE+=['RC574','R9CDF','RA877','R8C2E','R2942','R3DD0','R9627','RFE9F','R86F8','R3EE8','S63B4','RBE3F','S1822','RE4AA','R240F'] 76 | 77 | NETWORK_DATA = "ShakeNetwork2020.csv" # data file containing seismometer names and locations, different format to 2019 data 78 | GLOBE_PHASES = sorted([ 79 | # Phase, distance 80 | ('P', 26), 81 | ('PP', 60), 82 | ('pP', 45), 83 | ('PcP', 80), 84 | ('PKIKP', 150), 85 | ('PKiKP', 100), 86 | ('S', 65), 87 | ('sP', -30), 88 | ('ScS', -60), 89 | ('SKS', -82), 90 | ('ScP', -40), 91 | ('Pdiff', -120), 92 | ('PKP', -160), 93 | ('SKiKP', -100), 94 | ('SKP', -140) 95 | ]) 96 | # Calculated constants 97 | STA_DIST = locations2degrees(EQLAT, EQLON, STA_LAT, STA_LON) # distance to local station 98 | EQLATLON = (EQLAT, EQLON) 99 | BUFFER = 60 # time before and after plot for taper data 100 | START_TIME=UTCDateTime(EQTIME) 101 | END_TIME=START_TIME+DURATION 102 | # Pretty paired colors. Reorder to have saturated colors first and remove 103 | # some colors at the end. This cmap is compatible with obspy taup 104 | cmap = get_cmap('Paired', lut=12) 105 | COLORS = ['#%02x%02x%02x' % tuple(int(col * 255) for col in cmap(i)[:3]) for i in range(12)] 106 | COLORS = COLORS[1:][::2][:-1] + COLORS[::2][:-1] 107 | #COLORS = [ cm.plasma(x) for x in linspace(0, 0.8, len(PHASES)) ] # colours from 0.8-1.0 are not very visible 108 | # End of parameters to define 109 | 110 | # utility subroutines for data handling and plotting 111 | def plottext(xtxt, ytxt, phase, vertical, horizontal, color, textlist): 112 | clash = True 113 | while clash == True: 114 | clash = False 115 | for i in range(len(textlist)): 116 | while textlist[i][0] > (xtxt - 3) and textlist[i][0] < (xtxt + 3) and textlist[i][1] > (ytxt - 24) and textlist[i][1] < (ytxt + 24): 117 | clash = True 118 | ytxt -= 2 119 | plt.text(xtxt, ytxt, phase, verticalalignment=vertical, horizontalalignment=horizontal, color=color, fontsize=10) 120 | textlist.append((xtxt, ytxt)) 121 | 122 | def parse(string): 123 | out = [""] 124 | counter = 0 125 | for l in string: 126 | if l.upper() in "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.:-,": 127 | if ord(l) == 44: 128 | counter += 1 129 | out.append("") 130 | else: 131 | out[counter] += l 132 | return out 133 | 134 | # Function to read a file using the context manager 135 | def readFile(networkdata): 136 | # Read list of lines 137 | out = [] # list to save lines 138 | with open (networkdata, "r") as rd: 139 | # Read lines in loop 140 | for line in rd: 141 | # All lines (besides the last) will include newline, so strip it 142 | out.append(parse(line.strip())) 143 | return out 144 | 145 | def justnum(string): 146 | out = "" 147 | for l in string: 148 | if l in "0123456789": 149 | out += l 150 | return out 151 | 152 | def nospaces(string): 153 | out = "" 154 | for l in string.upper(): 155 | if l in "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789": 156 | out += l 157 | else: 158 | out += "_" 159 | return out 160 | 161 | # Process seismic data 162 | # Get the list of seismometers from file and sort them 163 | allStations = readFile(NETWORK_DATA) # changed to work with 2020 stations list from http://www.fdsn.org/networks/detail/AM/ 164 | # work out the distance of each seismometer from the epicentre and sort the list by distance from event 165 | seismometers = [] # empty list of seismometers 166 | for station in allStations: 167 | if station[2] != "Latitude": # ignore the header line 168 | distance = locations2degrees(EQLAT, EQLON, float(station[2]), float(station[3])) 169 | if (distance <= MAX_DIST and distance >= MIN_DIST and station[0] not in EXCLUDE): 170 | seismometers.append([station[0], round(float(station[2]),4), round(float(station[3]),4), round(distance,4)]) 171 | seismometers.sort(key = lambda i: i[3]) # Contains 'Code', Latitude, Longitude, distance (degrees), sort by distance 172 | 173 | # read in seismic traces 174 | waveform = Stream() # set up a blank stream variable 175 | loaded = [] 176 | dist = 0 # record of how far away the next seismometer is that we are looking for 177 | readit = False # flag to say that we have successfully read the data 178 | loaded_stations = [] # list of stations successfully loaded 179 | filtertext1 = "" 180 | filtertext2 = "" 181 | 182 | geolocator = Nominatim(user_agent="Raspberry Shake section plotter") # tool for getting place names 183 | fp = open(FILE_STEM+"_stations.txt", "w") 184 | for station in seismometers: 185 | distAB, azAB, azBA = gps2dist_azimuth(station[1], station[2], EQLAT, EQLON) # Station dist in m from epicentre 186 | if station[0] == STATION or ((not((station[3] > STA_DIST-STEP) and (station[3] < STA_DIST+STEP))) and station[3] >= dist): 187 | # read in Raspberry Shake 188 | # Use file if it is there 189 | try: 190 | fileflag = "N" 191 | infile = "../" + FILE_STEM + "/"+station[0]+".mseed" 192 | invfile = "../" + FILE_STEM + "/"+station[0]+".xml" 193 | errfile = "../"+FILE_STEM+"/"+station[0]+".error" 194 | if path.isfile(infile) and path.isfile(invfile): 195 | if path.getsize(infile) > 0 and path.getsize(invfile) > 0: 196 | st = read(infile) 197 | inventory = read_inventory(invfile) 198 | fileflag = "F" 199 | except: 200 | print("Failed to read file") 201 | # If file has not been loaded and there is no error file, download data 202 | if fileflag == "N" and not path.isfile(errfile): 203 | try: 204 | # Download and filter data 205 | st = client.get_waveforms(NETWORK, station[0], "00", CHANNEL, starttime=START_TIME-BUFFER, endtime=START_TIME+TDOWNLOAD+BUFFER,attach_response=True) 206 | inventory = read_inventory('https://fdsnws.raspberryshakedata.com/fdsnws/station/1/query?network=AM&station=%s&level=resp&format=xml&nodata=404&starttime=%s' % (station[0], str(START_TIME))) 207 | fileflag = "D" 208 | st.merge(method=0, fill_value='latest') 209 | st.write(infile, format='MSEED') 210 | inventory.write(invfile, format="STATIONXML") 211 | except: 212 | print("Failed to download trace " + station[0]) 213 | # in the case that either a file has been loaded or trace downloaded 214 | if fileflag != "N": 215 | try: 216 | st.detrend(type='demean') 217 | pre_filt = [0.001, 0.005, 45, 50] 218 | st.remove_response(inventory=inventory, pre_filt=pre_filt,output="DISP",water_level=60,taper=True)#, plot=True) 219 | if station[3] <= 90: 220 | st.filter('bandpass', freqmin=F1, freqmax=F2) 221 | filtertext1 = "Bandpass Filter: freqmin="+str(F1)+", freqmax="+str(F2)+" at up to 90 degrees." 222 | else: 223 | st.filter('bandpass', freqmin=F3, freqmax=F4) 224 | filtertext2 = "Bandpass Filter: freqmin="+str(F3)+", freqmax="+str(F4)+" at greater than 90 degrees." 225 | st.decimate(DECIMATION) 226 | test = st.slice(START_TIME, END_TIME) 227 | if len(test[0]) > 0: 228 | if station[0] == STATION: # make an additional copy to blacken plot 229 | waveform += st.slice(START_TIME, END_TIME) 230 | loaded_stations.append(station) 231 | loaded.append(station) 232 | sta_x = len(loaded_stations)-1 233 | found_home = True 234 | waveform += st.slice(START_TIME, END_TIME) 235 | outstring = str(station[1])+"\t"+str(station[2])+"\t"+str(station[3])+"\t"+str(abs(waveform[-1].max()))+"\t"+ station[0]+ "\t"+str( distAB)+ "\t"+str( azAB) + "\t" +str( azBA) + "\n" 236 | fp.write(outstring) 237 | print(fileflag + "\t" + outstring, end="") 238 | readit = True 239 | except: 240 | print("Failed to remove response " + station[0]) 241 | else: 242 | readit = False 243 | 244 | if readit == False: 245 | ef = open(errfile, "w") 246 | ef.write("Error reading file") 247 | ef.close() 248 | 249 | if readit == True: 250 | # locate the seismometer and add this to the station record 251 | try: 252 | location = geolocator.reverse(str(station[1]) + ", " + str(station[2])) 253 | address_list = location.address.split(",") 254 | if len(address_list) > 4: 255 | address = ",".join(address_list[-1*(len(address_list)-2):]).strip() # remove the two most specific parts 256 | else: 257 | address = ",".join(address_list[:]).strip() # use the whole address 258 | except: 259 | address = "Unknown" 260 | station.append(address) 261 | loaded_stations.append(station) 262 | loaded.append(station) # Add the details of loaded seismometers to the loaded list 263 | readit = False 264 | if dist <= station[3]: 265 | dist = station[3] + STEP 266 | 267 | fp.close() 268 | if len(waveform) == len(loaded_stations): 269 | # add station details to the waveforms and print out the details for interest 270 | opfile = open(nospaces(FILE_STEM)+'-Stations.txt', "w") 271 | for y in range(len(waveform)): 272 | waveform[y].stats["coordinates"] = {} # add the coordinates to the dictionary, needed for the section plot 273 | waveform[y].stats["coordinates"]["latitude"] = loaded_stations[y][1] 274 | waveform[y].stats["coordinates"]["longitude"] = loaded_stations[y][2] 275 | waveform[y].stats["distance"] = loaded_stations[y][3] 276 | # Set the abbreviated name of the location in the network field for the plot title 277 | waveform[y].stats.network = NETWORK 278 | waveform[y].stats.station = loaded_stations[y][0] 279 | 280 | waveform[y].stats.location = loaded_stations[y][4] 281 | waveform[y].stats.channel = CHANNEL 282 | # opfile.write("--------------------------------------------------------------------------------------------------------\n") 283 | try: 284 | opfile.write(str(y) + " " + loaded_stations[y][0] + " Lat: " + str(loaded_stations[y][1]) + " Lon: " + str(loaded_stations[y][2]) + " Dist: " + 285 | str(loaded_stations[y][3]) + " degrees, Address: " + loaded_stations[y][4] + "\n") 286 | except: 287 | opfile.write(str(y) + " " + loaded_stations[y][0] + " Lat: " + str(loaded_stations[y][1]) + " Lon: " + str(loaded_stations[y][2]) + " Dist: " + 288 | str(loaded_stations[y][3]) + " degrees, Address: Not writable \n") 289 | print("--------------------------------------------------------------------------------------------------------") 290 | print(loaded_stations[y][0], "Lat:", loaded_stations[y][1], "Lon:", loaded_stations[y][2], "Dist:", 291 | loaded_stations[y][3], "degrees, Address:", loaded_stations[y][4]) 292 | print("\n", waveform[y].stats, "\n") 293 | opfile.close() 294 | 295 | # Create the section plot.. 296 | fig = plt.figure(figsize=FIG_SIZE, dpi=DPI) 297 | plt.title('Section plot for '+FILE_STEM+' '+EQTIME+" lat:"+str(EQLAT)+" lon:"+str(EQLON), fontsize=12, y=1.07) 298 | # set up the plot area 299 | plt.xlabel("Angle (degrees)") 300 | plt.ylabel("Elapsed time (seconds)") 301 | plt.suptitle("Modelled arrival times for phases using " + MODEL + " with a focal depth of " + str(EQZ) + "km", fontsize=10) 302 | # plot the waveforms 303 | waveform.plot(size=PLOT_SIZE, type='section', recordlength=DURATION, linewidth=1.5, grid_linewidth=.5, show=False, fig=fig, color='black', method='full', starttime=START_TIME, plot_dx=ANGLE_DX, ev_coord = EQLATLON, dist_degree=True, alpha=0.50, time_down=True) 304 | ax = fig.axes[0] 305 | transform = blended_transform_factory(ax.transData, ax.transAxes) 306 | for tr in waveform: 307 | ax.text(float(tr.stats.distance), 1.0, tr.stats.station, rotation=270, 308 | va="bottom", ha="center", transform=transform, zorder=10, fontsize=8) 309 | # Add the local station name in red 310 | if found_home == True: 311 | ax.text(float(waveform[sta_x].stats.distance), 1.0, waveform[sta_x].stats.station, rotation=270, 312 | va="bottom", ha="center", transform=transform, zorder=10, fontsize=8, color = 'red') 313 | 314 | # print out the filters that have been used 315 | plt.text(0, DURATION *1.05, filtertext1) 316 | plt.text(0, DURATION *1.07, filtertext2) 317 | 318 | # Print the coloured phases over the seismic section 319 | textlist = [] # list of text on plot, to avoid over-writing 320 | for j, phase in enumerate(PHASES): 321 | color = COLORS[PHASES.index(phase) % len(COLORS)] 322 | x=[] 323 | y=[] 324 | model = TauPyModel(model=MODEL) 325 | for dist in range(MIN_DIST, MAX_DIST+1, 1): # calculate and plot one point for each degree from 0-180 326 | arrivals = model.get_travel_times(source_depth_in_km=EQZ,distance_in_degree=dist, phase_list=[phase]) 327 | printed = False 328 | for i in range(len(arrivals)): 329 | instring = str(arrivals[i]) 330 | phaseline = instring.split(" ") 331 | if phaseline[0] == phase and printed == False and int(dist) > 0 and int(dist) < 180 and float(phaseline[4])>0 and float(phaseline[4])