Three years of silence, the need to say something. Consuming seismo data webservice.
Currently I'm employed as a seismologist, I did not do programming for ages, so why not to try some quick dirty-spaghetti seismo-related coding?
Here is the link to fetch info on last 10 earthquakes: "http://www.seismicportal.eu/fdsnws/event/1/query?limit=10" Let's start with Windows Forms app, later I'll try to shift it to ASP.NET platform, the newest ASP.NET core looks terrific.
The form is to feed a datagridview with latest quakes info from the web-service on a timer. Distance to quakes from my location will be calculated. Selection of quakes to feed the datagridview will be based on magnitude and the distance. Times of expected phases of a quake wave will be calculated and added to the datagridview. The phases are P, S, LQ, LR, PKIKP, PKHKP etc.
First, install free Microsoft Visual Studio Community Edition and create a Windows Forms app C# project. Add to its form a datagridview control, a timer and a button. A timer is to periodically launch a sub to fetch the data from the web-service into an XDocument.
The code for the sub could be:
var xdoc = XDocument.Parse(GetHttpWebRequest("http://www.seismicportal.eu/fdsnws/event/1/query?limit=" + limit));
var Node0 = xdoc.FirstNode;
XPathNavigator nav = Node0.CreateNavigator();
nav.MoveToFirstChild();
nav.MoveToFirstChild();
do {
XPathNavigator nav1 = nav.Clone();
showQuake(nav1);
} while (nav.MoveToNext());
The XDocument xdoc is navigated through to call showQuake sub on each quake record. GetHttpWebRequest is to be shown at the end of this post. Some var declarations within showQuake sub are needed:
CultureInfo culture = null;
culture = CultureInfo.CreateSpecificCulture("en-US"); string str = String.Empty;
string preferredOriginID = String.Empty;
string region = String.Empty;
string datetime = String.Empty;
string latitude = String.Empty;
string longitude = String.Empty;
string depth = String.Empty;
string magnitude = String.Empty;
string distance = String.Empty;
string P_time = String.Empty;
string S_time = String.Empty;
string LQ_time = String.Empty;
string LR_time = String.Empty;
Now let the sub showQuake to parse the quake node in a brutal and dirty way:
nav2.MoveToFirstChild();
preferredOriginID = nav2.Value;
listOriginID.Sort();
int myIndex = listOriginID.BinarySearch(preferredOriginID);
if (myIndex < 0) {
listOriginID.Add(preferredOriginID);
}
else {
return;
}
preferredOriginID = preferredOriginID.Substring(22, 16);do {
str = nav2.Name;
switch (str)
{
case "description":
nav2.MoveToFirstChild();
region = nav2.Value;
nav2.MoveToParent();
break;
case "origin":
nav2.MoveToFirstChild();
do {
str = nav2.Name;
switch (str)
{
case "time":
nav2.MoveToFirstChild();
datetime = nav2.Value;
datetime = datetime.Substring(11, 10);
nav2.MoveToParent();
break;
case "latitude":
nav2.MoveToFirstChild();
latitude = nav2.Value;
nav2.MoveToParent();
break;
case "longitude":
nav2.MoveToFirstChild();
longitude = nav2.Value;
nav2.MoveToParent();
break;
case "depth":
nav2.MoveToFirstChild();
depth = nav2.Value.ToString();double iDepth = Double.Parse(depth, culture);depth = (iDepth / 1000).ToString();
nav2.MoveToParent();
break;
default:
break;
}
} while (nav2.MoveToNext());
nav2.MoveToParent();
break;case "magnitude":
nav2.MoveToFirstChild();
nav2.MoveToFirstChild();
str = nav2.Name;
magnitude = nav2.Value;nav2.MoveToParent();
nav2.MoveToParent();
break;
default:
break;
}
} while (nav2.MoveToNext());
So, the quake parameters related vars like latitude, magnitude etc are filled with the data. Now let's calculate quake epicenter distance from Simferopol (where I live) based on latitude and longitude.
double sLatitude = double.Parse(latitude);
double sLongitude = double.Parse(longitude);
double simLat = 44.948301;
double simLon = 34.112878;
Position pos1 = new Position();
pos1.Latitude = simLat;
pos1.Longitude = simLon;Position pos2 = new Position();
pos2.Latitude = sLatitude;
pos2.Longitude = sLongitude;Haversine hv = new Haversine();
double result = hv.Distance(pos1, pos2, DistanceType.Kilometers);
double dd = Math.Round(result / 110.8, 1);
string d = dd.ToString();
distance = Math.Round(result, 0).ToString();
The Haversine sub related stuff:
/// The distance type to return the results in.
public enum DistanceType { Miles, Kilometers };
/// Specifies a Latitude / Longitude point.
public struct Position
{
public double Latitude;
public double Longitude;
}class Haversine
{
/// Returns the distance in miles or kilometers of any two
/// latitude / longitude points.
/// <param name=”pos0″></param>
/// <param name=”pos1″></param>
/// <param name=”pos2″></param>
/// <param name=”type”></param>
/// <returns></returns>
public double Distance(Position pos1, Position pos2, DistanceType type)
{
double R = (type == DistanceType.Miles) ? 3960 : 6371;double dLat1 = pos1.Latitude;
double dLon1 = pos1.Longitude;
dLat1 = pos2.Latitude - dLat1;
dLon1 = pos2.Longitude - dLon1;double dLat = this.toRadian(dLat1);
double dLon = this.toRadian(dLon1);double a = Math.Sin(dLat / 2) * Math.Sin(dLat / 2) +
Math.Cos(this.toRadian(pos1.Latitude)) * Math.Cos(this.toRadian(pos2.Latitude)) *
Math.Sin(dLon / 2) * Math.Sin(dLon / 2);
double c = 2 * Math.Asin(Math.Min(1, Math.Sqrt(a)));
double d = R * c;return d;
}
private double toRadian(double val)
{
return (Math.PI / 180) * val;
}
}
Based on the calculated distance, the times of phases of the quake wave are to be calculated:
calcPStime(datetime, dd, ref P_time, ref S_time);
calcLQLRtime(datetime, dd, ref LQ_time, ref LR_time);private void calcPStime(string datetime, double dd, ref string P_time, ref string S_time)
{
int dd1 = int.Parse(Math.Truncate(dd).ToString());
int dd2 = int.Parse(Math.Round((dd - dd1) * 10).ToString());double[] p_numbers = new double[105] {
0,18, 22, 46, 60, 75,89,103,116,131,
144, 158,172,185,198,211,224,237,249,261,
272, 283,292,303,312,322,332,350,359,368,
377,385,394,403,411,420,428,437,445,453,
461,470,478,486,494,502,510,518,525,533,
541,548,556,563,570,578,585,592,599,606,
612,619,626,632,639,645,653,658,664,670,
676,682,688,695,701,705,711,717,722,728,
733,738,743,748,753,758,763,768,773,777,
782,787,791,796,800,805,809,814,818,823,
827,832,836,841,845};double[] s_numbers = new double[105] {
0,13, 24, 35, 47, 57,68,79,91,101,
113, 123,133,144,155,166,176,186,197,207,
219,227,236,244,252,259,265,280,287,294,
301,309,315,322,329,336,343,349,356,363,
370,376,383,389,396,402,408,414,421,427,
433,440,445,452,458,464,470,476,482,488,
495,501,506,513,518,524,529,535,541,547,
553,558,564,569,574,580,585,590,595,600,
605,610,616,621,625,630,635,639,643,649,
653,656,661,665,669,674,677,681,685,688,
692,696,701,704,708};if (dd1 < 105)
{
DateTime time = DateTime.Parse(datetime);
P_time = time.AddSeconds(p_numbers[dd1]).ToString("HH:mm:ss");
S_time = time.AddSeconds(p_numbers[dd1]+ s_numbers[dd1]).ToString("HH:mm:ss");
}
}
private void calcLQLRtime(string datetime, double dd, ref string LQ_time, ref string LR_time)
{
int dd1 = int.Parse(Math.Truncate(dd).ToString());
int dd2 = int.Parse(Math.Round((dd - dd1) * 10).ToString());double[] LQ_numbers = new double[21] {
6.4,9.0, 11.4, 14, 16.4, 18.8,21.2,23.6,26.2,28.7,
31, 33.2,35.7,37.8,40,42.3,44.4,46.8,49,50.2,51.6};double[] LR_numbers = new double[21] {
6.7,9.3,12.2, 15.1, 17.8, 20.5,23.4,26,28.6,31.2,
33.6,36.2,38.6,41.2,43.6,46.2,48.4,50.6,52.8,54.6,56.3};if ((9 < dd1) && (dd1 < 111))
{
double ddd = dd1 / 5 - 2;
dd1 = int.Parse(Math.Truncate(ddd).ToString());
DateTime time = DateTime.Parse(datetime);
LQ_time = time.AddSeconds(LQ_numbers[dd1] * 60).ToString("HH:mm:ss");
LR_time = time.AddSeconds(LR_numbers[dd1] * 60).ToString("HH:mm:ss");
}
}
Some simplest logic to filter out non-interesting quakes could be implemented:
string[] quakeRec = { preferredOriginID, region, latitude, longitude, d,
magnitude, depth, datetime, P_time, S_time, LQ_time, LR_time };if (int.Parse(distance) < 300
|| int.Parse(distance) < 600 && double.Parse(magnitude) > 2
|| int.Parse(distance) < 1200 && double.Parse(magnitude) > 2.5
|| int.Parse(distance) < 2400 && double.Parse(magnitude) > 3
|| int.Parse(distance) < 5000 && double.Parse(magnitude) > 3.5
|| int.Parse(distance) < 10000 && double.Parse(magnitude) > 4
|| int.Parse(distance) < 25000 && double.Parse(magnitude) > 4.5
)
{
rowsAdded += 1;
dataGridQuakes.Rows.Insert(rowsAdded, quakeRec);
}
And the sub to format the datagridview, feel free to call it on the button click when the timer is started.
private void showGrid(ref DataGridView q)
{
q.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.AllCells;
q.DefaultCellStyle.Padding = new Padding(3, 0, 3, 0);
q.ColumnHeadersDefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleCenter;
string[] qn = new string[] {
"OriginID", "Region", "Latitude", "Longitude", "d", "M",
"H", "Time", "P time", "S time", "LQ time", "LR time" };
for (int i = 0; i < qn.GetUpperBound(0); i++)
{
q.Columns.Add(qn[i], qn[i]);
}
}
GetHttpWebRequest sub:
private static string GetHttpWebRequest(string url){
HttpWebRequest request = null;
HttpWebResponse response = null;
string ret = string.Empty;
try{
request = (HttpWebRequest)WebRequest.Create(url.Trim());
request.Method = "GET";
request.ContentType = "application/x-www-form-urlencoded";
request.Timeout = 15000;
response = (HttpWebResponse)request.GetResponse();
ret = new StreamReader(response.GetResponseStream(), Encoding.Default).ReadToEnd();
response.Close();
}
catch { }
finally{
if (response != null) { response.Close(); }
}
return ret;
}
Sergey D. Sukhotinsky.